Repository navigation
Carry an agent's first prompt on its command line instead of pasting it (step 3 of 7) - #24080
brennanb2025 wants to merge 77 commits into
Conversation
The host's agent.launch typed any launch prompt into the shell as part of the launch command. A long or multi-line prompt then ran line by line in the shell, and an agent that never showed readiness or crashed at startup had nothing guarding where its text went. agent.launch now carries a prompt on the typed line only when the line stays one line, control-free and at most 512 bytes; otherwise the agent starts clean and the host pastes the prompt once the agent's own ready signal fires (bracketed paste plus its composer marker or a quiet render, read only after the shell's last hand-off, never while the pane's own shell is proven in front), with main's draft-paste bytes and an Enter 50 ms later. Orchestration worker starts wait on tui-idle as before. A replay-safe launch admits and claims its ledger row in one write, Qwen Code gets a second Enter, the desktop and phone share one launch-refusal classifier, and hosts advertise agent.launch.prompt-carry.v1. Split out of #23748, which moves the desktop source-control buttons onto this path.
…h launch line, as main did #24257 moved every multi-line or over-512-byte prompt off the typed launch line and pasted it after readiness. The phone's AI buttons and review notes, whose multi-line prompts main typed whole into zsh, then reached Claude 0.5-3 s later and their RPC reply waited for the paste. The host now names the shell a local macOS or Linux line is typed into, the way the spawn picks it, and a multi-line prompt rides a zsh line when every line is at most 512 bytes and the whole line at most 8 KB. A real-zsh test types such a line through Orca's own ready barrier and startup write, including when a slow user config makes the write land early. Elsewhere the measured unsafe cases keep the paste: bash 3.2 runs multi-line lines piecemeal, fish drops an early multi-line write, and any shell loses a line over 1 KB written early.
…unch-host-prompt-delivery # Conflicts: # src/main/runtime/tui-idle-evidence.ts
…dows lane's gate scan The Windows lane registration check read `const ZSH_PATH = process.platform === 'win32'` (the head of a multi-line ternary) as a Windows-true flag, so `describe.skipIf(!ZSH_PATH)` looked like a Windows-only suite. The file is POSIX-only; the zsh lookup is now a function.
… module Main's protocol-version.ts sits at the 300-line cap, so the prompt-carry capability pushed it over. The four agent.launch capabilities and their doc move to agent-launch-runtime-capability.ts, re-exported by name and spread into RUNTIME_CAPABILITIES at the same position; the advertised lists and every export are unchanged.
…wn module `export *` from protocol-version left the four names undefined under the mobile recording loader, which resolves a relative import through a Proxy with no own keys, so 37 phone recordings lost agent.launchReplay. Importers now name agent-launch-runtime-capability directly; protocol-version only spreads its list.
…unch-host-prompt-delivery
… local caller id Both were inert in step 1 and existed only for step 2. agent.launch will become a public plugin API, so every wire field is permanent once shipped; a top-level viewMode reads as "choose terminal vs chat", which the host decides. Step 2 introduces placement and view intent under a placement object instead.
…unch-host-prompt-delivery # Conflicts: # src/main/ipc/desktop-renderer-runtime-capabilities.test.ts
…unch-host-prompt-delivery
…es' hold anchor Main (#24375) moved Codex's provisional-header check into codex.json's provisional_startup hold anchor and deleted codex-terminal-readiness.ts, so the launch readiness hold now asks showsHoldAnchor, as main's own settled check does.
…and census the zsh fixture Main (#24375) answers a name-only title from each agent's rule file ahead of the sustained-title lane, so gemini.json's name_title settled a launch readiness wait on the shell's auto-title while Gemini was still booting. A launch now asks quiet of every weak idle verdict, as that lane did. Main's readiness census requires a recorder for every runtime fixture; the zsh prompt recording is a non-agent control. Gemini's synthetic baseline is regenerated for this PR's stated change: a bare gemini title is no longer its rest mark, so name-only rows settle weak, and a fresh working or blocked status is no longer overridden.
A launch line that is long or has several lines could lose lines or wedge a shell that is still starting, because it was typed into the terminal before the shell could read it whole. The terminal host (local daemon, and the SSH relay) now writes such a line to a script file and types one short line that runs it, for every POSIX shell; a shell that cannot read Orca's quoting runs Orca's own agent lines through /bin/sh. If the script cannot be written, the terminal says so. The SSH background launch path no longer types the line from the renderer; the relay stages it like any other terminal. Bumps the terminal daemon protocol to 40 (main took 39); a v39 daemon keeps its sessions and gets no staged line. Restacked onto #24257 from the version reviewed on top of #23748; carries none of #23748's changes.
1245810 to
55fd9fe
Compare
9e24b10 to
cb0d0fa
Compare
fish and ksh run the commands of a sourced file in the shell's own process group, so a staged agent was not a job: Ctrl-Z was dropped (a typed line stops), and the shell-in-front check saw the shell's group in the foreground while the agent ran. fish now runs the script with `eval (string collect < '<path>')`, which runs it as if typed and leaves the shell's job-control mode alone. ksh and mksh run a long Orca-built line through `/bin/sh '<path>'`, a real job; a long command the user wrote is typed as before. The real-shell test now asserts the agent's process group is its own and is the terminal's foreground group. The staging rule also reuses hasControlByte and the 512-byte budget from startup-line-prompt-carry so the carry and staging limits cannot drift.
A launch prompt for an agent whose CLI takes one could still be pasted into its screen once it looked ready: the host pasted any prompt whose typed line was over 512 bytes or held a newline (outside short-lined zsh), and the desktop's generated-prompt launches started the agent empty and pasted. A paste can lose text, land its Enter on a startup dialog (Codex's update prompt), or wait seconds for the agent to look ready. Where a launch prompt rides is now decided in one place, the startup-plan builder (carryLaunchPrompt), for every launch path: - on the agent's command line, which the previous step stages when the line is too long or multi-line to type; - in a private launch file the line points at, when the prompt is past 16,384 characters, sensitive, or one a Windows shell would damage (a control byte; on PowerShell a ", a %NAME% pair or a trailing \; past cmd's 8,191-character cap). The execution host writes it before typing the line, quoted for its shell, and grants Claude a read of its directory. A failed write refuses the spawn with a Copy-prompt notice; - pasted once the agent is ready only for an agent that takes text after start, a prompt needing a launch file its agent is not measured reading (only Claude and Codex are), a reused terminal, and a paired host, which is sent a command and never a file. This replaces the host's typed-line check (512 B, or zsh with every line <=512 B and <=8 KiB), which existed because nothing staged a long line. WSL writes its staged line and launch file into the distro and runs the line in the account's own login shell (sourced in bash, zsh and sh, eval'd in fish, through /bin/sh in ksh), with ~/.cache/orca made 0700 first. The "prompt wasn't sent" notice with Copy prompt lives here, shared by the new agent tab, background draft, paste timeout, work item and new workspace. Rebuilt on the restacked staging step; the earlier version sat on a base that carried #23748.
cb0d0fa to
de94a21
Compare
… is proven in front A launch pasted its prompt unless a shell was proven in the terminal's foreground, so any read that could not prove one let the prompt through. After an agent exited at startup, its shell turned bracketed paste on at the next prompt, readiness fired on it, and the prompt was typed into the shell: - macOS: a pane runs its shell under login, so the process-group fence's root was never the shell's group and never proved it; the cached foreground name could also still name the exited process. - Windows Git Bash and WSL: the shell-alone-in-its-job check never answers. Now one fresh read of the terminal's foreground decides: agent, shell or unknown. Only 'agent' lets a write through (paste, Enter, second Enter, reused panes too); 'shell' still drops a ready signal. A Windows host never proves the agent, so there the launch line carries the prompt at any size, as on main.
…ivery mean the agent got it Round-1 review of the rebuilt step 3. - One outcome, switched on by every caller. planLaunchPrompt answers none, on-line, launch-file or paste-after-ready; only the carried outcomes hold the plan to launch, so no caller can read "a plan exists" as "carried". buildAgentStartupPlan now builds only a prompt-less launch. The new agent tab and work items had dropped a prompt an agent could not take in a file (any agent but Claude and Codex) and reported it delivered; they now paste it. The composer paths, background sessions, agent.launch, agentSession.create and phone quick commands switch on the same outcome. - One rule for hosts that type the line raw. A Windows host or a paired Orca keeps a line to #24257's 512-byte typed budget; anything longer, multi-line or damaged by the shell rides a launch file for Claude and Codex and is pasted for others. A paired host is one predicate (launchHostIsPaired). - Delivered means the agent got it. A prompt the new tab's line carried resolves its delivery when the agent's own hook reports a turn or the agent shows its composer, and false when the PTY never spawns (a refused launch file) or exits first, so callers that resolve review threads or mark notes delivered wait for that, not for the tab. - Daemon protocol v41 writes launch files. A spawn routed to an older daemon is refused with the launch-file reason instead of typing a pointer to a file nobody wrote. - A refused launch file or WSL staged line reaches agent.launch as its reason instead of a 10 s handle timeout, and hands the prompt back with Copy prompt for a staged line too. - A WSL distro is probed only when the spawn needs its directory, and a failed probe is remembered for 30 s. - Every hook status row shows the prompt a launch file carries instead of its pointer sentence.
A host that cannot prove the launched agent holds its terminal (a Windows host, a local WSL pane on one, SSH to Windows) refuses a paste, so the one carry rule keeps such a prompt on the line, as main typed it; Claude and Codex still take a launch file for a long or shell-damaged prompt. launchHostProvesAgentInFront moves to its own module, since the typed-line shell naming beside it is gone with #24257's typed-line check.
…y and when it ends
…nto brennanb2025/launch-prompt-on-command-line
…nto brennanb2025/launch-prompt-argv-carry The foreground read keeps this branch's four-way verdict, on the shared launched-agent host the worker brief check also reads.
…nto brennanb2025/worker-brief-on-launch-line A brief that rode the launch line still skips the paste; a pasted brief takes #24257's new check. The local brief-check test uses Amp, whose brief is pasted here since an argv agent's rides the line.
…unch-host-prompt-delivery Brings main to e2da3a1, which fixes main's own localization coverage audit; no conflicts.
…t-on-command-line
…o the stack is one chain #24934 was stacked on #24257; it now sits on #24081 (24257 -> 23962 -> 24080 -> 24081 -> 24934). The prompt-delivery table keeps #24080's argv row and this branch's unconfirmed row. The restart replay test pins its stub to the paste path, since #24080's fixture now reports an offered prompt carried unless told otherwise.
…/launch-prompt-argv-carry
…/worker-brief-on-launch-line
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (11)
🚧 Files skipped from review as they are similar to previous changes (6)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughThe pull request adds host-aware prompt delivery through command arguments, launch files, or post-ready paste. It adds launch-file validation, secure writing and cleanup, WSL launch-directory probing, daemon protocol support, PTY and relay propagation, and prompt-receipt tracking. Renderer paths now display localized notices for delivery failures. Session titles and agent status prompts resolve launch-file pointers back to remembered prompt text. Related tests cover Windows quoting, WSL behavior, protocol compatibility, staging failures, and delivery outcomes. Priority: ➖ Normal Merge Risk: 🟡 Moderate · up to Some managed-worktree launches that require file transport can pass a pointer to a file that was never written, so the initial prompt may not reach the agent. Fix this before merging. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to Task text previously delivered after startup can now become visible through process listings on shared machines. Concurrent launches can also leave an unused private task file behind. Private-file protections limit exposure, but the confidentiality and cleanup implications warrant attention. Retained concerns
Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 43.48% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 69 functions across 73 files. (7 skipped: 7 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.
🟠 Major · Forward startup.startup.launchFile to… · orca-runtime-terminal-create-deduplication.ts:163-172
src/main/runtime/orca-runtime-terminal-create-deduplication.ts:163-172
🎯 Functional Correctness | 🟠 Major | ⚡ Quick winForward
startup.startup.launchFiletocreateTerminal.
buildStartupForAgentnow returns alaunchFilewhen the prompt exceeds the line ceiling. The command then names only a placeholder pointer.launchAgentTerminaldoes not passlaunchFile, so the host never writes the file. The agent receives a pointer to a nonexistent path, and the prompt is lost. Also, afollowupreturned for a paste-after-ready plan is dropped here.Proposed fix
startupCommandDelivery: startup.startup.startupCommandDelivery, + ...(startup.startup.launchFile ? { launchFile: startup.startup.launchFile } : {}), telemetry: startup.startup.telemetry,
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
df9e31f8-60ca-4e3a-a6b2-7090d8ce7106
📒 Files selected for processing (256)
.github/workflows/pr.ymlconfig/scripts/pr-code-change-scope.mjssrc/main/agent-hooks/first-work-branch-rename.test.tssrc/main/agent-hooks/first-work-branch-rename.tssrc/main/agent-hooks/first-work-rename-runtime.tssrc/main/agent-hooks/launch-file-prompt-by-pane.tssrc/main/agent-hooks/server-launch-file-prompt.test.tssrc/main/agent-hooks/server/server-status-update.tssrc/main/agent-launch/__fixtures__/host-agent-startup-call-sites.txtsrc/main/agent-launch/agent-launch-executor.test.tssrc/main/agent-launch/agent-launch-executor.tssrc/main/agent-launch/agent-launch-prompt-delivery.tssrc/main/agent-launch/agent-launch-surface-factories.tssrc/main/agent-launch/host-agent-startup-attribution.test.tssrc/main/ai-vault/session-title-launch-prompt.tssrc/main/daemon/daemon-create-or-attach-request.tssrc/main/daemon/daemon-foreground-confirmation-protocol.test.tssrc/main/daemon/daemon-launch-file-protocol.test.tssrc/main/daemon/daemon-protocol-version.test.tssrc/main/daemon/daemon-protocol-version.tssrc/main/daemon/daemon-pty-session-spawn.tssrc/main/daemon/daemon-pty-spawn-request.tssrc/main/daemon/daemon-pty-spawn-result.tssrc/main/daemon/daemon-pty-startup-delivery.test.tssrc/main/daemon/daemon-spawn-result-identity.tssrc/main/daemon/daemon-terminal-admission.tssrc/main/daemon/daemon-unstageable-line.test.tssrc/main/daemon/session.tssrc/main/daemon/terminal-host-create-contract.tssrc/main/daemon/terminal-host-session-create.tssrc/main/daemon/terminal-host-startup-staging.test.tssrc/main/daemon/terminal-host-wsl-launch-directory.test.tssrc/main/daemon/terminal-host.test.tssrc/main/daemon/types.tssrc/main/daemon/unstaged-agent-line-refusal.tssrc/main/ipc/ai-vault-session-title-routing.test.tssrc/main/ipc/ai-vault-session-title-routing.tssrc/main/ipc/pty/ipc/launched-agent-foreground.tssrc/main/ipc/pty/ipc/spawn-launch-refusal.test.tssrc/main/ipc/pty/ipc/spawn-options-launch-file.test.tssrc/main/ipc/pty/ipc/spawn-options.tssrc/main/ipc/pty/ipc/spawn-types.tssrc/main/ipc/pty/ipc/spawn.tssrc/main/ipc/pty/register-handlers.tssrc/main/ipc/pty/runtime/spawn-options.tssrc/main/ipc/pty/runtime/spawn-state.tssrc/main/ipc/qoder-independent-renderer-spawn-review.test.tssrc/main/ipc/worktree-remote.tssrc/main/opencode/opencode-model-startup-plan.test.tssrc/main/opencode/opencode-model-startup-plan.tssrc/main/providers/local-launch-artifact-directory.test.tssrc/main/providers/local-launch-artifact-directory.tssrc/main/providers/local-pty-launch-plan.tssrc/main/providers/local-pty-provider-shell-readiness.test.tssrc/main/providers/local-pty-session-activation.tssrc/main/providers/local-pty-spawn-wsl-shutdown-order.test.tssrc/main/providers/local-pty-spawn.tssrc/main/providers/pty-provider-contract.tssrc/main/providers/ssh-plain-shell-pty-provider.test.tssrc/main/providers/ssh-plain-shell-pty-provider.tssrc/main/providers/ssh-pty-spawn-request.tssrc/main/providers/startup-line-typed-length.live-shell.test.tssrc/main/providers/wsl-launch-directory-resolution.test.tssrc/main/providers/wsl-launch-directory-resolution.tssrc/main/pwsh.tssrc/main/runtime/agent-launch-typed-line-shell.test.tssrc/main/runtime/agent-launch-typed-line-shell.tssrc/main/runtime/agent-session-launch-startup.tssrc/main/runtime/agent-terminal-startup-prompt.test.tssrc/main/runtime/claude-agent-teams-shim-env.test.tssrc/main/runtime/claude-agent-teams-shim-env.tssrc/main/runtime/launched-agent-foreground.test.tssrc/main/runtime/launched-agent-foreground.tssrc/main/runtime/orca-runtime-activate-managed-worktree.tssrc/main/runtime/orca-runtime-agent-session-operation.test.tssrc/main/runtime/orca-runtime-agent-teams-launch-plan.tssrc/main/runtime/orca-runtime-create-agent-session.tssrc/main/runtime/orca-runtime-create-managed-worktree.tssrc/main/runtime/orca-runtime-create-runtime-owned-mobile-session-terminal.tssrc/main/runtime/orca-runtime-create-terminal-desktop.test.tssrc/main/runtime/orca-runtime-create-terminal-desktop.tssrc/main/runtime/orca-runtime-create-terminal.tssrc/main/runtime/orca-runtime-resolve-mobile-session-terminal-command.tssrc/main/runtime/orca-runtime-restore-live-paired-renderer-session-owned-mobile-terminals.tssrc/main/runtime/orca-runtime-run-create-mobile-session-terminal.tssrc/main/runtime/orca-runtime-terminal-create-deduplication.tssrc/main/runtime/orca-runtime-tests/mobile-session-tabs-part-12.spec.tssrc/main/runtime/qoder-terminal-command-resolution.test.tssrc/main/runtime/rpc/methods/agent-launch-caller-selection.test.tssrc/main/runtime/rpc/methods/agent-launch-failure-code.test.tssrc/main/runtime/rpc/methods/agent-launch-failure-code.tssrc/main/runtime/rpc/methods/agent-launch-prompt-carry.test.tssrc/main/runtime/rpc/methods/agent-launch-surfaces.tssrc/main/runtime/rpc/methods/agent-launch-terminal-prompt.tssrc/main/runtime/rpc/methods/agent-launch-worktree-creation.tssrc/main/runtime/rpc/methods/agent-launch.test-fixture.tssrc/main/runtime/runtime-agent-launch-resolution.tssrc/main/runtime/runtime-agent-terminal-startup.tssrc/main/runtime/runtime-ai-vault-commands.test.tssrc/main/runtime/runtime-ai-vault-commands.tssrc/main/runtime/runtime-folder-worktree-create.tssrc/main/runtime/runtime-local-worktree-terminal-startup.tssrc/main/runtime/runtime-managed-worktree-create-types.tssrc/main/runtime/runtime-pty-controller-contract.tssrc/main/runtime/runtime-remote-managed-worktree-create.tssrc/main/runtime/runtime-store-contract.tssrc/main/runtime/runtime-terminal-contracts.tssrc/main/runtime/runtime-worktree-agent-startup.test.tssrc/main/runtime/runtime-worktree-agent-startup.tssrc/main/runtime/terminal-foreground-group.test.tssrc/main/runtime/terminal-foreground-group.tssrc/main/runtime/terminal-launch-refusals.test.tssrc/main/runtime/terminal-launch-refusals.tssrc/main/runtime/this-orca-launch-host.test.tssrc/main/runtime/this-orca-launch-host.tssrc/main/runtime/wsl-broken-launch-folder.test.tssrc/main/runtime/wsl-pane-launch-carry.test.tssrc/preload/api/pty-api.tssrc/preload/api/pty-bridge-session-control.tssrc/preload/api/pty-bridge-stream-and-serialization.tssrc/relay/pty-handler-startup-command-staging.test.tssrc/relay/pty-handler.tssrc/renderer/src/components/right-sidebar/buildSourceControlAgentDeliveryPlan.tssrc/renderer/src/components/right-sidebar/useSourceControlAgentActionStart.tssrc/renderer/src/components/sidebar/folder-workspace-agent-startup.tssrc/renderer/src/components/sidebar/folder-workspace-composer-submit.test.tssrc/renderer/src/components/sidebar/folder-workspace-composer-submit.tssrc/renderer/src/components/terminal-pane/ipc-pty-connect.tssrc/renderer/src/components/terminal-pane/ipc-pty-spawn-request.tssrc/renderer/src/components/terminal-pane/pty-connection-types.tssrc/renderer/src/components/terminal-pane/pty-connection/pane-startup-command-options.tssrc/renderer/src/components/terminal-pane/pty-connection/pty-input-recovery.tssrc/renderer/src/components/terminal-pane/pty-transport-launch-refusal.test.tssrc/renderer/src/components/terminal-pane/pty-transport-types.tssrc/renderer/src/components/terminal-pane/refused-agent-launch-tab.tssrc/renderer/src/hooks/composer-state/composer-submit-orchestration.tssrc/renderer/src/hooks/composer-state/full-creation-startup.test.tssrc/renderer/src/hooks/composer-state/full-creation-startup.tssrc/renderer/src/hooks/composer-state/full-submit-preparation.tssrc/renderer/src/hooks/composer-state/quick-creation-execution.tssrc/renderer/src/hooks/composer-state/quick-startup-plan.tssrc/renderer/src/hooks/ipc-events/terminal-request-ipc-bridge.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/fr.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/renderer/src/lib/agent-background-draft-delivery.tssrc/renderer/src/lib/agent-draft-readiness.test.tssrc/renderer/src/lib/agent-draft-readiness.tssrc/renderer/src/lib/agent-launch-caller-permissions.test.tssrc/renderer/src/lib/agent-launch-caller-placement.test.tssrc/renderer/src/lib/agent-launch-caller-prompt-transport.test.tssrc/renderer/src/lib/agent-launch-platform.test.tssrc/renderer/src/lib/agent-launch-platform.tssrc/renderer/src/lib/agent-launch-prompt-delivery.tssrc/renderer/src/lib/agent-launch-prompt-not-delivered-notice.test.tssrc/renderer/src/lib/agent-launch-prompt-not-delivered-notice.tssrc/renderer/src/lib/agent-launch-prompt-receipt.test.tssrc/renderer/src/lib/agent-launch-prompt-receipt.tssrc/renderer/src/lib/agent-paste-draft.tssrc/renderer/src/lib/agent-startup-delayed-delivery.tssrc/renderer/src/lib/ai-vault-tab-title-requests.tssrc/renderer/src/lib/ai-vault-tab-title-sync.test.tssrc/renderer/src/lib/ai-vault-tab-title-sync.tssrc/renderer/src/lib/command-code-prompt-status-seed.tssrc/renderer/src/lib/composer-agent-startup-plan.tssrc/renderer/src/lib/launch-agent-background-session-remote.test.tssrc/renderer/src/lib/launch-agent-background-session.tssrc/renderer/src/lib/launch-agent-in-new-tab-windows-quoting.test.tssrc/renderer/src/lib/launch-agent-in-new-tab.test.tssrc/renderer/src/lib/launch-agent-in-new-tab.tssrc/renderer/src/lib/launch-agent-new-tab-prompt-delivery.test.tssrc/renderer/src/lib/launch-agent-new-tab-prompt-delivery.tssrc/renderer/src/lib/launch-agent-paste-timeout-notice.tssrc/renderer/src/lib/launch-agent-startup-prompt-plan.tssrc/renderer/src/lib/launch-file-host.test.tssrc/renderer/src/lib/launch-file-host.tssrc/renderer/src/lib/launch-work-item-direct-agent-routing.tssrc/renderer/src/lib/launch-work-item-direct-agent.test.tssrc/renderer/src/lib/launch-work-item-direct-agent.tssrc/renderer/src/lib/launch-work-item-direct-route-preparation.tssrc/renderer/src/lib/launch-work-item-direct.test.tssrc/renderer/src/lib/launch-work-item-direct.tssrc/renderer/src/lib/local-pwsh-availability.tssrc/renderer/src/lib/new-workspace.test.tssrc/renderer/src/lib/new-workspace.tssrc/renderer/src/lib/pending-worktree-creation.tssrc/renderer/src/lib/runtime-agent-background-create.tssrc/renderer/src/lib/source-control-agent-action-plan.test.tssrc/renderer/src/lib/source-control-agent-action-plan.tssrc/renderer/src/lib/ssh-background-startup-delivery.test.tssrc/renderer/src/lib/ssh-background-startup-delivery.tssrc/renderer/src/lib/tui-agent-startup.test.tssrc/renderer/src/lib/tui-agent-startup.tssrc/renderer/src/lib/worktree-creation-background-mount.test.tssrc/renderer/src/lib/worktree-creation-flow-ready-toast.test.tssrc/renderer/src/lib/worktree-creation-flow-startup.tssrc/renderer/src/lib/worktree-creation-flow-stranded-surface.test.tssrc/renderer/src/lib/worktree-creation-flow.test.tssrc/renderer/src/lib/worktree-startup-payload.tssrc/renderer/src/store/terminals/terminal-actions.tssrc/renderer/src/store/terminals/terminal-state.tssrc/renderer/src/web/preload-api/web-terminal-api.tssrc/shared/agent-launch-runtime-capability.tssrc/shared/ai-vault-session-title.tssrc/shared/claude-agent-teams-tmux-compat.tssrc/shared/cmd-startup-arg-quoting.win32.test.tssrc/shared/dsh-startup.test.tssrc/shared/flag-prompt-startup.tssrc/shared/freebuff-launch.test.tssrc/shared/freebuff-startup.test.tssrc/shared/launch-file-directory-grant.test.tssrc/shared/launch-file-writing.test.tssrc/shared/launch-file-writing.tssrc/shared/launch-host.test.tssrc/shared/launch-host.tssrc/shared/launch-prompt-carrier.test.tssrc/shared/launch-prompt-carry.test.tssrc/shared/launch-prompt-carry.tssrc/shared/launch-prompt-file.tssrc/shared/launch-prompt-plan.test-fixture.tssrc/shared/local-build-compatibility-contract.jsonsrc/shared/local-build-compatibility-contract.tssrc/shared/omp-fresh-launch-shell.test.tssrc/shared/omp-fresh-launch.test.tssrc/shared/opencode-startup-prompt.test.tssrc/shared/qoder-agent.test.tssrc/shared/qoder-cn-launch-recipe.test.tssrc/shared/rpc-contract/ai-vault-params.tssrc/shared/runtime-terminal-contracts.tssrc/shared/runtime-terminal-create-request.tssrc/shared/runtime-types.tssrc/shared/startup-command-staging.node-pty.test.tssrc/shared/startup-command-staging.test.tssrc/shared/startup-command-staging.tssrc/shared/startup-line-prompt-carry.test.tssrc/shared/startup-line-prompt-carry.tssrc/shared/tui-agent-config-types.tssrc/shared/tui-agent-config.tssrc/shared/tui-agent-prompt-transport.test.tssrc/shared/tui-agent-resume-startup.tssrc/shared/tui-agent-startup-hermes.test.tssrc/shared/tui-agent-startup-shell.tssrc/shared/tui-agent-startup.test.tssrc/shared/tui-agent-startup.tssrc/shared/typed-startup-line.tssrc/shared/windows-launch-line.test.tssrc/shared/windows-launch-line.tssrc/shared/windows-shell-prompt-damage.test.tssrc/shared/worktree/launch-types.tssrc/shared/wsl-launch-directory.test.tssrc/shared/wsl-launch-directory.tssrc/shared/wsl-login-shell-command.tssrc/shared/zcode-orchestration-contract.test.ts
💤 Files with no reviewable changes (15)
- src/renderer/src/lib/worktree-creation-flow-ready-toast.test.ts
- src/shared/tui-agent-resume-startup.ts
- src/renderer/src/lib/worktree-creation-background-mount.test.ts
- src/main/runtime/agent-launch-typed-line-shell.test.ts
- src/main/providers/startup-line-typed-length.live-shell.test.ts
- src/renderer/src/lib/ssh-background-startup-delivery.test.ts
- src/renderer/src/lib/worktree-creation-flow.test.ts
- src/renderer/src/components/sidebar/folder-workspace-agent-startup.ts
- src/shared/flag-prompt-startup.ts
- src/renderer/src/lib/command-code-prompt-status-seed.ts
- src/main/runtime/runtime-store-contract.ts
- src/shared/startup-line-prompt-carry.test.ts
- src/renderer/src/lib/ssh-background-startup-delivery.ts
- src/main/runtime/agent-launch-typed-line-shell.ts
- src/shared/startup-line-prompt-carry.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.
| const launchFile = writeRelayLaunchFile(params.launchFile, command, spawnEnv, shell) | ||
| if (launchFile) { | ||
| command = launchFile.command | ||
| Object.assign(spawnEnv, launchFile.env) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
rg -nP -C4 'export (async )?function (applyRelayAgentWorkspaceTrust|getRelayShellLaunchConfig)' src/relayRepository: stablyai/orca
Length of output: 1531
🏁 Script executed:
printf '%s\n' '--- pty-handler launch/spawn flow ---'
sed -n '2070,2290p' src/relay/pty-handler.ts
printf '%s\n' '--- workspace trust implementation ---'
sed -n '1,240p' src/relay/agent-workspace-trust-spawn.ts
printf '%s\n' '--- shell launch implementation ---'
sed -n '1,220p' src/relay/pty-shell-launch.ts
printf '%s\n' '--- launch-file writer/remover definitions and uses ---'
rg -n -C3 'writeRelayLaunchFile|removeLaunchFile|LaunchFile' src/relay/pty-handler.ts src/relayRepository: stablyai/orca
Length of output: 29579
🏁 Script executed:
printf '%s\n' '--- workspace-trust operation ---'
rg -n 'applyWorkspaceTrustOnThisHost|function applyWorkspaceTrust' src/main/execution-host-workspace-trust.ts
nl -ba src/main/execution-host-workspace-trust.ts | sed -n '1,300p'
printf '%s\n' '--- launch-file helper ---'
nl -ba src/shared/launch-file-writing.ts | sed -n '1,220p'
printf '%s\n' '--- handler cleanup and ownership transfer ---'
nl -ba src/relay/pty-handler.ts | sed -n '2110,2325p'
printf '%s\n' '--- relevant base-to-head changes ---'
git diff --unified=8 761d63a4e52ff7915d4432c004df13182db63010 f5ed44e2220c4d8f27b236aac72e21ed6b9a7ca1 -- src/relay/pty-handler.ts | rg -n -C12 'writeRelayLaunchFile|applyRelayAgentWorkspaceTrust|removeLaunchFile|launchFile' || test "$?" -eq 1Repository: stablyai/orca
Length of output: 36255
🏁 Script executed:
printf '%s\n' '--- history-injection bindings ---'
rg -n -C3 'injectRelayFishHistoryEnv|injectRelayHistoryEnv' src/relay
printf '%s\n' '--- relevant history helper declarations and bodies ---'
rg -l 'function injectRelay(Fish)?HistoryEnv|const injectRelay(Fish)?HistoryEnv' src/relayRepository: stablyai/orca
Length of output: 13694
🏁 Script executed:
printf '%s\n' '--- history-injection bindings ---'
rg -n -C3 'injectRelayFishHistoryEnv|injectRelayHistoryEnv' src/relay
printf '%s\n' '--- history helper source files ---'
rg -l 'function injectRelay(Fish)?HistoryEnv|const injectRelay(Fish)?HistoryEnv' src/relayRepository: stablyai/orca
Length of output: 13674
🏁 Script executed:
nl -ba src/relay/terminal-history.ts | sed -n '1,175p'Repository: stablyai/orca
Length of output: 6586
🏁 Script executed:
nl -ba src/relay/terminal-history.ts | sed -n '1,175p']]Repository: stablyai/orca
Length of output: 213
🏁 Script executed:
printf '%s\n' '--- trust-request parser ---'
rg -n -C35 'parseAgentWorkspaceTrustSpawnRequest' src/shared/agent-workspace-trust-spawn-request.ts
printf '%s\n' '--- shell startup feature implementation ---'
rg -n -C45 'export function (selectShellStartupFeatures|encodeShellStartupFeatures)' src/main/shell-startup-features.ts
printf '%s\n' '--- inherited ZDOTDIR helpers ---'
rg -n -C35 'export function (inheritedZdotdirEnv|resolveInheritedZdotdir)' src/main/zsh-wrapper-dir-ownership.tsRepository: stablyai/orca
Length of output: 5959
🏁 Script executed:
printf '%s\n' '--- startup staging binding ---'
rg -n -C35 'function stageStartupCommand|export function stageStartupCommand' src/shared src/relay
printf '%s\n' '--- wireAndStore binding ---'
rg -n -C45 'wireAndStore\(' src/relay/pty-handler.tsRepository: stablyai/orca
Length of output: 18723
🏁 Script executed:
nl -ba src/shared/startup-command-staging.ts | sed -n '1,185p']Repository: stablyai/orca
Length of output: 213
🏁 Script executed:
nl -ba src/shared/startup-command-staging.ts | sed -n '1,185p'Repository: stablyai/orca
Length of output: 9116
Clean up the launch file if startup staging throws.
After pty.spawn, provider-delivered commands may reach stageStartupCommand before wireAndStore takes ownership of the launch file. stageStartupCommand calls randomBytes(8) before its write-error try; if that synchronous call fails, the error escapes without removing the launch file.
🐛 Suggested fix
if (managed.startupCommand?.providerDelivery && managed.startupCommand.command) {
- managed.stagedStartupCommand = stageStartupCommand({
- command: managed.startupCommand.command,
- shellPath: shell,
- orcaBuiltLine: launchAgent !== undefined
- })
+ try {
+ managed.stagedStartupCommand = stageStartupCommand({
+ command: managed.startupCommand.command,
+ shellPath: shell,
+ orcaBuiltLine: launchAgent !== undefined
+ })
+ } catch (error) {
+ removeLaunchFile(launchFile)
+ throw error
+ }Source: Learnings
…ch-prompt-argv-carry Conflicts resolved against #23962's head this branch had merged (83de487), so each keeps main's squashed #23962 plus this branch's change: - daemon protocol v42 (launch files), v41 (#23962's staged startup lines) now in PREVIOUS; build compatibility contract and pinned version tests follow. - agent-draft-readiness keeps main's OpenCode agent-row grace (#25101) and this branch's bracketed-paste-off revocation of the quiet window. - startup-line-prompt-carry.test.ts stays deleted; its rule lives in launch-prompt-carry. Semantic follow-ups from #23672 (CR submission, one-line PowerShell quoting): - windowsLaunchLineVerdict reads line breaks from the prompt too: PowerShell now quotes a multi-line prompt onto one physical line, which hid the measured multi-line rows (a 9 KB multi-line prompt read as exact) and the shim's line-break cut. - Typed launch lines end in CR; raw multi-line lines submit each break with CR; the PowerShell multi-line expectations use the one-line backtick form. - A duplicated pair of #23962 staging tests left by the auto-merge is removed. - agent-paste-draft: the two identical launch-paste tails share sendLaunchPaste; tui-agent-config writes devin on one line like jcode (main sat at the line cap).
- Takes main's deletion (#25791) of the host startup call-site inventory, its attribution test and daemon-protocol-version.test.ts; v42 stays pinned by daemon-foreground-confirmation-protocol and the build-contract test, and launch files by daemon-launch-file-protocol. - #25720's custom-launch-command test plans its prompted launch with this branch's planner, which keeps the custom command on the line.
|
Parked as a draft: launch-point unification (all agent launches through agent.launch: #24934, #25430, #25624) is being rebuilt directly on main so it doesn't wait on delivery changes. This PR changes how the first prompt reaches the agent, and it comes back after unification lands, as its own PR on top of it. |
… written when the surface exists Step 1 of the agent-launch unification, on main. - The dispatcher stamps every request's caller from what its connection proved (runtime socket: the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it. - The launch record is written twice: once when the tab exists (what creation settled: on the launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is known. A restart in between finds the running agent instead of answering "unknown". - A replay re-derives its terminal handle from the pane key in the running host, and shows `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1. - The record store opens in its own slot, without building the chat host; the chat host is built on that same store. Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the `unconfirmed` row), and main's journal-database open in install(), which now goes through the record-store slot.
… written when the surface exists (#24934) Step 1 of the agent-launch unification, on main. - The dispatcher stamps every request's caller from what its connection proved (runtime socket: the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it. - The launch record is written twice: once when the tab exists (what creation settled: on the launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is known. A restart in between finds the running agent instead of answering "unknown". - A replay re-derives its terminal handle from the pane key in the running host, and shows `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1. - The record store opens in its own slot, without building the chat host; the chat host is built on that same store. Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the `unconfirmed` row), and main's journal-database open in install(), which now goes through the record-store slot.
…ed (#25430) * feat(agent-launch): host-assigned caller identity and a launch record written when the surface exists Step 1 of the agent-launch unification, on main. - The dispatcher stamps every request's caller from what its connection proved (runtime socket: the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it. - The launch record is written twice: once when the tab exists (what creation settled: on the launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is known. A restart in between finds the running agent instead of answering "unknown". - A replay re-derives its terminal handle from the pane key in the running host, and shows `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1. - The record store opens in its own slot, without building the chat host; the chat host is built on that same store. Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the `unconfirmed` row), and main's journal-database open in install(), which now goes through the record-store slot. * feat(agent-launch): show an agent's tab at once, where the caller asked for it An agent.launch now shows its terminal tab before admission and spawn, in the requested placement (group and/or anchor tab), and the pane attaches to the agent as soon as it runs. A pane whose agent can't start, or whose start can't be confirmed, says so instead of becoming a plain shell, and keeps saying so across restarts. A user's close of the tab or its pane during the launch stops it and answers agent_launch_tab_closed. Whose view moves is unchanged from main for every caller. Rebuilt on main (with #24934) from the previous branch head 8f62858. * style(runtime): one-line the tab-order map so the headless browser-tabs runtime stays under its line limit The merge of main (#25724's emitMobileSessionTabsSnapshot metadata) plus this branch's close mark put the file one line over max-lines. Formatting only.
…agent.launch (#25624) * feat(agent-launch): host-assigned caller identity and a launch record written when the surface exists Step 1 of the agent-launch unification, on main. - The dispatcher stamps every request's caller from what its connection proved (runtime socket: the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it. - The launch record is written twice: once when the tab exists (what creation settled: on the launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is known. A restart in between finds the running agent instead of answering "unknown". - A replay re-derives its terminal handle from the pane key in the running host, and shows `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1. - The record store opens in its own slot, without building the chat host; the chat host is built on that same store. Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the `unconfirmed` row), and main's journal-database open in install(), which now goes through the record-store slot. * feat(agent-launch): show an agent's tab at once, where the caller asked for it An agent.launch now shows its terminal tab before admission and spawn, in the requested placement (group and/or anchor tab), and the pane attaches to the agent as soon as it runs. A pane whose agent can't start, or whose start can't be confirmed, says so instead of becoming a plain shell, and keeps saying so across restarts. A user's close of the tab or its pane during the launch stops it and answers agent_launch_tab_closed. Whose view moves is unchanged from main for every caller. Rebuilt on main (with #24934) from the previous branch head 8f62858. * feat(agent-launch): the desktop AI buttons start their agent through agent.launch Part 3 of 3 of the agent-launch unification, on main. The AI buttons (Fix checks, the source-control actions, commit/push recovery, Explain commit, notes and annotation sends, session continuation) started their agent from the window. They now ask the host's agent.launch to start it, with no prompt, into the tab the window makes at the click, under one operation id per click; the window's pane waits for the host's agent instead of spawning a shell. The window then pastes the prompt with main's own paste, moved out verbatim and started only once the host's agent holds the tab, so delivery and follow-ups are exactly main's. A typed new-tab prompt and launches while chat is the default keep main's path. The source-control dialog drops its launch-command preview, which the host builds now. * style(runtime): one-line the tab-order map so the headless browser-tabs runtime stays under its line limit The merge of main (#25724's emitMobileSessionTabsSnapshot metadata) plus this branch's close mark put the file one line over max-lines. Formatting only. * fix(agent-launch): watch an AI button's agent for readiness from its first output The window started watching for the agent's ready signal only after the host answered agent.launch, so an agent that had already turned on bracketed paste by then was never seen ready and its prompt waited out the readiness budget. Readiness is now watched from the moment the tab's terminal exists, as main watches it; the paste is written, and the chat copy seeded, only once the host says its agent started in that tab.
|
Closing this large older launch stack and rebuilding exact first-prompt delivery at the current way agents are started. Create an issue for long prompts and Windows quoting before closing. The remaining problem is tracked in #27676. |
… message carries (#24488) * fix(quick-commands): save agent prompts whole, bounded by what a chat message carries Agent quick-command prompts were silently cut to 6,000 characters on save: the editor accepted a long paste, settings normalization trimmed it, and reopening showed the shortened copy. The 6,000 was a Windows launch speed threshold, not a limit on prompts. - Storage no longer trims. A save is refused, never trimmed, when a prompt's chat text block is over 256 KiB of JSON (measured on its real escaped size, the measure the chat send schema applies) or the list is too large for one paired reply. Host and desktop store both check before writing. - The editor shows the size against the limit and blocks Save past it, and stays open with the text when the host refuses the save. - terminal.quick-commands.long-prompts.v1: hosts withhold prompts over 6,000 characters from clients that do not advertise it (older clients reject a whole list holding one); editors keep an older host's 6,000 cap. - The phone editor no longer drops pasted text past 6,000 via maxLength. Launch delivery of long prompts is left to the launch-prompt series (#24257, #23962, #24080); this branch's earlier paste-after-ready delivery duplicated it and is removed. * fix(quick-commands): drop a stale duplicate doc comment * fix(quick-commands): advertise long prompts to this build's own host too * fix(quick-commands): keep submitted editor immutable until save settles * test(quick-commands): keep storage and save lifecycle coverage focused * fix(quick-commands): keep portalled choices within pending form ownership * Preserve retained credentials when settings validation refuses a save --------- Co-authored-by: OrcaWin <alpha-eng@stably.ai>
The problem
When Orca starts a terminal agent with a first prompt, it often starts the agent empty and pastes the prompt into its screen once the agent looks ready. That paste can lose part of the text, can land its Enter on a startup dialog, and makes you wait for the agent to look ready before your prompt shows up.
Today that happens in two places. The desktop's prompt launches that wait for readiness (Fix checks, commit/push-failure fix, Explain changes, Resolve conflicts, notes and annotation sends, session continuation, work items) always start the agent empty and paste. The host's
agent.launch(used by the phone, the CLI and orchestration) pastes any prompt whose typed launch line is over 512 bytes or contains a line break, except a short-lined prompt in zsh up to 8 KB, because until the previous step nothing could deliver a long typed line intact.On Windows, a prompt typed on the launch line has its own damage: cmd adds stray
^and a line break submits its line early, and Windows PowerShell 5.1 drops every"from a prompt it hands to the agent's program, so on main today the agent gets your text with its quotes missing. (Main now writes a multi-line PowerShell prompt as one line with`nescapes, #23672, so PowerShell no longer submits it early.)What changes for you
",%NAME%and a trailing\arrive."or trailing\rides the line on PowerShell 7, which carried it exactly, but not on 5.1, which drops every"on main today: there it gets a launch file, so the agent reads your quotes exactly. A multi-line prompt is written as one PowerShell line (fix(terminal): submit startup commands with Enter on every platform #23672); its limits are still the measured ones, counted from the prompt's line breaks. Orca knows which one the pane is, because it picks the executable itself: the requested shell, then the PowerShell setting, then whether pwsh.exe is installed.agent.launchand workspace creation with an agent (phone, CLI, orchestration),agentSession.create, phone quick commands, the new-workspace composer, background drafts, and a prompt typed into a new agent tab. There it takes a prompt over 100,000 bytes on macOS, Linux, WSL and SSH, and on Windows only a prompt that shell was measured to damage or lose: cmd over 8,191 characters or with a line break; PowerShell over about 32,000 characters or a multi-line prompt of 9 KB or more, and on 5.1 a"or trailing\; Git Bash at 20 KB or more. A pointer is better there than a damaged prompt."or trailing\when the PowerShell is unknown: on an SSH Windows host (its relay picks the shell) or a paired Orca, and on this machine with the "auto" PowerShell setting before the pwsh.exe probe has answered..cmdor.batshim in its command setting goes through cmd.exe, so cmd's%NAME%expansion and PowerShell's legacy handling of"and a trailing\also count as damage there. A bareclaudeorcodexmay be either a shim or a native executable, so it is judged as typed.--add-dir) so it does not stop to ask. Elsewhere, any other agent keeps the paste after ready for a prompt that would need the file; a caller that has no paste after ready (agentSession.create, phone quick commands) keeps it on the line, as main did.~/.cache/orca, which Orca makes 0700 when it first finds it; if that folder is deleted later, Orca recreates it over the share with the distro's default mode until Orca restarts. It stays as long as its terminal does and is removed when the terminal closes. The line carries one sentence pointing at it:The full task is in the file <path>. Read it and complete the task it describes.~/.cache/orca. Orca checks that folder before planning, including before the first launch into a WSL distro; a probe failure, or a refused write, counts for 30 seconds. Where main typed the line (a Windows host cannot prove the agent is in front, so this includesagent.launchand worker start into WSL), the host types it as is, as main does: a 5-line prompt arrives exactly, and a 25 KB one leaves bash waiting for more input, as on main.Scope
Affected: every way Orca starts a terminal agent with a first prompt: the new agent tab and the desktop AI buttons that use it, quick launch, quick commands, session continuation, work items, new workspace with an agent, background drafts,
agentSession.create, andagent.launchfrom desktop, phone and CLI. Hosts: macOS, Linux, Windows, WSL and SSH.Not affected: agents that only take text after they start (still pasted once ready), follow-up messages, reused terminals, structured chat sessions, and orchestration worker start (step 4).
A desktop driving a paired remote Orca keeps the line to the 512-byte typed budget and pastes anything longer, because that host may be an older build that neither stages a line nor writes a file. A paired host's prompt that a desktop action waits on is always pasted, because this client cannot read a paired host's terminal to prove the agent received a carried prompt.
The plan, and where this PR sits
This is step 3 of 7 of the agent launch plan. Steps 1 and 2 are on main, so this PR is now based on main.
agent.launch.How it works
carryLaunchPrompt(src/shared/launch-prompt-carry.ts), answers where a prompt rides:none,on-line,launch-fileorpaste-after-ready.planLaunchPrompt, and only the carried outcomes hold the plan to launch, so a caller cannot launch without switching on the outcome.buildAgentStartupPlannow builds only a prompt-less launch, and the repo's exhaustive-switch lint fails a caller that skips an outcome.describeLaunchHostinsrc/shared/launch-host.ts): whether it is a paired Orca; whether it can prove the agent is in front (any host but Windows; an SSH or paired host is judged by its own platform); which PowerShell a local Windows pane is spawned as (spawnedWindowsPowerShell: the same requested shell, PowerShell setting and pwsh.exe probe the spawn uses; unknown for an SSH or paired host); and whether it takes a launch file (not a paired Orca, which may be older, and not an SSH Windows host, whose relay may run the pane in WSL, set by the host's OpenSSH default shell, which this client cannot see). The desktop and the host each call it with where the launch runs; no caller passes these as flags.paste):never;when-host-proves-agent, feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257's guarded paste; oronce-agent-runs, the desktop paste main uses for submit-after-ready prompts (AI buttons, notes sends), written once the agent's process owns the terminal. Each caller passes the kind main used on its path.windowsLaunchLineVerdictinsrc/shared/windows-launch-line.ts: exact, damaged, or not measured); on a paired Orca, feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257's 512-byte typed budget.once-agent-runspaste gets that paste; any other caller's prompt rides a launch file.waitForLaunchPromptReceipt, with one read: feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257's crash-guard predicate (judgeTerminalForeground), which now tellslaunched-agent(a process in the terminal's foreground group named as the launched agent on its command line) fromother(any other process that is not a shell, such as an agent behind a wrapper or a command override, or a startup file's own command). feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257's paste guard counts both as the agent, exactly as before.ESC[?2004l) revokes it.psof the pane's terminal), and runs once more the moment the agent looks ready. A shell in front is not an exit on its own: a slow shell startup draws its prompt, and looks ready, before it runs the launch line (stack QA saw a false "exited" notice 3 of 3 times that way). Agent exited needs proof the agent ran:launched-agentread in front, then the shell back or the terminal gone.agent.launch, waiting on the tab's handle, fails at once with the codelaunch_file_unavailable, recorded as a launch that failed without effects.unstageableLine(type, the default, orrefuse), from the desktop through main's pty spawn to the terminal daemon, which reads it in one place (unstagedAgentLineRefusal). It is optional: a daemon that predates it types the line, as before, so there is no protocol bump. The local terminal host used when the daemon is unavailable, and the SSH relay, do not read it.~/.cache/orca0700) only when a launch needs it: before planning a prompt too long to type (probedThisOrcaLaunchHost, which the spawn would await anyway), and at the spawn for a launch file or an Orca-built line long enough to stage. A failed probe is remembered for 30 seconds. With no folder, the host refuses a launch file (its path would mean nothing in the distro) and types a line as is. The terminal daemon refuses a line whose staging write failed only when the folder looked usable; with this machine's temp folder unwritable it types the line, which is what the plan chose. One predicate (startupLineNeedsStaging) decides both "stage this line" and "this spawn needs the distro".launchPromptShownForPane, which maps a launch file's pointer back to the prompt it carried. The branch rename uses the same function, and so does the session title the tab label and sidebar agent row show.Why this approach
Differences from the common pattern:
~/.cache/orcabroken), a line too long or multi-line to type:unstageableLine: 'refuse', which the terminal daemon alone reads. Main pasted it, so this is still worse than main: the user pastes it themselves.agent.launch, where the host's check applies and main's paste is planned up front, and remove this.node.exeor its native executable so no cmd hop re-parses the line. Owner: this stack, after step 7, together with the next entry; no issue filed yet..cmdshim. Through a shim, PowerShell", a trailing\and%NAME%can be damaged on the typed paths, as on main. Follow-up: the execution host reports where each agent's command resolves. Owner: this stack, after step 7; no issue filed yet.chcp 65001as main's do, but no QA row carried such a prompt into cmd.What the agent itself still shows
The agent receives the pointer sentence as its first message. So its own screen, its session history and resume list (Claude's
/resume, Codex's history), and any title it writes from that message all show "The full task is in the file …".Orca's views of that same history show it too, because they read the agent's transcript after the file is gone: the chat view of a terminal tab, and AI Vault's session list. The tab label and the sidebar agent row do not: they title the session with the prompt the launch file carried.
That happens for Claude and Codex only:
How this differs from the previous version of this PR
.cmdshim's cut at a line break went unseen. The verdict now counts line breaks in the prompt too, so the measured rows apply as before (re-breaking it fails 4 tests).launched-agentandother, instead of a second read by process name; the read backs off from 250 ms to 2 seconds (P2-1).unstageableLinecomes from the rule's outcome for every caller whose paste main used, so a work item now sends it too (P2-2).cmd-startup-arg-quoting.win32.test.tshad failed on every run since d8cfcf3. Two expected one argument, but Claude's launch-file line has two (the folder grant, then the pointer, whole). One piped a non-ASCII prompt into cmd, which decodes piped input in its startup code page; the rule now leaves non-ASCII on cmd to main's delivery, and the harness no longer claims to measure it.agent.launchand worker start into a WSL workspace with a broken~/.cache/orca, before or after its first use, were all refused, a 5-line prompt worse than main). The WSL folder is probed before planning, so the first launch plans like later ones. Only a write that fails in a folder that looked usable is refused.sensitiveflag is removed. Its only producer was the orchestration worker brief, which Start orchestration workers with their brief on the launch command line (step 4 of 7) #24081 now sends through the same rule as ordinary text, so every file was minted with it off. It was never in a released build, so removing the wire field is safe."or trailing\gets a launch file (main broke it), and on 7 it rides the line (main carried it).%NAME%, and on PowerShell 7"and a trailing\) got a launch-file pointer.agentSession.createand phone quick commands refused a prompt they could not paste on a Windows host; they now keep it on the line, as main did.agent.launchaslaunch_file_unavailable, a launch that failed without effects; the desktop closes the tab it opened.agent.launch, and the source-control preview's copy of the paste branch.agent.launchand origin/main 66799f7) and ef62954 (feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257 now also proves the agent in front when it runs behind a wrapper such as/bin/sh; the receipt uses that same check), b865c7d (in tcsh and nu, a plain Orca agent line is typed as is instead of going through/bin/sh), and 22c4f3e (feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257 judges that wrapper proof by when its capture began).Size
240 files, +8,579/−1,787 against step 2: production 154 files +3,812/−935, tests 86 files +4,767/−852.
Testing
This merge (04559df):
tc:node,tc:webandtc:clipass through tc-gate; oxlint and the changed-code gate are clean; the reliability manifest, RPC catalog and localization catalogs pass; the daemon crossing check adopts release v1.4.221 (v40) under v42. The 155 test files this PR changes or that import its conflicted modules pass (2,049 tests), as do the protocol and build-contract tests. Cross-version wire: 20 of 21 files pass; the remaining one is test(cross-version): load releases that import a package main dropped #25731's new test expecting@streamparser/jsonto be uninstalled, which main's ownpackage.jsoninstalls again since fix(feedback): optimize oversized screenshots without losing detail #23664; Carry an agent's first prompt on its command line instead of pasting it (step 3 of 7) #24080 touches neither, so I expect the same red on main (not yet run there).Final-review commit (f29abc5):
tc:node,tc:webandtc:clipass through tc-gate; oxlint on its 43 changed files and the changed-code gate are clean. The 1,850 test files that import its changed modules up to three levels deep (899 renderer), run with a 120-second test timeout at load 83 to 153: 1,844 pass (20,037 tests), 3 are skipped by their own guards and 1 is a runtime spec that runs insideorca-runtime.test.ts; 2 failed. One was a load flake that passed on rerun. The other held two real failures: work-item tests that still expected the pre-review Windows behaviour. They now expect a submit-after-ready prompt pasted on a Windows host (as on the base) and a 3-line draft prefilled on PowerShell's line (measured exact); all 14 in that file pass.Re-broken and watched fail, one at a time: the rule keeping the line for an AI button on Windows (6 tests), an unconfirmed new-tab result raising the notice,
othercounted as the agent having run, no back-off (the 30-second wait's read count), no read at the ready signal, the judge not split, the rule never asking to refuse an unstageable line, the work item or the new-tab plan dropping it, Command Code's seeded row counted, the draft judged by nothing, and non-ASCII on cmd counted exact.Windows CI (
package (windows)) had failed three cases incmd-startup-arg-quoting.win32.test.tson every run since d8cfcf3. On 0a43c0a it passes, with the whole PR Checks run green. Two cases expected one argument where Claude's launch-file line has two (the folder grant, then the whole pointer). The other two failures were the harness: it pipes the line into cmd, which decodes it in its startup code page whateverchcpsays, so accents and CJK, in a prompt or in the home folder's path, arrived garbled while cmd split the line correctly. Those cases now keep to ASCII (the apostrophe, the space and%that cmd's quoting must survive). A non-ASCII prompt or path in a real cmd pane is not measured, and the rule leaves a non-ASCII prompt on cmd to main's delivery.tc:node,tc:webandtc:clipass.oxlint on the changed code files and
check:code-quality:changedagainst step 2 (212 files) are clean. This version adds no strings; the five localization checks passed on the previous version (the coverage audit crashed once when run straight after the others, and passed alone 4 of 4).Main run, 610 named test files: 599 pass (7,881 tests), 4 are skipped by their own platform guards, and 7 are runtime spec files that run inside
orca-runtime.test.ts.Second run, the 229 further test files that import this version's changed modules (launch host, staging, launch-file writing, daemon session create, local spawn, the four main planners, the pane connect) up to three levels deep: all pass (2,367 tests). The earlier versions' wider sweep (1,231 further files) passed on the previous head.
That first run caught an import cycle: the new tab-close step imported the store from the terminal transport, which the store's terminal slice imports, so 4 renderer test files failed to load. The close now loads the store lazily; all 371 renderer test files in both lists then passed, and
tc:webpassed again.The
unstageableLinefield (latest commit) has a test per hop: the new agent tab asks to refuse only for a submit-after-ready prompt; the pane's queued startup hands it to the transport; the transport sends it with the spawn; main's pty spawn keeps it only with the renderer's own command and a known value; and through a real in-process terminal daemon (client request, admission, session create) a refused line is refused with nothing typed, while the same spawn without the field creates the session to type it, as a daemon that predates the field does. A WSL case refuses a 25 KB line for a refusing caller and still types a short one.For that commit:
tc:node,tc:webandtc:clipass, oxlint and the changed-code gate are clean, and the 1,525 test files that import its runtime changes up to three levels deep (755 of them renderer) ran: 1,517 passed (16,506 tests), 3 are skipped by their own guards, 1 is a runtime spec that runs insideorca-runtime.test.ts, and 4 that failed under load (load 38 to 53: a 15-second timeout in the sidebar card tests, and a pane count in the paired-reconnect test, which was in the previous round's load reruns too) passed on rerun (the paired one 3 of 3). Type-only additions to the store and contract types were left to the typecheck.The receipt commit (latest) drives the real receipt through a slow shell startup (a startup
sleepin front, the shell's prompt looking ready, the shell in front for three reads, then the agent): delivered, never exited. Re-breaking it (a shell read after readiness counted as exited, any non-shell process counted as the agent, a terminal exit counted as exited without proof, no deadline) fails its tests.tc:web, oxlint and the gate pass; the 24 test files that import the receipt pass (9 of them first timed out under load 69 and passed on rerun).src/main/runtime/wsl-broken-launch-folder.test.tsdrives the real path for stack QA 2a:agent.launch's plan with the WSL probe, the spawn's probe, and the terminal daemon's session create, with the distro folder broken before first use (Claude 25 KB, Claude 5-line, Grok 25 KB, Claude 120 KB: each typed as is) and after (the launch whose write fails after the probe found the folder is refused; the next ones are typed as is).Each Windows row of the stack QA (cmd, PowerShell, Git Bash; Claude and Codex; built with the rig's own
node stub.jscommand) is a test case insrc/shared/windows-launch-line.test.ts: rows main delivered exactly ride the line or main's paste, and only rows main was measured to damage or lose get a launch file.Neither run had an unhandled error. The first main run had one failure: under load, the real-shell test's 100,000-byte case read a truncated argv in zsh, dash and fish. The harness read the stub agent's capture 100 ms after it appeared, before the write finished. The stub now renames the capture into place once written; the same 24-file batch then passed twice. That failure depends on load and did not reproduce on demand with the old harness, so the fix is by construction, not shown by a re-break.
Each fix in this version was checked by re-breaking it and watching its test fail: readiness counting on its own,
ESC[?2004lnot revoking it, the refusal code, the tab close, a live writer's folder swept, the in-flight wait, the old 512-byte Windows budget, a no-paste caller refusing, a paired host judged by the client's platform, a Windows AI-button prompt filed instead of pasted (rule and caller), main's desktop paste ignored on a WSL host, the old 16,384 ceiling, the paste-first step limited to Windows, the ceiling counted in characters, the rule ignoring whether a host takes a launch file, an SSH Windows host taking one, an older daemon refusing or being sent the command, the wire parser still requiring the removedsensitivefield, the Windows-wide rule put back (16 QA-row tests fail), PowerShell"counted as damaged, an AI button typing an unmeasured line, a Tab or carriage return typed, Git Bash held to cmd's limit, a spelled-out shim not recognised, the PowerShell ignored or pwsh counted as 5.1, a requested--shellignored, the renderer leaving the PowerShell unknown, "auto" ignoring the pwsh.exe probe, session titles left unmapped on this desktop or over RPC, only a whole pointer recognised, the renderer sending no pane, a shell read counted as "not delivered", an exited agent getting the "wasn't sent" notice, a WSL path planned by the Windows setting (main and desktop), a WSL pane judged by Git Bash's limits, a Tab or carriage return counted as damage on PowerShell, an unstaged agent line typed in full, a WSL folder kept after a refusal, the rule ignoring a host that cannot stage, the host fact ignoring the folder check, and a refused write not remembered (WSL and this machine), the write site refusing a WSL line with no folder (5 tests fail), no probe before planning (the 120 KB row fails), the daemon refusing a staging failure in an unusable folder, the notice shown per spawn instead of per tab, a tab holding a live terminal closed, and theunstageableLinefield dropped at each of its seven places (the new tab not asking, the pane startup, the transport request, main's pty spawn, the daemon client, the daemon's admission, and the daemon ignoring it): each makes its test fail.tests/e2e,mobile/and config reference none of the removed names.Not measured (live runs were out of scope for this round): click to "delivered" against main. Expected, not proven: with hooks on, "delivered" fires at the agent's first turn, which needs no quiet window, while main's paste could start only after the agent turned bracketed paste on and then printed nothing for 1.5 seconds. Without hooks, an agent already working on its prompt may not go quiet, so the receipt can wait up to the readiness timeout (8 seconds by default) before its read, later than main.
Not run: live QA of this head. The plan's acceptance rows for this step need the coordinator's QA lanes:
"and trailing\now a launch file), PowerShell 7 and Git Bash, rerun against this head, with the "auto" PowerShell setting both with and without pwsh.exe installed, plus the unmeasured bands (PowerShell multi-line 512 bytes to 9 KB, Git Bash 9 KB to 20 KB) and a real npm-installedclaude.cmd;Visual Proof
Pending the coordinator's live QA for this step.
Notes
Author X handle: @BrennanKB5