/** * @fileoverview ChatProcessManager — spawn wrapper for `roy-agent act`. * * v2.5.0: encapsulates the user-decided integration strategy documented in * /tmp/task-show-v2.5-spawn-plan.md. We do NOT depend on the roy-agent * SDK; instead we shell out to `node act -s ` per query and stream the subprocess stdout back to the * consumer. * * Concurrency: a FIFO queue caps the number of in-flight subprocesses at * `maxConcurrent`. Excess calls `await` until a slot frees up. * * Lifecycle: every `sendStream` call spawns exactly ONE short-lived * subprocess. The subprocess is SIGTERM'd (then SIGKILL after 5s) on * `abort()`. The slot is released in the `exit` handler so a failure * cannot leak concurrency. * * Output parsing: each line written by the subprocess is classified into * a `ChatChunk` (see type below). Sentinels and ANSI codes are stripped * before classification — see `chat-output-parser.ts`. */ /** One unit of output emitted by `roy-agent act`. */ export type ChatChunkType = "start" | "text" | "reasoning" | "done" | "error"; export interface ChatChunk { type: ChatChunkType; /** Plain text payload (already ANSI-stripped). */ text?: string; /** Final metadata attached to start/done chunks. */ meta?: { sessionId: string; latencyMs?: number; exitCode?: number; }; /** Error info attached to error chunks. */ error?: { message: string; code?: string; }; } export interface SendOptions { /** Hard timeout (default = constructor `defaultTimeoutMs`). */ timeoutMs?: number; /** Working dir for the subprocess (default = process.cwd()). */ cwd?: string; /** Extra args appended AFTER `--tool-calls=false`. Used by tests. */ extraArgs?: string[]; } export interface ChatProcessManagerOptions { /** Absolute path to the roy-agent CLI entry. */ cliPath: string; /** Maximum concurrent subprocesses (default 4). */ maxConcurrent?: number; /** Default timeout per query (default 300_000 ms). */ defaultTimeoutMs?: number; /** * v2.5.7+: extra args appended to EVERY spawned subprocess invocation * (after `--tool-calls=false`). Used by tests that wire * `fake-act.mjs --mode=multi`; production deployments leave this * empty. */ defaultArgs?: string[]; } export interface SendResult { exitCode: number; durationMs: number; } /** * Format captured stderr for the error chunk payload. * * ## Why this exists (Task #2831 bug fix) * * The previous implementation called `stderrBuf.trim().split(/\r?\n/) * .slice(-3).join(" | ")`, which dropped everything except the last * three lines. When `roy-agent act` crashed with a SyntaxError dump, * the user only saw the trailing `}]` partial JSON + Node.js version * banner — the actual error type, message, file path, and stack * frames were silently discarded. * * ## Strategy (v2.5.5+) * * 1. **Empty stderr** → fall back to `"roy-agent act exited with * code "` so the chunk is never undefined/empty. * 2. **≤10 lines** → return the FULL stderr verbatim. Short * crashes should be readable in full. * 3. **>10 lines** → return the first 5 lines + a clear truncation * marker (`... (N lines omitted) ...`) + the last 5 lines. The * head always carries the error type/message; the tail often * carries the leaf frame and Node.js banner. * * Each line is preserved with its original newline so the user can * actually read the stack in the chat panel instead of a pipe-joined * blob. */ export declare function formatStderrForError(stderr: string, exitCode: number): string; /** * Manager — single instance per task-show process. Stateless beyond the * concurrency queue + abort map. */ export declare class ChatProcessManager { private readonly cliPath; private readonly maxConcurrent; private readonly defaultTimeoutMs; private readonly defaultArgs; private active; private queue; private abortFlags; private totalCompleted; constructor(opts: ChatProcessManagerOptions); /** Block until a concurrency slot is available. */ private acquire; /** Release a slot; wake the next waiter if any. */ private release; /** * Stream a chat turn. The callback receives ChatChunk objects in the * order they are classified; the returned promise resolves with the * subprocess exit metadata. */ sendStream(sessionId: string, message: string, onChunk: (chunk: ChatChunk) => void, opts?: SendOptions): Promise; private emitLine; /** Abort an in-flight query (best-effort SIGTERM). */ abort(sessionId: string): void; /** Stats for monitoring / health endpoints. */ stats(): { active: number; queued: number; totalCompleted: number; }; } //# sourceMappingURL=chat-process-manager.d.ts.map