@sema-agent/core
Version:
Stateless, task-oriented AI agent core
31 lines (30 loc) • 1.94 kB
TypeScript
/**
* design/116 detach([C] gap ②)— per-tool-call detach 信号毂。
*
* The missing middle of mid-flight ctrl+b: `TaskStream.detach(toolCallId)` fires the controller here; a
* detach-capable tool (Bash) picks the signal up BY toolCallId from its mount closure and threads it into
* `env.exec({ detachSignal })`, where the env adopts the running child as a background shell. Run-local
* (one hub per task, created in prepare-task) — never cross-task. Zero vendored surface: the loop's
* tool-call plumbing is untouched; the hub rides the existing closure path like taskRegistry does.
*
* `request()` uses create-then-abort so ordering races are safe: a detach that lands BEFORE the tool
* reads its signal still detaches (the tool receives an already-aborted signal and adopts immediately).
*/
export declare class ToolDetachHub {
private readonly controllers;
/** the detach verb is caller-exposed — an unknown/never-started toolCallId would otherwise
* strand its controller until run end (gc only fires on the real tool-completion path), a per-run memory
* growth vector. Bound the map: on overflow, evict the OLDEST entry (Map preserves insertion order); a
* live tool re-materializes its controller on the next signalFor() and an aborted one stays aborted via
* the tombstone set below. */
private static readonly MAX_CONTROLLERS;
/** Fired-but-evicted ids (bounded): keeps create-then-abort race safety across an eviction. */
private readonly firedTombstones;
private controllerFor;
/** The signal a detach-capable tool threads into its env call. */
signalFor(toolCallId: string): AbortSignal;
/** Fire a detach request for a (possibly not-yet-started) tool call. Idempotent. */
request(toolCallId: string): void;
/** Drop a finished call's controller (bounded memory; called from the tool's completion path). */
gc(toolCallId: string): void;
}