pub struct ForgeSdkBridge {
inner: Arc<BridgeInner>,
}Expand description
In-process bridge wrapping a single forge_sdk::Client.
Single instance per connection. The bridge owns the spawned
forge_sdk::Client, the can_use_tool parking lots, and the
outbound AgentEvent channel.
Fields§
§inner: Arc<BridgeInner>Implementations§
Source§impl ForgeSdkBridge
impl ForgeSdkBridge
Sourcepub(crate) fn new(
config_dir: PathBuf,
display_name: Option<String>,
extra_mcp_servers: Vec<(String, McpServer)>,
env: HashMap<String, String>,
) -> Self
pub(crate) fn new( config_dir: PathBuf, display_name: Option<String>, extra_mcp_servers: Vec<(String, McpServer)>, env: HashMap<String, String>, ) -> Self
Construct a fresh bridge bound to config_dir with an
optional forge-account display_name. Both are stored as
typed fields. config_dir is consulted by every in-process
accessor (oauth, settings, catalog scans) and exported to
the spawned claude subprocess as CLAUDE_CONFIG_DIR.
display_name, when present, is read back through the
handle’s display_name(). The internal event channel
is created here; consumers grab the receiver once via
ForgeSdkBridge::take_events.
Sourcepub(crate) fn env(&self) -> HashMap<String, String>
pub(crate) fn env(&self) -> HashMap<String, String>
The resolved forge.toml env to stamp onto every spawned
claude subprocess. Cloned per spawn_session call, but the
values were read from disk once at forge BOOT - a new bridge
does not re-read them, so a forge.toml edit needs a forge
restart, not a new session.
Sourcepub(crate) fn extra_mcp_servers(&self) -> Vec<(String, McpServer)>
pub(crate) fn extra_mcp_servers(&self) -> Vec<(String, McpServer)>
Forge-workspace-supplied in-process MCP servers to attach to
every spawned claude subprocess (e.g. the forge server
carrying the peer-coordination tools - #114 v1). Cheap-clone
via the McpServer’s Arc<dyn Tool> internals; called once
per spawn_session invocation.
pub(crate) fn event_tx(&self) -> &UnboundedSender<AgentEvent>
pub(crate) fn inner_pending( &self, ) -> &Arc<Mutex<HashMap<String, Sender<PermissionDecision>>>> ⓘ
pub(crate) fn inner_pending_questions( &self, ) -> &Arc<Mutex<HashMap<String, Sender<QuestionOutcome>>>> ⓘ
pub(crate) fn session_id_slot_arc(&self) -> &Arc<Mutex<String>> ⓘ
fn client(&self) -> Option<Client>
pub(crate) fn set_client(&self, client: Client)
pub(crate) fn clear_client(&self) -> Option<Client>
Sourcefn emit_mcp_error_or_log(
event_tx: &UnboundedSender<AgentEvent>,
session_id: String,
operation: &'static str,
server_name: Option<String>,
error_msg: String,
)
fn emit_mcp_error_or_log( event_tx: &UnboundedSender<AgentEvent>, session_id: String, operation: &'static str, server_name: Option<String>, error_msg: String, )
Send an AgentEvent::McpOperationError to the consumer; log a
BRIDGE_LIFECYCLE warn if the channel is closed (terminal
event for a user-visible MCP failure - silent-drop on
teardown race would lose the only path to surface).
On send failure we destructure the unsent event back out of
the SendError so the warn log carries session_id,
server_name, and the underlying error text - the most
actionable triage signals.
Sourcefn dispatch<F, Fut>(&self, label: &'static str, f: F) -> Result<()>
fn dispatch<F, Fut>(&self, label: &'static str, f: F) -> Result<()>
Spawn a fire-and-forget client call. Logs and drops on failure.
Sourcefn dispatch_with_failure<F, Fut, T>(
&self,
label: &'static str,
f: F,
on_failure: T,
) -> Result<()>
fn dispatch_with_failure<F, Fut, T>( &self, label: &'static str, f: F, on_failure: T, ) -> Result<()>
Spawn a fire-and-forget client call. A failure inside the
spawned future - or the client-less early Err - is emitted as
the caller’s typed event (a None maps to log-only) - a view
rolls back nothing otherwise: the spinner, the optimistic
chip, the waiting awaiter all unwind only if this event
arrives.
Source§impl ForgeSdkBridge
impl ForgeSdkBridge
Sourceconst CONTROL_RESPONSE_TIMEOUT: Duration
const CONTROL_RESPONSE_TIMEOUT: Duration
send_control parks until the CLI’s control_response; the
wait is bounded so a silent CLI surfaces as a typed failure
instead of a chip/spinner stuck on an optimistic change that
never took.
Sourceconst PROBE_MARK_DEADLINE: Duration = Self::CONTROL_RESPONSE_TIMEOUT
const PROBE_MARK_DEADLINE: Duration = Self::CONTROL_RESPONSE_TIMEOUT
How long a seat’s context-usage mark may stand before any asker may
reclaim it. The probe gives up at Self::CONTROL_RESPONSE_TIMEOUT,
counted from its closure’s first poll, so a mark past that bound is
either already dead or a probe the timeout is about to drop -
reclaiming costs at worst one overlapping probe, while leaving a dead
mark costs the seat its reading for the life of the process.
pub(crate) fn take_events(&self) -> Option<UnboundedReceiver<AgentEvent>>
pub(crate) fn prompt_text( &self, session_id: String, text: String, uuid: String, ) -> Result<()>
pub(crate) fn prompt_with_images( &self, session_id: String, text: String, images: Vec<ImageAttachment>, uuid: String, ) -> Result<()>
Sourcepub(crate) fn cancel_queued(
&self,
session_id: String,
uuid: String,
) -> Result<()>
pub(crate) fn cancel_queued( &self, session_id: String, uuid: String, ) -> Result<()>
Drop one prompt still in the CLI’s queue. The answer rides
AgentEvent::PromptCancelResolved rather than the call’s return: the
dispatcher channel is fire-and-forget, and a caller that reads false
has to tell the reader the prompt was already taken.
pub(crate) fn cancel(&self, session_id: String) -> Result<()>
Sourcefn set_mode_rejection_text(err: &Error) -> String
fn set_mode_rejection_text(err: &Error) -> String
The CLI’s own refusal text for a failed set_permission_mode.
The SDK wraps it twice (MessageParse Display + a
control failed: prefix); strip both so the chat line carries
the reason, not the wrapper.
pub(crate) fn set_mode( &self, session_id: String, mode: PermissionMode, ) -> Result<()>
pub(crate) fn set_model(&self, session_id: String, model: String) -> Result<()>
Sourcefn check_session_id(&self, session_id: &str, label: &'static str) -> bool
fn check_session_id(&self, session_id: &str, label: &'static str) -> bool
Verify the requested session_id matches the bridge’s current
session before dispatching a user-action method. In a session-swap
race a cancel/set_mode/set_model for session A could
otherwise hit session B’s Client. Logs the mismatch and returns
false; the caller is expected to drop the dispatch with a no-op
Ok(()). A request carrying no id at all is refused the same way
and logged at error, since there is nothing to match it to.
Other user-action methods (prompt_with_images) intentionally
opt out - see the inline rationale at each call site.
Sourcefn trace_session_id_bypass(&self, session_id: &str, label: &'static str)
fn trace_session_id_bypass(&self, session_id: &str, label: &'static str)
Sibling of Self::check_session_id for methods that intentionally
pass through on mismatch (no silent drop). Logs a breadcrumb at
trace so postmortems can correlate “my prompt vanished” with
a session-swap race, without raising the noise floor under
normal operation.
pub(crate) fn get_context_usage(&self, session_id: String) -> Result<()>
pub(crate) fn get_mcp_snapshot(&self, session_id: String) -> Result<()>
Sourcefn decode_or_report(
&self,
session_id: Option<&str>,
launch_settings: Value,
) -> Result<SessionLaunchSettings>
fn decode_or_report( &self, session_id: Option<&str>, launch_settings: Value, ) -> Result<SessionLaunchSettings>
Decode the launch settings, or report the refusal as the spawn failure it is (Ved refused the spawn, 2026-10-15).
The event is the point of doing it here. ConnectionFailed is what
the seat’s own task records a spawn failure by and rolls the row on, so
a payload that does not decode leaves a seat that says why and can be
retried, not one stuck at Spawning with nothing to read.
Sourcepub(crate) fn new_session(
&self,
session_id: Option<String>,
cwd: String,
launch_settings: Value,
) -> Result<()>
pub(crate) fn new_session( &self, session_id: Option<String>, cwd: String, launch_settings: Value, ) -> Result<()>
Start a session under session_id, or under an id the CLI picks
when that is None.
pub(crate) fn resume_session( &self, session_id: String, cwd: String, launch_settings: Value, ) -> Result<()>
Sourcepub(crate) fn resume_or_new_session(
&self,
session_id: String,
cwd: String,
launch_settings: Value,
) -> Result<()>
pub(crate) fn resume_or_new_session( &self, session_id: String, cwd: String, launch_settings: Value, ) -> Result<()>
Re-enter session_id, keeping the identity through a refusal.
Three attempts, in order: resume it; when the CLI refuses - the
transcript is gone - start a new session under the SAME id, which
the CLI adopts and which keeps the identity stable; when that is
refused too, start under a fresh id rather than failing. The
third attempt exists because the first two can both be refused
for a reason that is neither of them being absent (an unreadable
transcript), and that is a state the pre-id fallback handled
silently by starting a new session. Only all three failing
surfaces ConnectionFailed. Used by project-rooted spawns
(Default / Named) where the catalog’s recorded lead may be stale
(e.g. cross-account scan, deleted file, schema drift). See
forge_primitives::AgentCommand::ResumeOrNewSession for the
motivation.
cwd is also passed to the resume attempt, not just the
fallback. claude --resume <id> indexes sessions by the
project key derived from its own working directory, so
inheriting forge’s $PWD (the default when cwd is empty) makes
every cross-directory resume fail with “No conversation found
with session ID …” even when the .jsonl exists.
pub(crate) fn permission_response( &self, _session_id: String, tool_call_id: String, outcome: PermissionOutcome, ) -> Result<()>
pub(crate) fn question_response( &self, _session_id: String, tool_call_id: String, outcome: QuestionOutcome, ) -> Result<()>
Sourcepub(crate) fn config_dir(&self) -> PathBuf
pub(crate) fn config_dir(&self) -> PathBuf
Cheap clone of the bridge’s bound config_dir. Used by the
session worker to thread CLAUDE_CONFIG_DIR into the spawned
claude subprocess and by direct-return accessors.
Sourcepub(crate) fn display_name(&self) -> Option<String>
pub(crate) fn display_name(&self) -> Option<String>
Cheap clone of the bridge’s bound forge-account
display_name, when forge-workspace picked one. Used by the
session worker to attach forge_account to the initial
ForgeAccountIdentity emit alongside the CLI-side account.
Sourcepub(crate) fn claude_pid(&self) -> Option<u32>
pub(crate) fn claude_pid(&self) -> Option<u32>
OS PID of the spawned claude child, when a client is bound
and the transport reported one. Used by
crate::env::processes to anchor an OS-level walk of the
descendant process tree for the Inspector pane’s PROCESSES
section. Returns None before the first new_session /
resume_session lands a client, or after clear_client.