import type { TurnOrigin } from '@xopcai/endpoint-tools-protocol'; /** * GatewayAgentRunner — webchat agent invocation and the surrounding control * surface (abort, steer, clarify-bridge plumbing, scheduled Task continuations). * * Was 200 lines of `GatewayService` covering seven concerns that all hung off * the same handful of fields (`activeWebchatRunBySession`, `runAbortControllers`, * `clarifyBridge`, `runRelay`): * * - `runAgent(message, channel, chatId, ...)` — wraps {@link runGatewayAgent} * - `abortAgentRun(runId)` — POST /api/agent/abort + cleanup * - `submitClarifyResponse(requestId, answer)` — UI answers a `clarify` call * - `runScheduledWebchatTurn(sk, userTurn)` — background webchat user turn * - `drainScheduledWebchatContinuation(sk, msg)` — background Task continuation * - `clarifyForSession({ sessionKey, request })` — clarify-bridge dispatch * used by `gatewayClarify.requestClarification` in AgentService * * Owns the two state maps (`activeWebchatRunBySession`, `runAbortControllers`) * directly so peer coordinators (sessions-api, marketplace, config) cannot * accidentally mutate them. */ import type { Config } from '../../config/schema.js'; import type { MessageBus } from '../../infra/bus/index.js'; import type { AgentService } from '../../agent/service.js'; import type { ChannelManager } from '../../channels/manager.js'; import type { SessionIndex } from '../../session/index.js'; import type { ClarifyStreamEvent } from '../clarify-bridge.js'; import { ClarifyBridge, type ClarifyBridgeRequest } from '../clarify-bridge.js'; import type { UserTurnAttachment, UserTurnInput } from '../user-turn-input.js'; import { SessionInputCoordinator, type SubmitSessionInput } from './session-input-coordinator.js'; export interface GatewayAgentRunnerOptions { bus: MessageBus; sessionIndex: SessionIndex; /** Resolved lazily — the runner is constructed before AgentService exists. */ getAgentService: () => AgentService; getChannelManager: () => ChannelManager; getConfig: () => Config; /** Publish low-frequency gateway state changes. */ emit: (type: string, payload: unknown) => void; publishRealtime: (topic: string, event: string, data: unknown) => void; completeRealtimeTopic: (topic: string) => void; } export declare class GatewayAgentRunner { private readonly opts; /** Per-run abort for webchat (POST /api/agent/abort or client disconnect). */ private readonly runAbortControllers; private readonly runCompletions; private readonly resolveRunCompletions; private readonly clarifyBridge; /** Maps webchat session key → active `runId` for `clarify` tool routing. */ private readonly activeWebchatRunBySession; readonly inputs: SessionInputCoordinator; constructor(opts: GatewayAgentRunnerOptions); /** True when a webchat agent run is currently in-flight for `sessionKey`. */ hasActiveRun(sessionKey: string): boolean; getActiveRunId(sessionKey: string): string | undefined; getClarifyBridge(): ClarifyBridge; /** Called from `GatewayService.stop()` so the bridge gets cleaned up. */ disposeClarifyBridge(): void; runAgent(message: string, channel: string, chatId: string, origin: TurnOrigin, attachments?: UserTurnAttachment[], thinking?: string, runOptions?: { signal?: AbortSignal; runId?: string; }): AsyncGenerator<{ type: string; [key: string]: unknown; }, { status: string; summary: string; }, unknown>; submitSessionInput(input: SubmitSessionInput): Promise<{ ok: true; effectiveDelivery: import("../../storage/sqlite/session-input-repository.js").SessionInputDelivery; state: import("../../storage/sqlite/session-input-repository.js").SessionInputState; } | { ok: false; code: "BAD_REQUEST" | "QUEUE_FULL"; }>; getSessionInputState(sessionKey: string): import("../../storage/sqlite/session-input-repository.js").SessionInputState; updateSessionInput(sessionKey: string, id: string, body: { version: number; content?: string; attachments?: UserTurnAttachment[]; thinking?: string; position?: number; }): Promise<{ ok: boolean; state: import("../../storage/sqlite/session-input-repository.js").SessionInputState; }>; removeSessionInput(sessionKey: string, id: string, version: number): { ok: boolean; state: import("../../storage/sqlite/session-input-repository.js").SessionInputState; }; recoverSessionInputs(): void; /** Abort an in-flight webchat agent run. */ abortAgentRun(runId: string): Promise<{ aborted: boolean; idle: boolean; }>; /** Deliver a user's answer to a pending `clarify` tool call. */ submitClarifyResponse(requestId: string, answer: string): boolean; /** Same execution path as scheduled continuation, but lets callers observe failures. */ runScheduledWebchatTurn(sessionKey: string, userTurn: UserTurnInput): Promise; runScheduledWebchatContinuation(sessionKey: string, message: string): Promise; /** Background drain for extension-initiated webchat turns (`scheduleWebchatContinuation`). */ drainScheduledWebchatContinuation(sessionKey: string, message: string): Promise; /** * Resolve clarify-bridge config for `sessionKey`: who delivers the question * (webchat stream, Telegram message, or both), then start the bridge request. * Rejects when neither path is available (e.g. CLI without webchat or TG). * * `publishStreamFor(runId)` is the bridge into AgentService's * `turnDispatcher.enqueueWebchatStreamEvent`. We take it as a callback so the * runner does not import AgentService statically. */ requestClarification(opts: { sessionKey: string; request: ClarifyBridgeRequest; publishStreamFor: (runId: string) => (event: ClarifyStreamEvent) => void; }): Promise; private deliverTelegramClarify; }