mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 03:31:23 +00:00
fix(channels,sandbox): land hard breakage cluster from reviewed PR bases
Lands reviewed fixes based on #25839 (@pewallin), #25841 (@joshjhall), and #25737/@25713 (@DennisGoldfinger/@peteragility), with additional hardening + regression tests for queue cleanup and shell script safety. Fixes #25836 Fixes #25840 Fixes #25824 Fixes #25868 Co-authored-by: Peter Wallin <pwallin@gmail.com> Co-authored-by: Joshua Hall <josh@yaplabs.com> Co-authored-by: Dennis Goldfinger <dennisgoldfinger@gmail.com> Co-authored-by: peteragility <peteragility@users.noreply.github.com>
This commit is contained in:
@@ -77,10 +77,22 @@ describe("sandbox fs bridge shell compatibility", () => {
|
||||
const executables = mockedExecDockerRaw.mock.calls.map(([args]) => args[3] ?? "");
|
||||
|
||||
expect(executables.every((shell) => shell === "sh")).toBe(true);
|
||||
expect(scripts.every((script) => script.includes("set -eu;"))).toBe(true);
|
||||
expect(scripts.every((script) => /set -eu[;\n]/.test(script))).toBe(true);
|
||||
expect(scripts.some((script) => script.includes("pipefail"))).toBe(false);
|
||||
});
|
||||
|
||||
it("resolveCanonicalContainerPath script is valid POSIX sh (no do; token)", async () => {
|
||||
const bridge = createSandboxFsBridge({ sandbox: createSandbox() });
|
||||
|
||||
await bridge.readFile({ filePath: "a.txt" });
|
||||
|
||||
const scripts = mockedExecDockerRaw.mock.calls.map(([args]) => args[5] ?? "");
|
||||
const canonicalScript = scripts.find((script) => script.includes("allow_final"));
|
||||
expect(canonicalScript).toBeDefined();
|
||||
// "; " joining can create "do; cmd", which is invalid in POSIX sh.
|
||||
expect(canonicalScript).not.toMatch(/\bdo;/);
|
||||
});
|
||||
|
||||
it("resolves bind-mounted absolute container paths for reads", async () => {
|
||||
const sandbox = createSandbox({
|
||||
docker: {
|
||||
|
||||
@@ -305,7 +305,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
|
||||
"done",
|
||||
'canonical=$(readlink -f -- "$cursor")',
|
||||
'printf "%s%s\\n" "$canonical" "$suffix"',
|
||||
].join("; ");
|
||||
].join("\n");
|
||||
const result = await this.runCommand(script, {
|
||||
args: [params.containerPath, params.allowFinalSymlink ? "1" : "0"],
|
||||
});
|
||||
|
||||
@@ -31,7 +31,10 @@ const deliverDiscordReply = deliveryMocks.deliverDiscordReply;
|
||||
const createDiscordDraftStream = deliveryMocks.createDiscordDraftStream;
|
||||
type DispatchInboundParams = {
|
||||
dispatcher: {
|
||||
sendBlockReply: (payload: { text?: string }) => boolean | Promise<boolean>;
|
||||
sendBlockReply: (payload: {
|
||||
text?: string;
|
||||
isReasoning?: boolean;
|
||||
}) => boolean | Promise<boolean>;
|
||||
sendFinalReply: (payload: { text?: string }) => boolean | Promise<boolean>;
|
||||
};
|
||||
replyOptions?: {
|
||||
@@ -427,9 +430,9 @@ describe("processDiscordMessage draft streaming", () => {
|
||||
expect(deliverDiscordReply).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("suppresses block-kind payload delivery to Discord", async () => {
|
||||
it("suppresses reasoning payload delivery to Discord", async () => {
|
||||
dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
|
||||
await params?.dispatcher.sendBlockReply({ text: "thinking..." });
|
||||
await params?.dispatcher.sendBlockReply({ text: "thinking...", isReasoning: true });
|
||||
return { queuedFinal: false, counts: { final: 0, tool: 0, block: 1 } };
|
||||
});
|
||||
|
||||
@@ -441,6 +444,20 @@ describe("processDiscordMessage draft streaming", () => {
|
||||
expect(deliverDiscordReply).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("delivers non-reasoning block payloads to Discord", async () => {
|
||||
dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
|
||||
await params?.dispatcher.sendBlockReply({ text: "hello from block stream" });
|
||||
return { queuedFinal: false, counts: { final: 0, tool: 0, block: 1 } };
|
||||
});
|
||||
|
||||
const ctx = await createBaseContext({ discordConfig: { streamMode: "off" } });
|
||||
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
await processDiscordMessage(ctx as any);
|
||||
|
||||
expect(deliverDiscordReply).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("streams block previews using draft chunking", async () => {
|
||||
const draftStream = createMockDraftStream();
|
||||
createDiscordDraftStream.mockReturnValueOnce(draftStream);
|
||||
|
||||
@@ -564,9 +564,8 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
||||
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
|
||||
deliver: async (payload: ReplyPayload, info) => {
|
||||
const isFinal = info.kind === "final";
|
||||
if (info.kind === "block") {
|
||||
// Block payloads carry reasoning/thinking content that should not be
|
||||
// delivered to external channels. Skip them regardless of streamMode.
|
||||
if (payload.isReasoning) {
|
||||
// Reasoning/thinking payloads should not be delivered to Discord.
|
||||
return;
|
||||
}
|
||||
if (draftStream && isFinal) {
|
||||
|
||||
Reference in New Issue
Block a user