From 9a09d8be25cb8990e2cd4ce138deaad89225adfe Mon Sep 17 00:00:00 2001 From: Gautam Tayal Date: Sat, 28 Mar 2026 14:51:35 +0530 Subject: [PATCH] fix: add script invocation of opencode bash --- packages/cli/Codequest | 1 + packages/cli/legal-ai | 1 + packages/cli/opinion-trading | 1 + packages/cli/web-streaming | 1 + packages/plugins/agent-opencode/src/index.ts | 21 ++++++---- .../runtime-tmux/src/__tests__/index.test.ts | 42 +++++++++++++++++++ packages/plugins/runtime-tmux/src/index.ts | 34 ++++++++------- 7 files changed, 79 insertions(+), 22 deletions(-) create mode 160000 packages/cli/Codequest create mode 160000 packages/cli/legal-ai create mode 160000 packages/cli/opinion-trading create mode 160000 packages/cli/web-streaming diff --git a/packages/cli/Codequest b/packages/cli/Codequest new file mode 160000 index 000000000..754ac9573 --- /dev/null +++ b/packages/cli/Codequest @@ -0,0 +1 @@ +Subproject commit 754ac957381d4b32ed245f4e4346999f4ca1b5f8 diff --git a/packages/cli/legal-ai b/packages/cli/legal-ai new file mode 160000 index 000000000..866c056d8 --- /dev/null +++ b/packages/cli/legal-ai @@ -0,0 +1 @@ +Subproject commit 866c056d82fdaea3786812fd42af5746e743741f diff --git a/packages/cli/opinion-trading b/packages/cli/opinion-trading new file mode 160000 index 000000000..b052b6af3 --- /dev/null +++ b/packages/cli/opinion-trading @@ -0,0 +1 @@ +Subproject commit b052b6af3c230ec50be57f6d18e70149ceacaf7e diff --git a/packages/cli/web-streaming b/packages/cli/web-streaming new file mode 160000 index 000000000..59044e9ff --- /dev/null +++ b/packages/cli/web-streaming @@ -0,0 +1 @@ +Subproject commit 59044e9ffe4681192a264d29816119c62126693f diff --git a/packages/plugins/agent-opencode/src/index.ts b/packages/plugins/agent-opencode/src/index.ts index 68f62e3b8..843113c2e 100644 --- a/packages/plugins/agent-opencode/src/index.ts +++ b/packages/plugins/agent-opencode/src/index.ts @@ -64,8 +64,8 @@ function parseSessionList(raw: string): OpenCodeSessionListEntry[] { /** * Parse JSON stream lines from `opencode run --format json` output. - * Each line is a JSON object. We look for objects containing a session_id field. - * The step_start event typically contains the session_id. + * Each line is a JSON object. Different OpenCode versions emit either + * `session_id` or `sessionID`, so accept both. */ function buildSessionIdCaptureScript(): string { const script = ` @@ -81,8 +81,9 @@ process.stdin.on('data', chunk => { if (!trimmed) continue; try { const obj = JSON.parse(trimmed); - if (obj && typeof obj.session_id === 'string' && /^ses_[A-Za-z0-9_-]+$/.test(obj.session_id)) { - captured = obj.session_id; + const candidate = obj?.session_id ?? obj?.sessionID; + if (typeof candidate === 'string' && /^ses_[A-Za-z0-9_-]+$/.test(candidate)) { + captured = candidate; } } catch {} } @@ -90,8 +91,9 @@ process.stdin.on('data', chunk => { if (buffer.trim()) { try { const obj = JSON.parse(buffer.trim()); - if (obj && typeof obj.session_id === 'string' && /^ses_[A-Za-z0-9_-]+$/.test(obj.session_id)) { - captured = obj.session_id; + const candidate = obj?.session_id ?? obj?.sessionID; + if (typeof candidate === 'string' && /^ses_[A-Za-z0-9_-]+$/.test(candidate)) { + captured = candidate; } } catch {} } @@ -204,7 +206,12 @@ function createOpenCodeAgent(): Agent { ]; const captureScript = buildSessionIdCaptureScript(); const fallbackScript = buildSessionLookupScript(); - const runCommand = ["opencode", "run", ...runOptions, "--command", "true"].join(" "); + const runCommand = [ + "opencode", + "run", + ...runOptions, + shellEscape("Create a session and reply with exactly: READY"), + ].join(" "); const resumeOptions = [...(promptValue ? ["--prompt", promptValue] : []), ...sharedOptions]; const resumeOptionsSuffix = resumeOptions.length > 0 ? ` ${resumeOptions.join(" ")}` : ""; const missingSessionError = shellEscape( diff --git a/packages/plugins/runtime-tmux/src/__tests__/index.test.ts b/packages/plugins/runtime-tmux/src/__tests__/index.test.ts index 2f8eecbf4..b06699c25 100644 --- a/packages/plugins/runtime-tmux/src/__tests__/index.test.ts +++ b/packages/plugins/runtime-tmux/src/__tests__/index.test.ts @@ -148,6 +148,48 @@ describe("runtime.create()", () => { ); }); + it("uses a temp launch script for long launch commands", async () => { + const runtime = create(); + const longCommand = "x".repeat(250); + + mockTmuxSuccess(); + mockTmuxSuccess(); + mockTmuxSuccess(); + + await runtime.create({ + sessionId: "launch-long", + workspacePath: "/tmp/ws", + launchCommand: longCommand, + environment: {}, + }); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.stringContaining("ao-launch-test-uuid-1234.sh"), + expect.stringContaining(longCommand), + { encoding: "utf-8", mode: 0o700 }, + ); + + expect(mockExecFileCustom).toHaveBeenNthCalledWith( + 2, + "tmux", + [ + "send-keys", + "-t", + "launch-long", + "-l", + expect.stringContaining("bash "), + ], + expectedTmuxOptions, + ); + + expect(mockExecFileCustom).toHaveBeenNthCalledWith( + 3, + "tmux", + ["send-keys", "-t", "launch-long", "Enter"], + expectedTmuxOptions, + ); + }); + it("cleans up session if send-keys fails", async () => { const runtime = create(); diff --git a/packages/plugins/runtime-tmux/src/index.ts b/packages/plugins/runtime-tmux/src/index.ts index a6be813d3..cb204ab7f 100644 --- a/packages/plugins/runtime-tmux/src/index.ts +++ b/packages/plugins/runtime-tmux/src/index.ts @@ -33,6 +33,21 @@ function assertValidSessionId(id: string): void { } } +function shellQuote(value: string): string { + return `'${value.replace(/'/g, `'\\''`)}'`; +} + +function writeLaunchScript(command: string): string { + const scriptPath = join(tmpdir(), `ao-launch-${randomUUID()}.sh`); + const content = `#!/usr/bin/env bash +rm -- "$0" 2>/dev/null || true +${command} +`; + writeFileSync(scriptPath, content, { encoding: "utf-8", mode: 0o700 }); + const invocation = `bash ${shellQuote(scriptPath)}`; + return invocation +} + /** Run a tmux command and return stdout */ async function tmux(...args: string[]): Promise { const { stdout } = await execFileAsync("tmux", args, { @@ -59,23 +74,12 @@ export function create(): Runtime { await tmux("new-session", "-d", "-s", sessionName, "-c", config.workspacePath, ...envArgs); // Send the launch command — clean up the session if this fails. - // Use load-buffer + paste-buffer for long commands to avoid tmux/zsh - // truncation issues (commands >200 chars get mangled by send-keys). + // Use a temp script for long commands so the pane shows a short + // invocation instead of a pasted wall of shell. try { if (config.launchCommand.length > 200) { - const bufferName = `ao-launch-${randomUUID().slice(0, 8)}`; - const tmpPath = join(tmpdir(), `ao-launch-${randomUUID()}.txt`); - writeFileSync(tmpPath, config.launchCommand, { encoding: "utf-8", mode: 0o600 }); - try { - await tmux("load-buffer", "-b", bufferName, tmpPath); - await tmux("paste-buffer", "-b", bufferName, "-t", sessionName, "-d"); - } finally { - try { - unlinkSync(tmpPath); - } catch { - /* ignore cleanup errors */ - } - } + const invocation = writeLaunchScript(config.launchCommand); + await tmux("send-keys", "-t", sessionName, "-l", invocation); await sleep(300); await tmux("send-keys", "-t", sessionName, "Enter"); } else {