UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

214 lines (213 loc) • 12 kB
import { compactionPassReport, DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact } from "../auto-compaction.js"; import { eventDefaultOn } from "../../prompt-assembly/event-registry.js"; import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js"; import { emitTrace } from "../trace.js"; import { answerSettled } from "./assemble-result.js"; import { errorCodeOf } from "./terminal-account.js"; import { awaitChargeWithSlowDisclosure, discloseUnevaluableWindow } from "./clock-and-limits.js"; import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption, legCompactionSettings, legContextOverlayOption } from "./compaction-call-options.js"; import { compactionHookOptions, recordCompactionReuse, seamCCompactionOptions } from "./compaction-seams.js"; import { compactionRestateOptions } from "./compaction-restate.js"; import { MAX_CONSECUTIVE_COMPACTION_FAILURES } from "./compaction-knobs.js"; import { emitSettled, reconciledEnd } from "./tool-end-body.js"; import { asThrownError, thrownText } from "../thrown-value.js"; export async function runTerminalAdoption(input) { const { spec, queue, prepared, rs, stats, ident, emitCommitted, startedToolCallIds, toolLabels, timeout, compactionBrain, compactionBreaker, windowSafetyOptions, undrainedUserAtEnd, userInputLostAtEnd, final, abortedLive, userInterruptedLive, callerStop, runner, sessions, next } = input; let { threw } = input; const settled = answerSettled(final, userInputLostAtEnd.current || prepared.undeliveredContinuationRef.current.parkedSteers.length > 0 || prepared.workCutRef.continuation || prepared.workCutRef.abortedBeforeTurnEnd); if (threw === undefined && prepared.brainCallGuardrailRef.timedOut !== undefined) { threw = prepared.brainCallGuardrailRef.timedOut; } if (threw === undefined && prepared.gateStopRef.terminal !== undefined) { threw = prepared.gateStopRef.terminal; } if (rs.limits.platformTerminal !== undefined) { threw = rs.limits.platformTerminal; } if (prepared.usageGovernance !== undefined) { try { await awaitChargeWithSlowDisclosure(prepared.usageGovernance.commit(stats.tokens, rs.telemetry.unpricedSpend ? undefined : stats.costMicroUsd, Date.now()), () => runner.deps.onError?.(new Error("the usage ledger's FINAL charge has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on a later flush). A wedged ledger store wedges this teardown, now visibly."), { phase: "config", sessionId: prepared.sessionId })); } catch (flushErr) { runner.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId }); } } const committedPause = prepared.pausedRef.current; const durablyPaused = committedPause !== undefined; const interrupted = !durablyPaused && (threw !== undefined || timeout.fired || rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || abortedLive || final?.stopReason === "aborted"); let orphansClosed = 0; let reconcileComplete = false; if (interrupted) { try { const report = await reconcileInterruptedSession(prepared.session, prepared.toolEffects, undefined, startedToolCallIds); orphansClosed = report.recovered.length; for (const orphan of report.recovered) { emitSettled(prepared.toolOccurrences, { toolCallId: orphan.toolCallId, spelledName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), args: orphan.args }, reconciledEnd(orphan), { push: (f) => queue.push({ ...f, ...ident() }), structured: prepared.structuredProjector, mediaByteCapBytes: runner.deps.mediaByteCapBytes }); emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId); } reconcileComplete = true; } catch (reconcileErr) { runner.deps.onError?.(asThrownError(reconcileErr), { phase: "interrupt-reconcile", sessionId: prepared.sessionId, }); } try { await prepared.harness.flushQueuedSessionWrites(); } catch (flushErr) { runner.deps.onError?.(asThrownError(flushErr), { phase: "interrupt-reconcile", sessionId: prepared.sessionId, }); } } if (interrupted && userInterruptedLive && reconcileComplete && !settled) { try { const entryId = await appendInterruptionMarker(prepared.session, { toolUseInFlight: orphansClosed > 0 }); emitCommitted(entryId, "user"); } catch (markerErr) { runner.deps.onError?.(asThrownError(markerErr), { phase: "interrupt-reconcile", sessionId: prepared.sessionId, }); } } let migratedParked = { steer: 0, followUp: 0 }; let carriedContinuation = 0; if (committedPause !== undefined && runner.deps.checkpointStore !== undefined) { const parkToken = committedPause.cause.token; const parkScope = committedPause.scope; const store = runner.deps.checkpointStore; const carried = []; const owed = [ ...prepared.undeliveredContinuationRef.current.parkedSteers.map(({ seq: _seq, ...input }) => ({ input })), ...prepared.harness.readParkableUserInputs().map((record) => ({ input: record, live: record })), ]; for (const { input, live } of owed) { try { if (!(await store.setPendingSteer(parkToken, parkScope, input))) break; if (live !== undefined) carried.push(live); else carriedContinuation += 1; } catch (parkErr) { runner.deps.onError?.(asThrownError(parkErr), { phase: "config", sessionId: prepared.sessionId, }); const parkCode = errorCodeOf(parkErr); if (parkCode === "steering.invalid_content" || parkCode === "steering.queue_full") continue; break; } } migratedParked = prepared.harness.dropParkedUserInputs(carried); } const owedUserInput = { held: undrainedUserAtEnd.current, migrated: migratedParked, continuation: { frames: prepared.undeliveredContinuationRef.current.parkedSteers, carried: carriedContinuation } }; if (rs.attach.attachState?.postCompactPending === true && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) { emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.announce_dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() })); } const comp = await finish(spec, prepared, { telemetry: { tracer: rs.telemetry.tracer, taskId: rs.telemetry.taskId }, skipCompaction: durablyPaused || (abortedLive && settled) || callerStop.signal.aborted || compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES, callerStop, minTokens: rs.counters.compactionFloor, brain: compactionBrain, windowSafety: windowSafetyOptions(prepared.harness.getModel()), passReport: compactionPassReport(queue, ident), onCompactionOutcome: (outcome, reason, end) => queue.push({ type: "compaction_outcome", outcome, trigger: "auto", reason, ...end, ...ident() }), }, runner, sessions); return await next({ threw, comp, answerSettled: settled, owedUserInput }); } async function finish(spec, prepared, opts, runner, sessions) { let comp; if (!opts?.skipCompaction) { try { const finishComp = await maybeCompact({ session: prepared.session, promptIdentity: prepared.promptIdentity, ...centerAdoptionOption(prepared), model: prepared.harness.getModel(), ...legContextOverlayOption(prepared, runner), compactionModel: prepared.compModel, ...forkContextOption(prepared, false), brain: opts?.brain ?? runner.deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, thinking: prepared.thinking, settings: legCompactionSettings(spec, legContextOverlayOption(prepared, runner).contextOverlay), customInstructions: spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS, signal: opts.callerStop.signal, manualCancelSignal: opts.callerStop.signal, minTokens: opts?.minTokens, overheadTokens: prepared.promptOverheadTokens, ...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}), onInputTruncated: emitInputTruncated(opts?.telemetry?.tracer ?? spec.tracer ?? runner.deps.tracer, opts?.telemetry?.taskId ?? spec.taskId ?? prepared.sessionId), ...(opts?.windowSafety ?? {}), workingFileAttachments: buildWorkingFileAttachments(spec, prepared), ...contextInstructionFilesOption(prepared), ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}), ...seamCCompactionOptions(runner, prepared), ...compactionRestateOptions(prepared), ...compactionHookOptions(runner, prepared, "auto", opts.callerStop.signal), onStarted: opts.passReport.onStarted, }); recordCompactionReuse(runner, prepared, finishComp); if (finishComp.unevaluableWindow) { discloseUnevaluableWindow(finishComp.unevaluableWindow, prepared, opts?.telemetry?.tracer ?? spec.tracer ?? runner.deps.tracer, opts?.telemetry?.taskId ?? spec.taskId ?? prepared.sessionId, runner.deps.onError); } comp = finishComp; } catch (err) { if (isCompactionManualCancel(err)) { emitTrace(opts?.telemetry?.tracer ?? spec.tracer ?? runner.deps.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: opts?.telemetry?.taskId ?? spec.taskId ?? prepared.sessionId, reason: "cancelled", ts: Date.now(), })); opts?.onCompactionOutcome?.("mooted", "cancelled", opts.passReport.end); } else { const failMsg = thrownText(err); const failReason = failMsg.length > 512 ? `${failMsg.slice(0, 512)}…` : failMsg; emitTrace(opts?.telemetry?.tracer ?? spec.tracer ?? runner.deps.tracer, () => ({ kind: "compaction.failed", version: 1, taskId: opts?.telemetry?.taskId ?? spec.taskId ?? prepared.sessionId, trigger: "auto", reason: failReason, ts: Date.now(), })); opts?.onCompactionOutcome?.("failed", failReason, opts.passReport.end); runner.deps.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId }); } } } try { await sessions.touch(prepared.sessionId); } catch (err) { runner.deps.onError?.(err, { phase: "config", sessionId: prepared.sessionId }); } try { await prepared.mcp.dispose(); } catch (err) { runner.deps.onError?.(err, { phase: "mcp", sessionId: prepared.sessionId }); } if (prepared.a2a !== undefined) { try { await prepared.a2a.dispose(); } catch (err) { runner.deps.onError?.(err, { phase: "a2a", sessionId: prepared.sessionId }); } } return comp; }