import type { AgentMessage } from "../internal/harness.js"; import type { Session } from "./session.js"; import type { ToolEffect } from "./types.js"; /** * Wake/resume side-effect reconciliation. * * Problem (design/07 · C3): a task can be interrupted (crash, kill, timeout, lost instance) in the * window between an assistant message with tool calls being persisted and that turn's tool results * being flushed. The session event log then ends with one or more **orphan tool calls** — a tool * call with no matching `toolResult`. On wake this is two bugs at once: * 1. Most chat APIs reject a request whose assistant tool_call is not followed by a tool result. * 2. The tool's `execute` may already have run (the side effect happened) — or may not have. The * outcome is genuinely unknown. * * Contract enforced here: * - **Never auto-replay a tool.** Reconstruction (`buildSessionContext`) already does not re-run * tools; this guarantees we also never synthesize a "success" that hides an unknown outcome. * - For each orphan, append a synthetic `toolResult` (isError) that tells the model the call was * interrupted and its outcome is unknown — making the transcript API-valid AND handing the * decision to the model under its verification discipline. * - Use the tool's {@link ToolEffect} to tune the message: `read`/`idempotent` are safe to repeat; * `write` (and unknown tools, conservatively) must be verified/confirmed before repeating. * * This runs on `acquire` of a resumed session, before the next turn starts. New/empty sessions and * cleanly-finished sessions have no orphans and are left untouched. */ /** An assistant tool call with no matching `toolResult` in the active branch. */ export interface OrphanToolCall { toolCallId: string; toolName: string; } /** Outcome of reconciling a resumed session. */ export interface ReconcileReport { /** Orphan calls that were closed with a synthetic interrupted result (empty when nothing to do). `entryId` = * the persisted toolResult entry id — message-identity Phase 1: the caller emits a `message_committed` for each * so a consumer's `entryId → message` map stays complete for these reconcile-appended results (they render a * `tool_end`). They are never a compaction floor (`toolResult` is excluded from cut points), so this is map * completeness, not `preserved_segment` correctness. */ recovered: Array; } /** * Find assistant tool calls in `messages` that have no matching `toolResult`. * Order follows first appearance in the transcript (orphans are normally only at the tail). * * `suspendedBatch` (design/45 §15.2 net-add #7): the set of tool-call ids belonging to a **deliberately * suspended** batch (the pending + not-yet-started calls of a durable-checkpoint suspension). These are * NOT orphans — they are waiting to be resumed via `runner.resume()`, not crash-interrupted. Wake-path * reconcile must skip them, or it would append `[INTERRUPTED]` results and **destroy the suspended * batch** (the bug this guard fixes: the wake/resume naming overload became real data destruction). * `runner.resume()` uses a checkpoint-aware entry that bypasses this reconcile entirely for those calls. */ export declare function findOrphanToolCalls(messages: AgentMessage[], suspendedBatch?: ReadonlySet): OrphanToolCall[]; /** * Reconcile a resumed session's active branch: close any orphan tool calls with a synthetic * interrupted `toolResult` (never re-running the tool). Returns what was recovered. * * @param toolEffects optional name→effect map (from the task's tools). Unknown names are treated as * `write` — the conservative default, so an unrecognized interrupted call is never said to be safe. */ export declare function reconcileInterruptedSession(session: Session, toolEffects?: Map, suspendedBatch?: ReadonlySet): Promise; //# sourceMappingURL=session-reconcile.d.ts.map