Repository navigation
Start worktree.create's agent through the launch executor - #26709
Conversation
The host's worktree.create handler now starts a requested agent (startupAgent, or a linked startupDraft) through executeAgentLaunch instead of calling the create directly. Nothing on the wire changes: the request, the reply and agentTerminalHandle are the same, the agent is still the workspace's startup terminal whatever the chat default says, and the create still delivers the text itself (folded into the command at any length, a follow-up for agents that read it after start, and the draft pasted unsent). Adds three host-internal executor inputs: terminalOnly, the legacy-host prompt policy, and a shared entry (createWorktreeWithStartupAgent) that host producers can call in place of createManagedWorktree.
Problem: after worktree.create moved onto the launch executor, a create from a linked issue (phone "create from issue", or a raw worktree.create with startupDraft) could differ from before in two narrow cases. When the draft's agent came from the settings default or from detection and its launch command could not be built (for example unbalanced quotes in the saved agent arguments), the workspace was recorded as created with that agent even though it never started, and a background create opened no terminal at all instead of its plain shell. When no agent was found, detection ran twice (an extra SSH round trip on remote repos). User-facing change: none intended; both cases now behave exactly as before the executor move. Mechanism: - The entry hands the draft's chosen agent to the create as a main-internal startupDraftAgent, which steers only the draft; createdWithAgent stays what the caller sent. A draft that resolves no agent is created without the draft, so the create does not detect again. - AgentLaunchExecution is a discriminated union: every normal launch must bring its surface factory, and a legacy-host launch must be terminal-only, bring a workspace factory, and cannot bring surfaces. The runtime guards the types replace are gone; the target kind is checked before any create. - Legacy-host receipts follow the delivery module's rule: a blank prompt is no prompt, a submit folded into the launch command is handed-to-terminal, a post-start send the create does not await is unconfirmed (host-internal), a draft is not-delivered. - The no-agent error lives with the legacy-host policy and is caught only by the shared entry. - Tests: a real-runtime parity test (stored agent, terminal spawns, detection count vs the create alone, including the unbuildable plan), exact-argument checks over every non-agent startup field, compile-time checks for the union, and direct tests of the draft agent resolver.
Two comments stated that headless automations and federation already go through createWorktreeWithStartupAgent; both still call createManagedWorktree directly. Reword them as the plan. Comment-only.
Review, QA and CI summary (head 9fcfc3a)The problem. Orca's host had two separate ways to start an agent. What changes for the user: nothing, by design. Same CLI output (including Fixed during review (no P0-P2 found):
Deferred (none user-visible today):
Verified.
Not verified.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (18)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review. 📝 WalkthroughWalkthroughThe changes add legacy-host launch handling and terminal-only mode to the launch executor. Worktree creation routes startup-agent requests through the executor. Draft startup agent selection now uses a shared resolver that checks requested and default agents before detecting available agents. New tests cover launch policy behavior, agent selection, worktree creation, and RPC requests. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to This change routes worktree.create startup agents through the launch executor while keeping the request and reply shape unchanged. Tests cover the main paths, including prompt delivery, draft handling and failure cases. No blocking issue was identified, though SSH hosts and non-macOS platforms were not exercised. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)✅ Passed checks (4 passed)
✨ 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 |
Main's worktree.create caller (#26709) builds a legacy-host launch factory with only createWorktree; type that policy's factory as worktree-only so it needn't implement this PR's createFolderWorkspace, which only agent.launch uses.
ELI5
Orca has one host operation that starts agents (the launch executor). Creating a workspace with an agent through
worktree.create(the CLI'sorca worktree create --agent … --prompt …, the phone's "create workspace from issue/PR", and every other caller of that request) still started its agent its own way, beside the executor. This PR sends those agent requests through the executor too. Nothing you see changes.What Changed
The problem. Agent launches had two separate starting paths on the host.
agent.launchran through the executor;worktree.createwithstartupAgentorstartupDraftcalled the workspace create directly and let it build the agent. Two paths meant every launch rule (which surface, how the prompt arrives, what is reported) had to be kept in step by hand, and the remaining host producers (headless automations, orchestration workers) had no shared entry to move onto.What is different for the user: nothing, by design. Same request, same reply (including
agentTerminalHandle), same agent, same tabs and focus:orca worktree create --agent claude --prompt "…"prints the same result, and the prompt still rides the agent's start command at any length.worktree.createstill starts a terminal agent: its reply promises a terminal handle.The mechanism. The
worktree.createhandler now callscreateWorktreeWithStartupAgentinstead ofcreateManagedWorktree; it takes the same arguments and returns the same result. When the request names an agent or a linked draft, it runs the executor with three host-internal inputs (none of them on the wire):terminalOnly): the launch settles as a terminal regardless of the chat default. It is the same branch of the mode decision that a "terminal" setting takes.promptPolicy: 'legacy-host'). The compiler enforces its shape: the executor's input is now one of two types, and a legacy-host launch must be terminal-only, must create a workspace, and cannot pass a surface factory, while every other launch must pass one: the create owns the text, exactly as before. It folds a prompt into the start command at any length, sends it after start to agents that read it then, and pastes a draft unsent. The executor delivers nothing itself. The create's startup terminal is the only surface: if it could not start the agent here (it hands the start to the window instead, or reports why inwarning), the executor does not build a second one.createWorktreeWithStartupAgent) with its own workspace factory. It keepsworktree.create's own create arguments (it does not await setup provisioning the wayagent.launch's factory does). Headless automations and remote workers can later call it in place ofcreateManagedWorktree.For a linked draft with no agent named, the host still picks the agent the same way (requested agent, then the default, then one detected on the host that runs it). That choice now happens just before the create, so the executor can name the agent. It is handed to the create through an internal field that only steers which agent the draft starts, so the workspace still records exactly what main records, including when that agent's start command cannot be built. Detection still runs once, also when no agent is found.
Why
This is the first step of moving every fresh agent start on the host onto one executor, without changing any client. Migrating the host side of
worktree.createcovers the CLI, the phone, older phones and every otherworktree.createcaller at once. Moving the CLI or phone ontoagent.launchinstead would change the CLI's printed result and need an old-host fallback. It would also need a host draft writer for new workspaces, so it is a feature, not parity.Differences from the common pattern, labeled:
worktree.createstays as its own wire method and keeps a terminal-only contract. Clients and hosts update independently, and its reply promises a terminal handle.Linked Issue
Maintainer change; part of the launch-unification work (no separate issue).
Visual Proof
No visual change by design. Main (left) vs this PR (right), hidden test app with stand-in agents that log what they receive:
orca worktree create --agent claude --prompt "fix the login bug": one terminal tab, prompt on the agent's command line.Same with "new agent tabs open as chat" switched on: still a terminal agent.
The phone's create-from-issue with an agent that reads input after start: the issue URL waits in the input, unsent.
Testing
startup-agent-worktree-create.test.ts: the entry passes a 3 KB prompt to the create with no typed-line measuring and no setup awaiting. It also covers a post-start agent, an empty prompt, a draft (startupDraft, no startupAgent), a draft with no agent resolved, a prebuilt command, a create that started no agent (no second surface), and a create failure.agent-launch-legacy-host-policy.test.ts: terminal-only ignores the chat default. The legacy policy hands the whole text to the create, reports a draft as not delivered, and refuses anything but a terminal-only create.worktree-create-startup-agent.test.ts: recorded CLI and phone requests go through the realworktree.createhandler with the chat default on. They get the same reply shape and handle, and the draft stays unsent.orca-runtime-startup-draft-launch-parity.test.ts: a linked-draft create on a real runtime detects the agent once and records the same agent and opens the same terminals as main, including when the agent's start command cannot be built.worktree,agent-launch, executor and startup tests pass.tc:nodeand the CI static checks pass.Manual QA on macOS (hidden test apps, main vs this PR, stand-in agents by absolute path): CLI create with a prompt, with a 3,000-character prompt, with no prompt, with an agent that reads input after start, with the chat default on, with
--activate, and the phone's raw draft request (Claude gets the URL through its draft flag, an agent without one gets it pasted with no Enter). Every row matched main: same JSON apart from terminal ids, one agent tab, no workspace switch unless asked, nothing submitted. Not verified: an SSH host (no free test target), the phone app itself (covered by a recorded-request test), Windows and Linux (no platform-specific code changed).Review
Two adversarial reviewers, an architecture review that sketched its own design first, a second-round review of the fixes, and a release-readiness pass. No P0-P2 findings. Fixed: detection running twice for a draft with no agent; the draft path recording the wrong agent when that agent's command can't be built (it could also leave a workspace with no terminal); runtime guards replaced by compile-time types; truthful internal prompt receipts; parity tests on a real runtime.
Agent skill upstream boundary
Notes
No wire change:
worktree.createparams and reply are unchanged, so older CLIs, phones and hosts keep working in every pairing. SSH, folder and WSL creates are unchanged: they still go through the same create, which routes them as before.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)