Repository navigation
Conversation
… approved in your terminal `npx @agent-socket/shell` prints a link for the agent's chat. Every command the agent sends to POST /run shows up in the terminal with its reason and runs only on Enter; n refuses it with an optional note back to the agent, e edits and runs it, a switches to auto. Results go back as plain text (status, exit, seconds, ran, note, truncated, then the output), directly within ~25 s or as an async task. Control, bidi and zero-width characters in agent text are shown as visible escapes. q / Ctrl-C at the prompt / the timer end the session so the link dies at once. Harness scenario 64 drives the real CLI in a pseudo-terminal against the relay. docs/plans/shell.md has the plan and the macOS menu bar app as the next step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UR8BTB2vTAfKgbiwPGD12P
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23b8c0d639
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| out() | ||
| out(cyan(`── ${call.n} · ${time} `) + dim("─".repeat(30))) | ||
| if (call.why) out(`why: ${escapeHidden(call.why)}`) | ||
| if (call.cwd !== START_DIR) out(dim(`in ${call.cwd}`)) |
There was a problem hiding this comment.
Escape the agent-supplied cwd before displaying it
When a JSON /run request supplies a cwd containing escape, bidi, or other control characters, this line writes it verbatim before approval, unlike the sanitized why and command. A caller holding the bearer link can therefore clear or rewrite the terminal or emit OSC sequences, making the approval display differ from the requested action even in step mode; render cwd through the same escaping and warning path.
Useful? React with 👍 / 👎.
| const lost = queue.filter((c) => !c.async) | ||
| for (const c of lost) { queue.splice(queue.indexOf(c), 1); c.finish({ status: "error", error: "connection dropped" }) } |
There was a problem hiding this comment.
Cancel the active synchronous call on disconnect
If the WebSocket drops before HOLD_MS while a call is displayed or executing, pump() has already shifted that call out of queue, so this filter never marks it lost even though call.async is still false. The relay returns 503 and tells the agent to retry, while the stale call can still be approved or finish locally; the retried request can then execute the same destructive operation a second time. Track and cancel the active synchronous call as well as queued calls when disconnecting.
Useful? React with 👍 / 👎.
| while (Buffer.byteLength(JSON.stringify(text)) > MAX_JSON) { | ||
| const keep = Math.floor(output.length * 0.8 / 2) | ||
| const cut = output.length - 2 * keep | ||
| output = output.slice(0, keep) + `\n[… ${cut} more characters cut …]\n` + output.slice(-keep) |
There was a problem hiding this comment.
Bound metadata while shrinking oversized results
This loop only shrinks output, so it never terminates when the fixed header fields alone exceed MAX_JSON. For example, an oversized agent-supplied nonexistent cwd is copied into error, or a user edits a near-100,000-character command and it is copied into ran; with empty output the serialized result remains over the cap forever and the CLI's event loop hangs. Truncate or cap all result fields, and stop iterating when shrinking output no longer reduces the payload.
Useful? React with 👍 / 👎.
CHANGELOG: 0.4.0 shipped without the shell, so its entry moves to Unreleased. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UccXUXqFpdT4o43WU13DhA
…e link drops, bound the result From the Codex review of #3: - The cwd an agent sends was printed raw before approval, so escape sequences in it could rewrite what you read. It is now shown escaped like the command and the reason, with the hidden-characters warning, and a cwd over 4,096 characters is refused with 400. - When the relay link dropped, only queued calls were dropped. The call on screen could still be approved after the agent got its 503 and was told to retry, so the command could run twice. It is now withdrawn if it hasn't started; if it was already running, the CLI says the agent didn't get the result. - Shrinking a result only cut the output, so oversized metadata (a huge cwd in an error, a long edited command in `ran`) spun forever. Every field is capped and the loop stops when the output can't shrink. Scenario 64 covers all three; each new check fails on the previous CLI. Scenario 58 took the token's verifier with split("_")[2], which is empty when the verifier itself starts with "_"; it now slices after the prefix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UccXUXqFpdT4o43WU13DhA
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e09af71e7b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| fs.writeFileSync(file, text + "\n", { mode: 0o600 }) | ||
| keyboard(false) | ||
| const editor = process.env.VISUAL || process.env.EDITOR || "vi" | ||
| spawnSync(SHELL, ["-c", `${editor} "$1"`, "sh", file], { stdio: "inherit" }) |
There was a problem hiding this comment.
Keep the event loop running while the command editor is open
When a user edits a multiline command and spends more than 30 seconds in the editor, spawnSync blocks the CLI's event loop, preventing the /run handler's 25-second timer from returning a task ID. The relay's independent timeout in relay/src/relay-do.ts then returns 504 tool_timeout, but closing the editor still executes the edited command locally. The agent receives no result and may retry an operation that already ran. Launch and await the editor asynchronously so task promotion and WebSocket processing continue during editing.
Useful? React with 👍 / 👎.
| cancelPrompt = () => { cancelled = true; out(); rl.close() } | ||
| rl.question(prompt, (answer) => { rl.close(); resolve(answer) }) | ||
| rl.on("close", () => { lineMode = false; cancelPrompt = null; keyboard(true); if (cancelled) resolve(null) }) | ||
| if (prefill) rl.write(prefill) |
There was a problem hiding this comment.
Preserve terminal escaping when prefilling the edit prompt
When a single-line command contains terminal control sequences and the user presses e, the original agent-supplied command reaches rl.write(prefill) without escaping. Node's readline writes those control bytes to the terminal, so an escape sequence that was safely displayed in the approval view can clear or rewrite the edit prompt immediately before the user submits the command. This is reproducible with a prefill containing \x1b[2K\x1b[1G. Use an editing path that does not render the original hidden characters verbatim, while preserving the command's actual contents.
Useful? React with 👍 / 👎.
| await closed | ||
| process.exit(0) |
There was a problem hiding this comment.
Finish process termination before exiting the CLI
If the session expires while a command ignores SIGTERM, end() calls killTree(), but its SIGKILL escalation is scheduled for two seconds later. Once the WebSocket closes, this unconditional process.exit(0) terminates the CLI before that escalation can run, leaving the command running after the session has ended. Await process-tree termination, including the escalation when necessary, before exiting.
Useful? React with 👍 / 👎.
What
npx @agent-socket/shelllets an agent anywhere (a cloud Claude session, a chat with a code sandbox) run shell commands on your machine through an agent-socket link. It needs no agent installed locally and no SSH. Every command waits in your terminal:The agent gets two tools,
GET /infoandPOST /run./runtakes the command as plain text with anx-whyheader, or JSON. It answers within about 25 s, or with a 202 task to poll. The result is plain text: status, exit code, seconds,ran:,note:, then the output, cut in the middle to fit the relay's 64 KiB task limit.shell/agents.mdis the briefing, and it includes a smallrunshell function for agents that work in a sandbox.The design is deliberately minimal. There's no command parsing, rules engine, AI reviewer or sandbox, because the agent's own harness already decides what to try and you decide what runs. There are no relay changes either: it's an ordinary SDK app, with the SDK vendored as in
gpu-browser.Files
shell/: the CLI (bin/agent-socket-shell.mjs),agents.md, README,package.json, the vendored SDK andscripts/vendor-sdk.sh.npm run buildnow vendors it too.harness/scenarios/64-shell.mjs: runs the real CLI in a pseudo-terminal (util-linuxscript) against the relay, typing keys like a person and calling the link like an agent.docs/plans/shell.md: the plan, how commands run, and the macOS menu bar app as the next step.Behaviour worth reviewing
sudocan ask for the password in the terminal and Ctrl-C works as in a shell. As in a terminal, Ctrl-C doesn't stop the command's own background jobs (cmd &). A timeout does stop the whole tree.\u{…}escapes with a warning.Test plan
node harness/run.mjs 64passes (22 checks): Enter, both the direct and the 202-task path; refusing with a note; edit; a queue of three in a givencwd; Ctrl-C; timeout leaving nothing running; long output keeping head and tail under the task cap; an escape-sequence command shown escaped; auto and back to step; a bad request getting 400;qexiting and the link answering 503 with the session ended rather than held.script,pgrepandpkill, which Ubuntu runners have.@agent-socket/shell0.1.0).🤖 Generated with Claude Code
https://claude.ai/code/session_01UR8BTB2vTAfKgbiwPGD12P