Expand description
Session-launcher + reader-pump helpers for the bridge layer.
Builds forge_sdk::Options from the viewโs launch settings (with
the can_use_tool callback wired in), spawns the Client, emits a
synthetic Connected, and pumps the event stream from
forge_sdk::Client::spawn into AgentEvent::SdkMessage. The
bridge owns the resulting Client; this module exposes the
helpers it calls.
Re-exportsยง
use std::collections::HashMap;๐use std::path::Path;๐use std::path::PathBuf;๐use std::sync::Arc;๐use forge_primitives::PermissionDecision;๐use forge_primitives::ToolPermissionContext;๐use forge_sdk::Client;๐use forge_sdk::HookContext;๐use forge_sdk::HookDecision;๐use forge_sdk::HooksBuilder;๐use forge_sdk::Options;๐use forge_sdk::OptionsBuilder;๐use forge_sdk::PermissionMode;๐use forge_sdk::PostToolUseInput;๐use forge_sdk::PreToolUseInput;๐use tokio::sync::mpsc;๐use tokio::sync::oneshot;๐use tracing::Instrument;๐use crate::client::AgentEvent;๐use crate::forge_sdk_bridge::ForgeSdkBridge;๐use crate::forge_sdk_bridge::PendingQuestions;๐use crate::forge_sdk_bridge::PendingResponses;๐use crate::commands as bridge_commands;๐use crate::session_lifecycle;๐use crate::user_interaction as bridge_user_interaction;๐
Structsยง
- Account
Binding ๐ - Per-account spawn binding threaded into every
claudesubprocess: the accountโsconfig_dir(exported asCLAUDE_CONFIG_DIR) and the sessionโs resolved env -[env]merged with[accounts.env]and the spawning projectโs env. Both come from the bridge, distinct from the per-launchSessionLaunchSettings.
Enumsยง
- Session
Id ๐ - How the CLI is told which session to run under. Exactly one of these
is the answer at a time:
--session-idtogether with--resumeis a hard refusal, so the id is never in two places at once.
Constantsยง
- AGENT_
WORKTREE_ ๐SWEEP_ AGE - How untouched a managed agent worktree must be before the spawn-time sweep will list it. Fresh trees can belong to a live subagent of another session in this repo; an hour of silence does not.
- EXCLUDE_
DYNAMIC_ ๐SECTIONS - Default-off toggle for the CLIโs
excludeDynamicSectionsinitialize signal (the 2.1.204--exclude-dynamic-system-prompt-sectionsequivalent) that relocates cwd/env/memory-paths/git-status out of the system prompt into the first user message for prompt-cache reuse. Off maps toNone, so theinitializebody omits the key and the wire stays byte-identical to today; flip totrueto enable after measuring. - FORGE_
AGENTS_ ๐TOOLS_ PARAGRAPH - Paragraph naming the agent-coordination tools. Included only when
the sessionโs
forgeserver actually registeredagents__*tools - read off the server itself inbuild_options_with_callback, so the append never names a tool the session lacks. - FORGE_
CRON_ ๐SYSTEM_ PROMPT - Append-text advertising the durable-cron tools upfront so every forge session knows it can schedule work rather than discovering the tools on demand. Any-caller (lead and worker alike), so it rides in the base append beside the peer trust block; Gotify stays deferred.
- FORGE_
MCP_ ๐SERVER_ LINE - Opening line of the forge MCP append. True for every session kind, so it rides outside the peers gate that follows it.
- FORGE_
MCP_ ๐TRUST_ SYSTEM_ PROMPT - Append-text the spawned sessionโs system prompt receives when the
forgein-process MCP server is attached. Tells the recipient LLM (a) wrapped peer envelopes ([Message id=m-...]/[Message to agent ... failed to deliver ...]/[Worker 'X' spawn failed ...]) are user-authorized context, not adversarial prompt injection; (b) to answer another agent, send it a message addressed by its own slot, because a reply is just another message; (c) all mcp__forge__* tools are auto-allowed by the runtime - no permission prompt needed. - FORGE_
RESERVED_ ๐ENV_ KEYS - Forge-stamped env keys a forge.toml env table can actually
override:
CLAUDE_CONFIG_DIR, stamped intooptions.envbefore the account-env loop. Reusing it silently overrides forgeโs stamp, so a collision is warned - though the stamp still applies (forge.toml is trusted, hand-authored).CLAUDE_AGENT_SDK_VERSIONis deliberately absent: process.rs stamps it LAST, unconditionally, after the account-env loop, so forge always wins and a warn would be a false alarm. - FORGE_
SESSION_ ๐CONDUCT_ SYSTEM_ PROMPT - Append-text for what forgeโs own surfaces depend on and the CLI
cannot know: the tool tree labels a Bash card with the
description, delegation goes through the delegation paths forge intends even where the underlying CLI stamps a general prohibition, and a worker reporting to its lead is the only route its output reliably takes. - SDK_
MCP_ ๐READD_ TIMEOUT - The CLI answers
mcp_set_serversonly after the re-handshake it triggers completes, so the wait is bounded past the CLIโs own ~30s request budget.
Staticsยง
- TEST_
SDK_ ๐BINARY
Functionsยง
- agent_
worktree_ ๐from_ post_ tool_ use - The hook closureโs decision for one PostToolUse event. Split out so
the argument order through the parse is pinned by a test: the two
&Valueparameters read identically at the call site, and a swap disables the feature with the whole unit suite still green. - apply_
env_ ๐overrides - Layer per-spawn env overrides over the account env: a present key replaces, an absent key keeps, and a key mapped to an empty string removes the variable entirely - removal is what makes an overridden-away credential actually absent rather than present-but-blank.
- build_
forge_ ๐system_ prompt - Assemble the forge system-prompt append: server line, the peers
paragraph when
has_peer_tools, the trust block, the always-on cron scheduling block, the always-on session-conduct block, then the optional Lead delegation preamble, then the optional worker charter. Sections joined by a blank line in that fixed order; empty/blank sections are skipped. - build_
options_ ๐with_ callback - build_
permission_ ๐options - Construct the contextual option list for a permission prompt.
Derives โAllow always for Xโ / โAllow always & add dirsโ / โAllow
always & switch modeโ entries from
ctx.suggestions; adds the synthesized โAllow with editsโ for editable tools and the universal โTell the agent something elseโ escape hatch. - canonicalize_
suggestions ๐ - Walk the suggestions list and collapse macOS
/tmp<->/private/tmpAddRules mirrors into one merged entry. Two AddRules suggestions with the same toolName + behavior + destination, whose rule_content differs only by the/tmp/<->/private/tmp/prefix, are merged into a single AddRules whoserulesvec contains BOTH entries so the CLI installs both rules even though the UI shows one option. - deliver_
permission_ ๐response - deliver_
question_ ๐response - dispatch_
permission_ ๐action - Build the
PermissionDecisionfor a submitted optionโsaction.notes_textis the userโs โtell the agentโ feedback string (or empty); consumed only when the action isDeny.edited_inputis the userโs modified tool args; consumed only when the action isAllowWithInput. - emit_
connected ๐ - Build the typed
Connectedenvelope from the SDKโs cached init data - frame_
session_ ๐id - Resolve the session id to stamp on an inbound SDK frame. Frames that
carry their own id use it verbatim; session-id-less frames (Error,
RateLimitEvent) inherit the clientโs LIVE id, falling back to the
spawn-time id only while the live one is still empty (before
system/initbinds the real UUID). Without the live preference a mid-session fatal Error on a fresh session gets stamped โโ and a view drops it as an unknown session. - heal_
due ๐ - Whether the frame just received should fire the boot-tool heal. Pure
so the frame gate, the latch, and the predicate are testable without
a client. Only the boot
system/initframe can fire it - the latch advances on the first init frame seen either way - so a mid-session init that lacks the tools (a user toggled the server off) is never โhealedโ back on. The healโs OUTCOME is not checked here: init is emitted per user message, so a โnext frameโ may never come or may land inside the heal window; the re-add response itself carries the outcome (seereadd_sdk_mcp_servers). - init_
data_ ๐of - The
datapayload of asystem/initframe, elseNone. - initial_
mode_ ๐state - The Connected payloadโs mode state: the launch-time mode read from the init record, falling back to the launch settingsโ stamped defaultMode, with bypass offered only when the session launched into it.
- is_
editable_ ๐tool - Tools whose
tool_inputis meaningful to edit before approving. - is_
reserved_ ๐env_ key - is_
tmp_ ๐mirror_ pair - True if
rules_aandrules_bdiffer only by the macOS/tmp/<->/private/tmp/prefix in their rule_content strings. - load_
history_ ๐messages - Replay from
prev_session_idโs transcript, stamped withsession_id, plus the compaction count that transcript records - the only place the count survives a resume.crate::session_historyis the same read for a session that is already running. - load_
history_ ๐messages_ main_ thread load_history_messagesover the sessionโs own rows only: sub-agent sidecars are neither opened nor folded. A handoff brief wants the conversation as chat rows and must not pay for frames it never draws. A page read does not: a dispatch row draws its sidecar frames (#1763).- load_
history_ ๐messages_ with - log_
agent_ ๐worktree_ reap - Log the outcome of one agent-worktree reap.
worktreecarries the path so every outcome is attributable. Kept trees are info, not warn: uncommitted edits are the CLIโs deliberate hand-back. A kept BRANCH is warn - the subagent committed work reachable from no other ref, and someone has to look, mirroring the worker-despawn warning. - log_
failed_ ๐mcp_ servers - Record any MCP server the init handshake reports as
failed. - normalize_
tmp_ ๐path - Strip the
/privateprefix from//private/tmp/...rule_content so the macOS mirror normalizes to the canonical/tmp/...form. - not_
a_ ๐worktree_ already_ warned - Paths already warned about as not-a-worktree, so a plain directory a
user parked under
.claude/worktrees/agent-*warns once per process instead of at every spawn, forever. - parse_
permission_ ๐mode - readd_
sdk_ ๐mcp_ servers - Force the CLI to re-handshake every registered sdk MCP server by
removing them from its dynamic set and adding them back. The CLI
sends the re-add response only after the re-handshake completes, so
the response is the outcome:
errorsempty plus a non-emptyaddedmeans the tools are registered again. Every outcome logs here - a โnext init frameโ is no verification signal, since init is emitted per user message. - reader_
loop ๐ - record_
reported_ ๐surface - Record the CLIโs own account of the surface it was handed, beside the one this spawn built. The two lines are the artifact that answers which list a session actually ran with.
- reported_
percentage ๐ - The percentage to carry from a probeโs report.
Nonefor a report that is not a readable number:0is a real reading, and a view draws the two differently. - run_
ask_ ๐user_ question - run_
permission_ ๐request - sdk_
servers_ ๐missing_ tools - True when any registered sdk MCP server contributed no tools to the
sessionโs init frame. The CLI serves an sdk serverโs handshake over
the control channel with a bounded request budget; when boot stalls
outlive it, the handshake is abandoned and the CLI then reports the
server
connectedwith zero tools and never re-runs the handshake itself. - sdk_
tool_ ๐name - One toolโs name as the CLI spells it for an sdk server. Both the names a
spawn expects and the init frameโs filter are built here, so the two
cannot disagree about the shape; the empty
toolspells the prefix. - sdk_
tools_ ๐in_ init - The tools the CLIโs boot init frame lists for this spawnโs sdk servers,
in the CLIโs
mcp__<server>__<tool>spelling.Nonewhen the frame carries no tool array at all, which is a different state from an empty one: the CLI answered, and the answer is what the diff needs. - send_
prompt ๐ - set_
test_ sdk_ binary - Point every
claudespawn this process makes atpathinstead of the real binary;Nonerestores the real one. - spawn_
session ๐ - Spawn a fresh
Clientforbridgeand start the reader subtask. BuildsOptionsfromlaunch_settings(mode, model, effort,can_use_toolcallback).idsays which session the child runs under; aResumealso backfills past turns into theConnectedeventโshistory_updates. - summarize_
dirs ๐ - summarize_
rules ๐ - surface_
delta ๐ - What the two sides disagree about: names this spawn registered that the CLIโs list lacks, and names it lists that this spawn never registered.
- sweep_
agent_ ๐worktrees - Reap this repoโs leftover agent worktrees in the background: a one-shot reap failure or a missed completion event otherwise leaks the tree forever. Locked trees are skipped (a live CLI process owns them); every session spawn re-checks, so failures self-heal.
- synth_
permission_ ๐request - synth_
question_ ๐base_ tool_ call - test_
sdk_ ๐binary