/** * Brainstorm Dispatcher Layer (Phase 4) * * Prepares and validates subagent dispatch payloads for brainstorm. The * parent LLM is the one that actually calls the `subagent` tool — this * module does NOT spawn subagents. It returns a hardened payload the * parent then hands to subagent(), and it tracks dispatch counts so the * per-brainstorm cap is enforced. * * Guards (Phase 2) are layered in here: * - guardDispatchCount refuses when the next dispatch would exceed the cap. * - guardArtifactPath refuses any artifact path outside the brainstorm folder. * * Registry (Phase 3) is layered in here: * - loadBrainstormRegistry surfaces the eligible-agent list. * - isBrainstormEligible checks that the requested agent is allowed. * * Read-only enforcement (Phase 4): * - enforceReadOnlyTools strips Write/Edit/Bash from the agent's tool list. * - The returned PreparedDispatch.tools is the cleaned allowlist. * * Audit (Phase 5) writes happen OUTSIDE this module. The dispatcher returns * a `startedAt` timestamp + `dispatchNumber` so the audit layer can log * the dispatch cleanly when the parent LLM reports back. */ import { type SenaiRole } from "../core/agents-config/suggestions.js"; import type { ScanType } from "../core/state.js"; /** Wall-clock budget per dispatch, in milliseconds. The parent LLM uses * this to decide when to cancel a stalled subagent. */ export declare const BRAINSTORM_DISPATCH_TIMEOUT_MS = 30000; /** Wall-clock budget for COMMUNITY-SCAN dispatches. Web research (search + * fetch + read) cannot complete in the 30s local-scan budget — 30s silently * degraded community scans to "draft questions are simpler", which killed * the external-evidence step exactly when it mattered. */ export declare const BRAINSTORM_COMMUNITY_DISPATCH_TIMEOUT_MS = 90000; /** Max dispatches per scan type at the scan gate (2 doc + 2 community * parallel, etc.). Separate from BRAINSTORM_DISPATCH_CAP, which bounds the * whole brainstorm. */ export declare const SCAN_TYPE_DISPATCH_CAP = 2; /** Default tools every brainstorm dispatch receives. Specialists may add * more read-only tools via the registry's per-role allowlist. */ export declare const DEFAULT_DISPATCH_TOOLS: readonly string[]; /** What the parent LLM hands us when it wants to dispatch a specialist. */ export interface DispatchRequest { /** Agent name — must be brainstorm-eligible AND resolve from the registry. */ agent: string; /** One-paragraph task description. Long copy-pasted files are forbidden. */ task: string; /** Optional: hint for the parent LLM about what summary to expect. */ expectedOutput?: string; /** Optional: explicit tools allowlist (defaults to DEFAULT_DISPATCH_TOOLS). */ tools?: readonly string[]; /** Optional: artifact paths the subagent may write. Each is path-guarded. */ artifactPaths?: readonly string[]; /** Optional: which scan gate this dispatch belongs to. When set, the * agent's role must serve that scan type (SCAN_TYPE_ROLES), the per-type * cap applies, and community scans get the longer timeout. */ scanType?: ScanType; } /** What the dispatcher returns when validation succeeds. The parent LLM * passes these args to `subagent({ ...subagentArgs, ...overrides })`. */ export interface PreparedDispatch { /** Agent name (validated). */ agent: string; /** Resolved role for the agent — used by audit log. */ role: SenaiRole; /** Task description (passed through verbatim; the dispatcher does not edit). */ task: string; /** Expected output hint for the parent LLM (or "" if not provided). */ expectedOutput: string; /** Hardened tools allowlist — Write/Edit/Bash already stripped. */ tools: string[]; /** Brainstorm run id (for context). */ brainstormRunId: string; /** Working directory for the subagent — points at the brainstorm folder. */ cwd: string; /** Subagent invocation args. The parent LLM spreads these into subagent(). */ subagentArgs: { agent: string; task: string; tools: string[]; /** Hint to the parent: how long to wait before cancelling. */ timeoutMs: number; /** CWD the subagent should run in. */ cwd: string; }; /** Monotonic dispatch number within this brainstorm (1-based). */ dispatchNumber: number; /** ISO timestamp when the dispatch was prepared. */ startedAt: string; } /** Result of dispatch preparation. */ export type DispatchPrepResult = { ok: true; prepared: PreparedDispatch; } | { ok: false; reason: string; details?: string[]; }; /** Prepare a specialist dispatch. * * Returns `{ok: true, prepared}` when the request is valid and the * parent LLM should proceed to call `subagent(prepared.subagentArgs)`. * Returns `{ok: false, reason}` when any guard refuses the dispatch. * * Validates in order: * 1. agent name is brainstorm-eligible (registry layer) * 2. dispatch count is under cap (guard layer) * 3. all artifact paths (if any) live inside the brainstorm folder * 4. tools list is read-only (after strip) — empty list is rejected * * The dispatcher never calls subagent itself. The parent LLM owns the * actual spawn. */ export declare function prepareDispatch(request: DispatchRequest, brainstormRunId: string, currentDispatchCount: number, currentScanTypeCount?: number): DispatchPrepResult; /** Resolve a SenaiRole from an agent name. Used by audit logging and tests. * Returns the best guess based on built-in defaults + role name match. */ export declare function resolveRoleForAgent(agentName: string): SenaiRole; /** Helper for the parent LLM: format the prepared dispatch as a prompt * block. Includes all subagent invocation args the parent needs to make * the call. */ export declare function formatPreparedDispatch(prepared: PreparedDispatch): string;