/** * TurnDispatcher — single entry point for direct (non-bus) agent turns. * * Wraps the two existing direct-turn runners (one-shot and streaming) so the * parent `AgentService` no longer carries a pair of huge `createXxxDeps()` * factories. The public surface — `processDirect`, `processDirectStreaming`, * `steerWebchatSession`, `enqueueWebchatStreamEvent`, * `notifyWebchatTranscriptAppend` — matches what `AgentService` exposed * previously, so callers (gateway, CLI) are unchanged. * * Like `OutboundCoordinator` and `InboundLoop`, this class accepts a wide * dependency bag in its constructor (the direct-turn pipeline touches almost * every other subsystem). The win is that AgentService becomes the only place * that wires those dependencies together; individual responsibilities now live * in their own classes and are unit-testable. */ import type { TurnOrigin } from '@xopcai/endpoint-tools-protocol'; import type { Config } from '../../config/schema.js'; import type { ContextualLogger } from '../../utils/logger/types.js'; import type { AgentManager } from '../agent-manager.js'; import type { CommandHandler } from '../messaging/command-handler.js'; import type { ModelManager } from '../models/index.js'; import type { SessionConfigStore } from '../../session/index.js'; import type { SessionStore } from '../../session/store.js'; import type { SessionContext, SessionHydrator, SessionStateBag } from '../session/index.js'; import type { InboundAttachmentInput, MediaRef } from '../../channels/attachments/inbound-persist.js'; import { type ProcessDirectStreamEvent } from '../service/process-direct-streaming.js'; import type { AgentSourceContextResolver } from '../source-context/types.js'; export interface TurnDispatcherConfig { log: ContextualLogger; agentManager: AgentManager; sessionStore: SessionStore; modelManager: ModelManager; sessionConfigStore: SessionConfigStore; sessionState: SessionStateBag; commandHandler: CommandHandler; getConfig: () => Config | undefined; /** Strict accessor — required for direct-turn paths that must have a config. */ requireConfig: () => Config; resolveSessionEndpoint: (sessionKey: string) => Promise<{ channel: string; chatId: string; }>; /** Establish per-session context (also creates the Agent + subscribes to events). */ initSessionContext: (sessionKey: string, channel: string, chatId: string, origin: TurnOrigin) => SessionContext; /** Per-session config hydration: workspace, model, thinking. */ sessionHydrator: SessionHydrator; prepareInboundAttachments: (sessionKey: string, attachments?: InboundAttachmentInput[]) => Promise; enqueueMaybeAutoTitleAfterPersist: (sessionKey: string) => void; enqueueProvisionalSessionTitle?: (sessionKey: string, userText: string) => void; endDirectRequestContext: () => void; /** Gateway hook fired after assistant text lands on disk (UI refetch). */ onSessionTranscriptUpdated?: (sessionKey: string) => void; resetSession: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string; } | null>; sourceContextResolver?: AgentSourceContextResolver; } export type DirectAttachment = InboundAttachmentInput; export interface ProcessDirectOptions { signal?: AbortSignal; runId?: string; deadlineAtMs?: number; } export declare class TurnDispatcher { private readonly cfg; private readonly log; constructor(cfg: TurnDispatcherConfig); /** One-shot direct turn (CLI / embedded TUI). */ processDirect(content: string, sessionKey: string, origin: TurnOrigin, attachments?: DirectAttachment[], thinking?: string, options?: ProcessDirectOptions): Promise; /** Streaming direct turn (webchat realtime / CLI streaming). */ processDirectStreaming(content: string, sessionKey: string, origin: TurnOrigin, attachments?: DirectAttachment[], thinking?: string, options?: { signal?: AbortSignal; runId?: string; }): AsyncGenerator; /** Push an out-of-band event into the live webchat stream for a session. */ enqueueWebchatStreamEvent(sessionKey: string, event: { type: string; [key: string]: unknown; }): void; /** Stream assistant text to live webchat session + notify transcript listeners. */ notifyWebchatTranscriptAppend(sessionKey: string, assistantText: string): void; /** * Queue a steering user message into pi-agent's in-flight run (delivered * after current tool work, before the next LLM call). See `Agent.steer` * in `@earendil-works/pi-agent-core`. */ steerWebchatSession(sessionKey: string, text: string): Promise; private buildStreamingDeps; private buildOneShotDeps; }