Repository navigation
Inbox: launch agents through one host interface (stack 4a) - #2402
Open
backnotprop wants to merge 41 commits into
Open
backnotprop wants to merge 41 commits into
backnotprop wants to merge 41 commits into
Conversation
…nto feat/inbox-launch-host # Conflicts: # docs/agent-guides/inbox-launch.md # docs/agent-guides/server-and-storage.md # packages/server/inbox-launch-claude.ts
…nto feat/inbox-launch-host # Conflicts: # docs/agent-guides/inbox-launch.md # docs/agent-guides/server-and-storage.md # packages/server/inbox-launch.ts
The engine (inbox-launch.ts) now holds `{ "claude-code": claude }`, one
LaunchHost per agent (inbox-launch-host.ts), and names no agent itself:
the record's agent, to.host and a permission question's author come from
the host. Claude Code's CLI calls, settings file, trust, availability,
bridge liveness, respawn, stop/rm/purge and attach move into
createClaudeLaunchHost (inbox-launch-claude.ts); its hook route, key,
leases and person-only terms into inbox-launch-claude-permission.ts. The
shared questions (ask, answer, withdraw) stay agent-neutral in
inbox-launch-permission.ts.
No behaviour change: signatures of createInboxLaunch and
createLaunchPermissions kept (hosts is an optional, test-only addition),
the record, routes and UI strings unchanged. A record whose agent has no
host gets no line, no sweep and 404 launch_not_found.
New: inbox-launch-host.test.ts registers an in-memory fakeLaunchHost and
drives start, the run line, Stop and a reply's restart through it.
Co-Authored-By: Claude <noreply@anthropic.com>
…feat/inbox-launch-host # Conflicts: # docs/agent-guides/inbox-launch.md # docs/agent-guides/server-and-storage.md # packages/server/inbox-launch-claude.ts # packages/server/inbox-launch-permission.ts # packages/server/inbox-launch.ts
…lete through the fake host; the fake's answer in Terminal is per session
…feat/inbox-launch-host # Conflicts: # packages/server/inbox-launch-claude.ts # packages/server/inbox-launch.ts
…feat/inbox-launch-host
This was referenced Oct 31, 2026
One shape for Codex (#2407), OpenCode (#2408) and Pi (#2412), so each can merge this and drop its own variant. Claude Code uses none of the optional seams; its behaviour and its words are unchanged. - LaunchHostContext.loginEnv(): a fresh login-shell read (shared with one under way, never held), for a host about to spawn its own process. - LaunchHost.close?(): the Inbox stops; the engine's stop() calls it on each host after it unsubscribes. - LaunchHost.stoppedNote?(launch): per launch, the words after "<name> stopped." when the session stopped without the person's Stop (the stopped line's `cause`); none: today's line. - A resume that throws is a failed restart ("It did not start again."), logged to start.log, never an unhandled rejection. - The permission card names the agent from its question's author (agentNameOf); Claude Code's cards read as before. - Merge-friendly lists: the engine's ownHosts, InboxLaunchAgent and OWN_FAKES one entry per line.
A host's `live` may read null as soon as its stop begins (OpenCode's does), so the 1 s sweep's maybeRespawn could resume a session mid-Delete (a server started into a session being deleted, then orphaned) or undo a Stop within a second. The sweep now skips a launch in `stopping` or `deleting`, and a reply still waiting when the person pressed Stop is held (`Memo.heldReply`): the person's next reply starts the session again and takes it along. Test: inbox-launch-host.test.ts, a slow Stop and a slow Delete with a reply waiting (fails without this change). Co-Authored-By: Claude <noreply@anthropic.com>
…feat/inbox-launch-host
…attachments and plugin check - launchNoteText and writeLaunchNote live in inbox-launch-host.ts, for the hosts that write thread notes. - Read on the Inbox's uploads in the settings file: an attachment is read without a prompt. - The login PATH goes in the settings file; a respawn runs with the login env, as a start does. - With no SHELL in the Inbox's env the account's own shell is read, never plain sh. - A start whose login env can't reach claude finds it in the install folders. - The plugin check reads the Claude folder the login shell names.
# Conflicts: # packages/server/inbox-launch.ts # tests/agent-cli-stubs.test.ts
# Conflicts: # tests/agent-cli-stubs.test.ts
# Conflicts: # docs/agent-guides/inbox-launch.md
# Conflicts: # docs/agent-guides/inbox-launch.md
# Conflicts: # packages/server/inbox-launch-permission.ts
…aunch-host # Conflicts: # packages/server/inbox-launch.ts
…and Delete) into feat/inbox-launch-host # Conflicts: # packages/server/inbox-launch.ts
…to feat/inbox-launch-host
…x-launch-host # Conflicts: # packages/server/inbox-launch.ts
…o feat/inbox-launch-host
…ner, guest stopped line) into feat/inbox-launch-host # Conflicts: # packages/inbox/components/RunLine.tsx # packages/server/inbox-launch.ts
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #2398 (
feat/inbox-launch-scratch, now merged in atfb9a6d45f, which brings #2378 at3e86bbbc5with the per-sessionlocalAnswerfix). Review these commits:Inbox launch: launch agents through one host interface(the refactor);Inbox launch: hosts keyed by their own agent; drop test-only and unused exports(review fixes);Inbox launch: the seam test covers a question, Stop's withdraw and Delete through the fake host; the fake's answer in Terminal is per session(review fixes, and the e2e flake).The merge commits only bring the stack together.
What this does
This is a pure refactor (PR 4a in the launch plan). The launch engine now launches agents through one interface:
packages/server/inbox-launch-host.ts:LaunchHost, a plain TypeScript interface, andLaunchHostContext, what the engine gives a host. There is no registry, plugin system or config. The engine buildshostsfrom[claude, ...options.hosts?.(context)], keyed by each host's ownagent.packages/server/inbox-launch.ts(the engine) names no agent. These come from the host or fromlaunch.agent:agentandto.host;target/composeBlock, which loop over the hosts;scratch_trusted;Availability, the 10 s sessions view and the permission transport are kept per host.
packages/server/inbox-launch-claude.ts:createClaudeLaunchHost. It holds Claude's:startOf;trusts, one member; where trust is accepted stays inside the Claude host);claude --bg, the bridge-polllive, respawn, stop/rm/purge (purge returnsskippedinside a repository);createClaudeHostis the CLI driver.packages/server/inbox-launch-claude-permission.ts: Claude's transport. It holds:readDue/settledAfterandlaunchPermissionHooks;claudePermissionTerms,claudeInBypass,CLAUDE_TERMINAL_ONLY,CLAUDE_HANDOFF,CLAUDE_HANDOFF_PLAIN, and the oneCLAUDE_NAME.packages/server/inbox-launch-permission.tsis now agent-neutral:createLaunchQuestions,openPermissionSinceandfileTarget. A question's author host and its delivery come fromlaunch.agent.After this PR, adding Codex is one new host file and its test fake.
No behaviour change
createInboxLaunchandcreateLaunchPermissionskeep their signatures.hostsis a new optional option, used only by tests.inbox-launch-permission.test.ts. Three of its lines now call the sharedopenPermissionSince(store, threadId)in place of the removed test-onlypermissions.openSince(root). The expected values are the same.A record whose agent has no host (after a downgrade):
signature()shows-;/api/inbox/launch/:id/*route and the permission route answer404 launch_not_found.Left for PR 4, as HOST.md says:
settingsState, which the engine still reads from the Claude host.Review fixes
hostskeyed byhost.agent. The option now returns a list.inbox-launch-host.test.ts, 3 tests) now also checks these:createLaunchQuestionslands with the fake's agent as its author (a mutation that hard-codes"claude-code"there fails it);stop, then its transport'swithdrawAll, and the question is superseded;stop,removeandwithdrawAll;purgefor its folder.inbox-launch-permission.spec.ts, mode read asautoinstead ofbypassPermissions):localAnswer. It was one global switch, so it answered every session still on a dialog, including one left from an earlier test, and that session's record could land first.localAnsweris now the id of the session the person answers inclaude attach, and only that session's dialog takes it. Every call in the spec names its thread's session (sessionOf).openSinceand the unused context members (store,now);ClaudeLaunchHost,LaunchQuestions,LaunchPermissions);CLAUDE_NAME.Merge of #2398 at
528f6e2ed(claudePurgeRefusaland the dry-run gate stay in the Claude host'spurge; thedeletingMap that makes a double-click Delete answer once stays in the engine), and earlier atdd72e65be(S5):agents[i].scratch_trustedfrom each host'strusts.scratch_inodeidentity checks and the scratch root's repository refusal stay in the engine (they are agent-neutral).claudeRepositoryAbove) stays in the Claude host.Correction: the earlier body named the wrong commit for the smoke lines. They come from
f40a32aa5/f6ab5fa3d. Both guard failures are now fixed upstream (#2376 at2e37ea8b0, #2378 at90844fbeb), and they pass here.Finishing for merge
This section does change behaviour, for launched Claude Code sessions only. Launch stays off by default.
launchNoteText(the sanitizer) andwriteLaunchNotelive ininbox-launch-host.ts. Codex, OpenCode and Pi use them.launchUploadsDirnames the Inbox uploads folder. A Claude launch's settings file allowsReadthere, so reading the person's own attachment doesn't sit on a prompt. The rule's real effect is for the real-CLI check.PATH.respawnnow gets the login env, asstartdoes:--bgandrespawnmay be what starts Claude's supervisor, and a worker inherits the supervisor's env.SHELLin the Inbox's env (launchd), the login read uses the account's shell.claude, it's found through the install folders of the plain env.available()looks in the login shell'sCLAUDE_CONFIG_DIR, the folder a launched session uses.Tests
528f6e2ed)inbox-launch-host.test.tsnever-touch-the-real-inbox,no-browser-sentinel,agents-md-sizeinbox-launch-claude+-permission+-scratchThe unit files are:
inbox-launch-{claude,env,permission,settings,scratch}andinbox-permission-answers;inbox-run-line,inbox-permissionandinbox-scratch;inbox-permission-hook,composeModel,sendTargets,shared/configandagent-cli-stubs.Their tests are the ones at
528f6e2ed; only the imports changed.bun run typecheckis clean.The apps were built in order (review, hook, opencode), and
.local/plannotatorwas compiled.The e2e runs used Bun 1.4.4, the e2e lock, a temp HOME and data dir,
PLANNOTATOR_INBOX_CONNECT=0andNO_BROWSER.Line counts (engine + Claude + shared permission, plus the new files):
inbox-launch-claude-permission.ts306 andinbox-launch-host.ts142);