pub struct AcpController {
tab_id: String,
block_id: String,
inner: Arc<Mutex<AcpInner>>,
broker: Option<Arc<Broker>>,
event_bus: Option<Arc<EventBus>>,
mstore: Option<Arc<Store>>,
filestore: Option<Arc<FileStore>>,
health_monitor: Arc<TurnActivityTracker>,
next_rpc_id: Arc<AtomicU64>,
outstanding_prompt_ids: Arc<Mutex<HashSet<u64>>>,
start_lock: Mutex<()>,
}Expand description
AcpController manages an ACP-speaking agent process.
Fields§
§tab_id: String§block_id: String§inner: Arc<Mutex<AcpInner>>§broker: Option<Arc<Broker>>§event_bus: Option<Arc<EventBus>>§mstore: Option<Arc<Store>>§filestore: Option<Arc<FileStore>>§health_monitor: Arc<TurnActivityTracker>§next_rpc_id: Arc<AtomicU64>Monotonically increasing JSON-RPC request ID.
outstanding_prompt_ids: Arc<Mutex<HashSet<u64>>>Every session/prompt request id that has been SENT but not yet
RESOLVED (by either a real stopReason completion or a JSON-RPC
error rejection). The turn is genuinely over only once this set
is empty — see resolve_prompt_and_check_idle’s doc comment for
why tracking the full set (not just “the latest” id) is necessary:
steering can leave multiple prompts outstanding at once, and
responses/rejections can arrive in any order relative to each
other and relative to new sends (independent stdin-writer and
stdout-reader tasks, no ordering guarantee between them). codex P2
on PR #2338 (twenty-seventh through thirtieth re-reviews).
start_lock: Mutex<()>Serializes ensure_started, so two deliveries that find the agent
stopped can’t both start it.
Implementations§
Source§impl AcpController
impl AcpController
pub fn new( tab_id: String, block_id: String, broker: Option<Arc<Broker>>, event_bus: Option<Arc<EventBus>>, mstore: Option<Arc<Store>>, filestore: Option<Arc<FileStore>>, ) -> Self
fn next_id(&self) -> u64
fn set_status(inner: &mut AcpInner, status: &str)
fn get_status_snapshot(&self) -> BlockControllerRuntimeStatus
fn publish_status(&self)
pub(crate) fn is_running(&self) -> bool
Sourcefn make_request(&self, method: &str, params: Value) -> String
fn make_request(&self, method: &str, params: Value) -> String
Build a JSON-RPC 2.0 request.
Sourcefn make_notification(&self, method: &str, params: Value) -> String
fn make_notification(&self, method: &str, params: Value) -> String
Build a JSON-RPC 2.0 notification (no id field).
Source§impl AcpController
impl AcpController
Sourcefn begin_process(&self) -> u64
fn begin_process(&self) -> u64
A new process takes over the controller: the generation moves on, and the prompts the previous process never answered are forgotten. Its waiter, which sees the new generation, no longer clears them; left there they would keep the turn active for good.
Sourcepub fn ensure_started(
&self,
block_meta: HashMap<String, Value>,
) -> Result<(), String>
pub fn ensure_started( &self, block_meta: HashMap<String, Value>, ) -> Result<(), String>
Start the agent again if its process has exited, from the pane’s meta; a no-op when it is running. One start at a time: a second delivery waits here and then finds it running.
Sourcepub fn send_message(
&self,
message: String,
message_id: Option<&str>,
) -> Result<(), String>
pub fn send_message( &self, message: String, message_id: Option<&str>, ) -> Result<(), String>
A user message from agentinput / agent.send (run_agent_turn):
sent as session/prompt, then acknowledged the way the other
controllers acknowledge theirs, so the pane stops showing it as pending.
Sourcefn spawn_env(
&self,
block_meta: &HashMap<String, Value>,
) -> HashMap<String, String>
fn spawn_env( &self, block_meta: &HashMap<String, Value>, ) -> HashMap<String, String>
The env this block’s ACP process is spawned with: cmd:env (either
shape), with the block’s row UID + token carried onto it — identity
M4b-2. Buys no attribution today (ACP agents are not given
agentmux-mcp) but keeps the counters true.
Trait Implementations§
Source§impl Controller for AcpController
impl Controller for AcpController
Source§fn start(
&self,
block_meta: HashMap<String, Value>,
_rt_opts: Option<Value>,
_force: bool,
) -> Result<(), String>
fn start( &self, block_meta: HashMap<String, Value>, _rt_opts: Option<Value>, _force: bool, ) -> Result<(), String>
force restarts even if already running.Source§fn stop(&self, _graceful: bool, _new_status: &str) -> Result<(), String>
fn stop(&self, _graceful: bool, _new_status: &str) -> Result<(), String>
graceful waits for process to exit; new_status is the target state.Source§fn get_runtime_status(&self) -> BlockControllerRuntimeStatus
fn get_runtime_status(&self) -> BlockControllerRuntimeStatus
Source§fn send_input(
&self,
input: BlockInputUnion,
_seq: Option<u64>,
) -> Result<(), String>
fn send_input( &self, input: BlockInputUnion, _seq: Option<u64>, ) -> Result<(), String>
seq is the per-TermViewModel monotonic counter; None means fire-and-forget (no ordering).Source§fn turn_tracker(&self) -> Option<Arc<TurnActivityTracker>>
fn turn_tracker(&self) -> Option<Arc<TurnActivityTracker>>
None for
controllers that track no turns.Source§fn controller_type(&self) -> &str
fn controller_type(&self) -> &str
Source§fn stop_for_replace(&self, new_status: &str) -> Result<(), String>
fn stop_for_replace(&self, new_status: &str) -> Result<(), String>
needs_replace path),
NOT because the block itself is being closed. Must terminate this
controller’s own CLI process so it doesn’t linger, but — unlike
stop() — must NOT reach any declared-background descendant the
process may have spawned (e.g. a task dev instance launched via
run_in_background: true); those must survive the replace. See
docs/specs/SPEC_BACKGROUND_TASK_TEARDOWN_SURVIVAL_2026_08_20.md. Read moreSource§fn shutdown(
&self,
_deadline: Instant,
) -> Pin<Box<dyn Future<Output = StopOutcome> + Send>>
fn shutdown( &self, _deadline: Instant, ) -> Pin<Box<dyn Future<Output = StopOutcome> + Send>>
deadline. The
returned future resolves once the process has actually exited (or
there was none). Does NOT drop the block’s process tracker — the
caller does that after this resolves, so nothing the agent started
outlives it. See
docs/specs/SPEC_AGENT_PANE_CLOSE_GRACEFUL_SHUTDOWN_2026_09_18.md §9.3. Read moreSource§fn agent_id(&self) -> Option<String>
fn agent_id(&self) -> Option<String>
ReactiveHandler::inject_message_inner, deliberately
NOT derived from ReactiveHandler’s own name_to_blocks/agent_info
maps (checking a registry against itself would be a tautology and
catch nothing). Default None for controller types that aren’t
jekt-addressable at all (e.g. plain terminals) — only ShellController
and PersistentSubprocessController currently override this, mirroring
the two paths that already resolve a jekt-registration identity at
spawn time (resolve_agent_id_for_jekt, muxbus_agent_id_from_env).Source§fn turn_provenance(&self) -> Option<TurnProvenance>
fn turn_provenance(&self) -> Option<TurnProvenance>
None = unknown, which never counts as the
user — the safe answer for controllers that can’t tell (ACP, App
Server, subprocess: both origins reach them through one method).Source§fn set_agent_id(&self, _id: Option<String>)
fn set_agent_id(&self, _id: Option<String>)
agent_id’s doc comment). Called whenever
ReactiveHandler::register_agent/register_agent_with_nonce
(re-)registers THIS block’s block_id under a (possibly different)
agent_id, so the two independently-written copies never drift apart
(#2697: agent_id() was captured once at spawn and never
refreshed, while the registry’s name bindings get re-keyed on every
register_agent call — e.g. handle_reactive_register’s
frontend-initiated HTTP path — causing a legitimately renamed or
reconfigured agent’s own messages to be falsely rejected as an
identity mismatch). Default no-op for controller types that don’t
override agent_id either.Source§fn stable_agent_id(&self) -> Option<String>
fn stable_agent_id(&self) -> Option<String>
AGENTMUX_AGENT_ID as captured
once at spawn, never refreshed. Distinct from agent_id,
which tracks the LIVE, renameable display name and is deliberately
refreshed every turn (see that method’s doc comment on #2697).
AGENTMUX_AGENT_ID is embedded verbatim in PR-body tags specifically
because it does NOT change if the agent is later renamed — but
ReactiveHandler’s registry only tracks one key per block, and
every turn’s agent_id() refresh evicts whatever this block was
previously registered under. ReactiveHandler::inject_message_inner’s
recipient-identity check (#2695) uses this alongside agent_id() so
a jekt addressed to the stable ID is validated correctly even after
the primary registry key has moved on to the live display name. See
INCIDENT_2026_09_09_JEKT_STABLE_ID_ALIAS.md. Default None,
mirroring agent_id’s default — only PersistentSubprocessController
currently overrides this.Source§fn stable_agent_uid(&self) -> Option<String>
fn stable_agent_uid(&self) -> Option<String>
db_agents.id) as carried in its
spawn env (AGENTMUX_AGENT_UID, set by build_persistent_spawn_env
since M1a), captured once at spawn like [stable_agent_id]. Backs the
registry’s UID confirmer: consulted only for targets that resolved by
UID, and None — a registered-but-not-yet-spawned controller, or a
block with no row — is “unverifiable”, not a mismatch. Default
None; only PersistentSubprocessController overrides this.Auto Trait Implementations§
impl !Freeze for AcpController
impl !RefUnwindSafe for AcpController
impl !UnwindSafe for AcpController
impl Send for AcpController
impl Sync for AcpController
impl Unpin for AcpController
impl UnsafeUnpin for AcpController
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more