@sema-agent/core
Version:
Stateless, task-oriented AI agent core
214 lines (213 loc) • 12 kB
JavaScript
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;
}