/** * CollaborationBus — kernel-level pause/resume signal for the * collaboration `controller` role (Phase 3 of idea #13 from IDEAS.md). * * The bus is a single-process singleton. The webui server (which hosts * the agent loop) creates one at boot, passes it to both the agent's * `toolCall` pipeline (as a middleware) and the `CollaborationWsHandler` * (which toggles it on client requests). * * Semantics: * - `requestPause(by)` flips the bus into the "paused" state. The * state has no timeout of its own — the *middleware* decides how * long to wait before auto-resuming (default 60s). * - `resume()` returns the bus to the running state. Idempotent: * calling resume() when not paused is a no-op returning `false`. * - `waitForResume(timeoutMs)` is what the middleware calls. It * returns `true` if the bus was resumed in time, `false` if the * timeout fired (in which case it also auto-resumes, so the agent * loop is never permanently stuck). * - `getState()` returns a snapshot for observability surfaces * (UI badges, CLI `/collab status`). * * Why a separate bus object rather than putting state on RunController? * * - The RunController is per-run; the bus is per-process. A single * pause signal across the whole webui process is the right * granularity (the webui hosts exactly one Agent.run at a time). * - The bus can be unit-tested without spinning up an Agent. The * middleware can be tested by injecting a fake bus. * - Future multi-session webui (when the bus is upgraded to filter * by sessionId) does not require touching the agent loop — the * middleware already receives the sessionId in its payload. */ export type CollabBusState = { paused: boolean; pausedAt: string | null; pausedBy: string | null; }; export declare class CollaborationBus { private pausePromise; private pauseResolve; private pausedAtMs; private pausedBy; isPaused(): boolean; getState(): CollabBusState; /** * Pause the agent loop. Idempotent: a second `requestPause` while * already paused is a no-op (the original pause wins; we do not * overwrite `pausedBy`). Returns true when the state actually * transitioned, false when it was already paused. */ requestPause(byParticipant: string): boolean; /** * Resume the agent loop. Returns true when the state actually * transitioned from paused → running, false when it was already * running (no-op). */ resume(): boolean; /** * Block until the bus is resumed or the timeout fires. Returns: * - `true` → bus was resumed in time * - `false` → timeout fired; bus was auto-resumed as a side effect * * When `timeoutMs` is `0` the wait is unbounded (the middleware must * be paired with an external AbortSignal in that case — we don't * expose one here to keep the API simple). */ waitForResume(timeoutMs: number): Promise; private readonly injectionQueue; private onConsumed?; /** * Register a listener fired when `collabInjectMiddleware` actually splices a * queued injection into a tool call (Phase 4 feedback loop). The webui's * CollaborationWebSocketHandler uses it to broadcast a * `collab.injection.granted` with phase `'consumed'` so observers learn the * injection was applied (and to which real tool). Last registration wins. */ onInjectionConsumed(fn: (info: ConsumedInjectionInfo) => void): void; /** * Invoked by `collabInjectMiddleware` immediately after it replaces a tool * call's result with a queued injection. No-op when no listener is set. */ notifyInjectionConsumed(info: ConsumedInjectionInfo): void; /** * Queue a manual tool result. The next time the agent's toolCall * pipeline sees a matching `toolUse.id`, the * `collabInjectMiddleware` consumes this entry and replaces the * real tool execution. Returns `false` if an injection for the * same id is already queued (idempotent — first write wins). */ injectToolResult(input: InjectedToolResult): boolean; /** * Pop an injection from the queue. Returns the payload (and * removes it) if one is pending, or `null` when nothing matches. * Called by the middleware on every tool call. */ takeInjection(toolUseId: string): InjectedToolResult | null; /** Inspect the queue size (for observability / tests). */ pendingInjectionCount(): number; } /** * Payload for a queued manual tool-call injection. The middleware * splices this into the toolCall pipeline as a synthetic result. */ export interface InjectedToolResult { toolUseId: string; /** Serialized content the model will see (string, JSON, etc.). */ content: unknown; /** When true, the result is rendered as a tool error. */ isError: boolean; /** * Free-form context surfaced in the broadcast and the audit log, * e.g. "controller: read returned the right file but with these * contents" or "controller: skipped the destructive bash call". */ reason: string; /** Participant id of the controller who issued the injection. */ authorId: string; } /** * Emitted to `onInjectionConsumed` listeners when the middleware splices a * queued injection into a real tool call. Carries the resolved tool NAME * (unknown at queue time — the injection is keyed only by tool_use_id). */ export interface ConsumedInjectionInfo { toolUseId: string; toolName: string; authorId: string; reason: string; isError: boolean; } //# sourceMappingURL=collab-bus.d.ts.map