import type { AssistantMessage, ImageContent, Model } from "../llm/index.js"; import type { AgentMessage, AgentTool, LoopMalformedToolUseRecovery, LoopThinkingOnlyRecovery, LoopTruncatedOutputRecovery, QueueMode, ThinkingLevel } from "../loop/types.js"; import type { AbortResult, AgentHarnessEvent, AgentHarnessEventResultMap, AgentHarnessOptions, AgentHarnessOwnEvent, AgentHarnessResources, AgentHarnessStreamOptions, ExecutionEnv, PromptTemplate, Skill } from "./types.js"; /** * Harness-level recovery wiring (design/118 ④b/⑤). The loop's prompt-too-long seam wants a * replacement TRANSCRIPT; at the harness level the correct policy is a SESSION-persisted reduction * (e.g. a forced compaction) — otherwise the next turn's `createTurnState()` rebuilds the context * from the session and the in-memory shrink evaporates. So the harness contract is: `recover` * persists the reduction to the session and returns whether anything shrank; the harness then * rebuilds the turn state FROM the session and hands the loop the rebuilt transcript. */ export interface HarnessLoopRecovery { truncatedOutput?: LoopTruncatedOutputRecovery; malformedToolUse?: LoopMalformedToolUseRecovery; thinkingOnly?: LoopThinkingOnlyRecovery; /** design/128-follow-up (TB 尸检 T1-4): recover from a DEGENERATE-REPETITION cutoff instead of ending the * run — the brain already cut the stream and stamped the turn errored; when `detect` matches, the loop * nudges "don't repeat that, take a different approach" and runs another turn, bounded to `maxContinues` * CONSECUTIVE times (default 2). The cut-off turn stays in the transcript (its salvaged text is real * context). `detect` is injected by the caller (core owns the brain's DEGENERATE_MESSAGE constant). */ degenerateOutput?: { detect: (message: AssistantMessage) => boolean; maxContinues?: number; }; /** design/130 P2b: recover from a WALLTIME-CUTOFF turn (the brain cut the stream at the soft * per-call deadline) with ONE bounded write-out continue — see LoopRecoveryOptions.walltimeCutoff. * `detect` is injected by the caller (core owns the brain's WALLTIME_CUTOFF_MESSAGE constant). */ walltimeCutoff?: { detect: (message: AssistantMessage) => boolean; maxContinues?: number; }; promptTooLong?: { /** Persist a context reduction to the SESSION; return true if anything shrank. Must not throw. */ recover: (attempt: number) => Promise; /** Override the loop's default prompt-too-long classifier. */ detect?: (message: AssistantMessage) => boolean; /** Max recovery retries per turn (loop default: 2). */ maxRetries?: number; /** M1 CC-withhold parity: hold the failure's stream events until the recovery outcome is * known (loop default: true). See LoopPromptTooLongRecovery.withholdErrorEvents. */ withholdErrorEvents?: boolean; }; } export declare class AgentHarness { readonly env: ExecutionEnv; private session; private phase; /** design/74 R3-A: a one-way soft-stop flag. When set, `shouldStopAfterTurn` returns true at the next turn * boundary so the loop exits CLEANLY (emit `agent_end` + return) WITHOUT an abort/interrupt — the path a * resource-slice suspend needs (an abort would trip the orphan-[INTERRUPTED] reconcile = corrupt resume). */ private _stopAfterTurn; private runAbortController?; private runPromise?; private pendingSessionWrites; private model; private thinkingLevel; private systemPrompt; private streamOptions; private getApiKeyAndHeaders?; private runtime?; private resources; private tools; private activeToolNames; private steerQueue; private steeringQueueMode; private maxOutputTokens?; private maxOutputTokensPerCall?; private callDeadlineMsPerCall?; private loopTrace?; private resilience?; private maxToolConcurrency?; private streamingToolExecution?; private followUpQueue; private followUpQueueMode; private nextTurnQueue; private loopRecovery?; /** roadmap #5 (CC Stop hook): the runner-wired stop gate — see {@link setStopGate}. */ private stopGate?; private handlers; constructor(options: AgentHarnessOptions); private getHandlers; private emitOwn; private emitAny; /** * Emit a hook event to its registered handlers and return a single result. * * **Last-write-wins:** handlers run in registration order; each non-`undefined` result overwrites * the previous one, and the LAST non-`undefined` result is returned (`undefined` = abstain, keeps * the prior winner). Handlers do NOT compose — registering two handlers that both return a result * for the same event silently drops all but the last. A handler that throws aborts the whole emit * (errors are not swallowed). Callers that need ordered, composable semantics for one event (e.g. * the design/37 two-phase tool gate: collect arg rewrites, then adjudicate policy) must funnel that * logic through a SINGLE handler rather than relying on multiple handlers + this fold. */ private emitHook; private emitBeforeProviderRequest; private emitBeforeProviderPayload; private emitQueueUpdate; private startRunPromise; private createTurnState; private createContext; private createStreamFn; private drainQueuedMessages; private createLoopConfig; /** * design/118 ④b/⑤: wire (or clear) the loop's recovery chains. The runner calls this after * construction — truncated-output auto-continue and the prompt-too-long recover policy are * PROFILE decisions (the harness only relays them to the loop). */ setLoopRecovery(recovery: HarnessLoopRecovery | undefined): void; /** * roadmap #5 (CC Stop hook): wire (or clear) the stop gate — consulted ONLY when the run would * otherwise end (no tool calls, steer + followUp queues dry). Non-empty return = the run * continues with those messages injected; empty = the run ends. Policy (the Hooks.stop call, * consecutive-block cap, reason formatting) lives in the runner; the harness only supplies the * drain-point timing. */ setStopGate(gate: (() => Promise) | undefined): void; private validateToolNames; private flushPendingSessionWrites; private handleAgentEvent; private emitRunFailure; private executeTurn; prompt(text: string, options?: { images?: ImageContent[]; }): Promise; steer(text: string, options?: { images?: ImageContent[]; }): Promise; followUp(text: string, options?: { images?: ImageContent[]; }): Promise; nextTurn(text: string, options?: { images?: ImageContent[]; }): Promise; appendMessage(message: AgentMessage): Promise; getModel(): Model; getThinkingLevel(): ThinkingLevel; setModel(model: Model): Promise; setThinkingLevel(level: ThinkingLevel): Promise; setActiveTools(toolNames: string[]): Promise; getSteeringMode(): QueueMode; setSteeringMode(mode: QueueMode): Promise; getFollowUpMode(): QueueMode; setFollowUpMode(mode: QueueMode): Promise; getResources(): AgentHarnessResources; setResources(resources: AgentHarnessResources): Promise; getStreamOptions(): AgentHarnessStreamOptions; setStreamOptions(streamOptions: AgentHarnessStreamOptions): Promise; setTools(tools: TTool[], activeToolNames?: string[]): Promise; /** * design/74 R3-A: request a CLEAN stop at the next turn boundary. Unlike {@link abort} (a hard * AbortController cancel that — with no `suspendRef` — drives the orphan-[INTERRUPTED] session reconcile), * this sets a one-way flag the loop reads via its `shouldStopAfterTurn` callback: the current turn finishes * its tool calls, the loop emits `agent_end`, and returns. The runner uses it for a resource-slice suspend * so the suspended session is left clean (resumable), never reconciled as interrupted. Idempotent. */ requestStopAfterTurn(): void; /** * VENDOR.md mod #5 (fixed at de-vendoring, design/118 ⑤): drain session writes queued during an * ABORTED run. The harness intentionally does NOT flush on the abort path — the caller's interrupt * reconcile must append its synthetic tool_results first (closing the open tool_call batch), then * call this, so queued writes always land on a legal transcript. Safe to call anytime; no-op when * the queue is empty. */ flushQueuedSessionWrites(): Promise; abort(): Promise; waitForIdle(): Promise; subscribe(listener: (event: AgentHarnessEvent, signal?: AbortSignal) => Promise | void): () => void; on(type: TType, handler: (event: Extract) => Promise | AgentHarnessEventResultMap[TType]): () => void; } //# sourceMappingURL=agent-harness.d.ts.map