import { type Span } from '@opentelemetry/api'; import type { SessionEvent, StepResult } from '../../../types/session/index.js'; import type { IterationContext } from './phases/index.js'; export type { IterationContext } from './phases/index.js'; export type { PhaseSignal } from './phases/index.js'; export type { ToolReviewOutcome } from './phases/index.js'; export { awaitedJobGraceMs, settleGraceMs } from './outstanding-work.js'; export declare class IterationOrchestrator { private ctx; private advisoryTurn; /** Live only within its iteration; never joined by a guessed array offset. */ getAdvisoryTurnContext(): import('../../../advisory/executor.js').AdvisoryTurnContext | undefined; /** Rejections so far. See {@link DEFAULT_ANSWER_REVIEW_LIMIT}. */ private answerReviewAttempts; /** * The last request envelope this turn recorded, so an unchanged one * costs a hash and no event. Per RUNNER, not module-level: two turns in * one process must not suppress each other's first envelope. */ private lastEnvelopeKey; private previousRequestContext; private projectObservations; /** Rich tool blocks already reported; durable history is scanned every turn. */ private readonly warnedRichToolResults; /** * The previous iteration held a `stopWhen` decision open for a worker. * * Set when the stop predicate fired and the turn took one extra turn to * read a delegated result, so the turn that then ends the turn can report * WHY it is over. Without it the outcome was right and the record was * wrong: the turn stopped because the host said so and reported `end_turn`, * and this repo carries thirteen `StopReason` values precisely so that a * run which ends for a nameable reason names it. * * Lives for exactly one iteration — see the read-and-clear at the top of * the loop, which is the only site that touches it besides the one that * sets it. */ private stopDeferredForOutstandingWork; /** One current input, independent of the compactable history array. */ private latestUserMessage; constructor(ctx: IterationContext); /** * Check the exact post-budget request for tool-result shapes the active driver * cannot carry. Initial capability negotiation cannot see results produced by * a later tool turn, so this boundary runs immediately before every provider * call. Keys are durable call/block coordinates, which prevents old history * from warning again on every subsequent iteration. */ private reportUnsupportedToolResults; /** * Adopt the turn's span after construction. * * The orchestrator is built before `query()` enters its generator body, * which is where the turn span is created — so the parent cannot be a * constructor argument without reordering setup around one field. */ setRootSpan(span: Span): void; runLoop(): AsyncGenerator; /** * The context and the two live reads the step-shaping helpers share. * * Built per call rather than held: `latestUserMessage` is replaced on * every operator turn and `steps` grows by one per step, so a captured * value would describe an earlier return. */ private stepShaping; /** Steps completed so far, exposed on the returned `Turn`. */ private readonly steps; getSteps(): readonly StepResult[]; /** * Fold one iteration into a `StepResult`. * * Every field here was already computed somewhere in the loop; the only * new work is subtracting the cumulative counters so the step carries * ITS usage rather than the turn's running total, which is the number a * caller asking "what did this step cost" actually wants. */ /** * Take everything queued for this turn since the last turn. * * Both channels drain here. `inboundMessages` is the manager's queue — * what `continueTask` and `queueMessage` push onto and nothing ever * collected. `steering` is the host's, and it could only ride on a tool * result, so guidance queued during a turn that called no tools stayed * pending until the turn ended. * * Returns the count so a caller can decide whether a turn is owed. An * empty drain must change nothing at all: a `continue` on nothing queued * spends an iteration and a model call to say the same thing again. */ private rememberUserMessage; private deliverInbound; private recordStep; /** Turns spent asking the model again for a valid structured output. */ private structuredOutputAttempts; private nativeStructuredAttempts; private structuredOutputDone; private pendingStructuredOutput; private structuredReviewAttempts; private structuredOutputRetryLimit; /** True while a structured output was demanded and has not arrived. */ private needsStructuredOutput; /** * The answer a terminal tool produced, or `undefined` to keep looping. * * Deliberately narrow. A terminal call decides the turn only when it is * the ONLY call the model made in that turn: a model that asked for * other work meant to see those results, and settling here would throw * away answers it requested. Same for a failed terminal call — an * error is not an answer, and the model is the one that should read * it. Both cases fall through to the ordinary path, and both say so in * the log rather than quietly costing the relay the flag was set to * avoid. */ private terminalToolOutput; /** * Record the structured output if this batch produced one. * * The tool validates against the Zod schema before its `execute` runs, * so reaching here successfully means the value is already valid — a * failed parse comes back as an error result and simply does not * satisfy the demand, which sends the loop round again. * * Narrow in the same way {@link terminalToolOutput} is, for its stated * reason and one that is sharper here. The neighbour refuses a shared * turn because "a model that asked for other work meant to see those * results". That applies unchanged. But the batch has ALREADY executed * by the time this runs — `runToolReview` settles it, side effects * included, before either of these is consulted — so settling here is * worse than discarding an answer the model wanted: the work happened, * its results went into the transcript, and the turn ended before any * model turn could read them. Nothing consumed what was spent, and * nothing said so. * * Sharper, too, because of WHEN this value was produced. The model * emitted its final answer in the same turn as a request for * information it had not yet received — it would not have asked * otherwise — so the answer is under-informed on the model's own * account, and settling ships it as final. * * So: relay, do not settle. The results are already in the transcript, * the demand is still unsatisfied, and the next turn produces the * answer with them in hand. Refusing to EXECUTE the batch was the other * candidate and is wrong — the defect is not that the tools ran, it is * that nobody read them, and denying a model work it asked for to * protect an answer it has not finished forming gives up a real * capability for nothing. The price is one extra turn when the paired * call was a pure side effect whose result the model did not need; * that is the price `terminalToolOutput` already pays, and a model * avoids it by not pairing. * * NOT charged to `maxRetries`. That budget bounds a model that cannot * satisfy the SCHEMA, and this one did. A turn reading two files a turn * while optimistically attaching its answer is making progress, and it * must not die reported as `structured_output_failed` — a failure that * did not happen. `maxIterations` is the bound for a model that keeps * doing work, and it is the bound the neighbour relies on for the * identical pathology. */ private captureStructuredOutput; private publishStructuredOutput; private reviewStructuredOutput; /** A reviewer failure aborts settlement; only an explicit rejection requests correction. */ private reviewAnswer; /** Evaluate the caller's halt predicate, if there is one. */ private shouldStop; private hasRunningAgentTasks; private acceptProviderRejectedImage; private requestFinalResponse; } //# sourceMappingURL=index.d.ts.map