import type { PlatformAdapter, IncomingMessage } from '../platform/index.js'; import type { RunThreadOptions } from '../core/types/thread-types.js'; type Enqueuer = (channel: string, fn: () => Promise) => boolean; type Tracker = (delta: number) => void; type Executor = (ctx: ThreadExecCtx) => Promise; /** Run a thread fire-and-forget while holding the daemon busy gate for the ENTIRE pipeline. * The Slack `!thread` path (ThreadExecutor.route), the scheduled-task path, and the task-dispatch * path all bracket runThread with trackPendingTask(±1). The MCP `thread_start` webhook path did * not — so a background thread was invisible to the busy/idle gate, and a deploy/restart deferred * during the orchestrating session's turn would fire on the next idle and SIGTERM app.ts * mid-thread, stamping it "Interrupted by server restart". This helper closes that gap. * * track(+1) is synchronous so the daemon observes `busy` before it can act on any idle. The gate * is held across BOTH the run AND the onSettled callback: onSettled (the MCP completion callback) * may wake the parent agent for a full LLM turn, and `track(-1)` synchronously emits IPC `idle`. * If we released the gate before awaiting onSettled, a deferred restart would fire mid-callback and * SIGTERM app.ts, dropping the proactive completion notification. So we await onSettled first, then * release. track(-1) lives in an inner `finally` so the gate never leaks. The thread runs detached: * errors are logged (the caller already returned the threadId to the MCP client, which polls * thread_status). `deps` is injectable for unit tests. */ export declare function runThreadDetached(threadId: string, runOpts: RunThreadOptions, deps?: { run?: (id: string, opts: RunThreadOptions) => Promise; track?: Tracker; onSettled?: (threadId: string) => void | Promise; }): void; export interface ThreadExecCtx { message: IncomingMessage; channel: string; adapter: PlatformAdapter; threadAnchorId: string | null; hasFiles: boolean; agentMessage: string; threadAddMatch: RegExpMatchArray | null; threadStartMatch: RegExpMatchArray | null; existingThread: any; isActiveThread: boolean; } export declare class ThreadExecutor { readonly _enqueue: Enqueuer; readonly _track: Tracker; /** Injectable for unit tests — allows verification of track(-1)-in-finally without running real thread ops. */ readonly _execute: Executor; constructor(opts?: { enqueue?: Enqueuer; track?: Tracker; execute?: Executor; }); route(ctx: ThreadExecCtx): Promise; private _executeReal; } export declare const threadExecutor: ThreadExecutor; export {};