@sema-agent/core 5.61.0 → 5.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/dist/agents/subagent.d.ts +12 -2
- package/dist/agents/subagent.js +3 -2
- package/dist/brain/open-responses.js +8 -3
- package/dist/brain/openai.js +4 -4
- package/dist/brain/stream-engine.d.ts +13 -2
- package/dist/brain/stream-engine.js +3 -3
- package/dist/core/auto-compaction.d.ts +6 -4
- package/dist/core/auto-compaction.js +3 -0
- package/dist/core/auto-mode-prompt-assets.js +1 -1
- package/dist/core/checkpoint-store.d.ts +36 -4
- package/dist/core/checkpoint-store.js +1 -0
- package/dist/core/context-edit.d.ts +36 -29
- package/dist/core/context-edit.js +3 -3
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +86 -4
- package/dist/core/hooks.js +3 -3
- package/dist/core/memory-engine/engine.d.ts +11 -0
- package/dist/core/memory-engine/engine.js +29 -3
- package/dist/core/memory-engine/index.d.ts +1 -1
- package/dist/core/memory-engine/origin-clearance.d.ts +28 -0
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/pricing.d.ts +24 -0
- package/dist/core/pricing.js +18 -0
- package/dist/core/runner/prepare-config-doors.d.ts +36 -2
- package/dist/core/runner/prepare-config-doors.js +66 -8
- package/dist/core/runner/prepare-task.d.ts +113 -12
- package/dist/core/runner/prepare-task.js +239 -131
- package/dist/core/runner/runtask.d.ts +7 -0
- package/dist/core/runner/runtask.js +325 -95
- package/dist/core/runner/turn-attachments.d.ts +137 -5
- package/dist/core/runner/turn-attachments.js +25 -2
- package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
- package/dist/core/tool-errors.d.ts +2 -1
- package/dist/core/tool-policy.d.ts +27 -0
- package/dist/core/trace.d.ts +5 -4
- package/dist/core/types.d.ts +163 -29
- package/dist/core/untrusted-text.d.ts +5 -4
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/usage-window-store.d.ts +109 -8
- package/dist/core/usage-window-store.js +79 -12
- package/dist/engine/harness/agent-harness.js +20 -5
- package/dist/engine/harness/types.d.ts +38 -0
- package/dist/engine/loop/agent-loop.js +20 -1
- package/dist/engine/loop/types.d.ts +41 -1
- package/dist/index.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +2 -2
- package/dist/orchestration/workflow-types.d.ts +48 -1
- package/dist/orchestration/workflow-types.js +12 -4
- package/dist/orchestration/workflow.d.ts +14 -3
- package/dist/orchestration/workflow.js +44 -19
- package/dist/prompt-assembly/event-registry.js +2 -0
- package/dist/prompts/default.js +1 -1
- package/dist/server/http.d.ts +1 -1
- package/dist/stores/file/usage-window-store.d.ts +1 -1
- package/dist/stores/file/usage-window-store.js +27 -6
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/scheduler-tools.js +9 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +3 -1
|
@@ -15,7 +15,7 @@ import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
|
|
|
15
15
|
import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill, sanitizeCompactionSettings } from "../auto-compaction.js";
|
|
16
16
|
import { ASK_USER_QUESTION_TOOL_NAME, canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace, markBoundOnlyQuestionFace } from "../ask-question.js";
|
|
17
17
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
18
|
-
import { computeCostMicroUsd, modelCostToPricing } from "../pricing.js";
|
|
18
|
+
import { computeCostMicroUsd, isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
|
|
19
19
|
import { emitTrace } from "../trace.js";
|
|
20
20
|
import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "../permission-rule-org.js";
|
|
21
21
|
import { emitTaskOutcome } from "../task-outcome.js";
|
|
@@ -36,13 +36,13 @@ import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
|
|
|
36
36
|
import { assembleResult, errorCodeOf } from "./assemble-result.js";
|
|
37
37
|
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, attachmentEnvelopeTags, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
|
|
38
38
|
import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
|
|
39
|
-
import { gatedCallIdOf, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
|
|
39
|
+
import { effectiveDelegationFacts, gatedCallIdOf, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
|
|
40
40
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
41
41
|
import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
|
|
42
42
|
import { hasVerifiableStructureSignal } from "./grounding-signal.js";
|
|
43
43
|
import { hasDestroy, isIsolated } from "../remote-env.js";
|
|
44
44
|
import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
|
|
45
|
-
import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
|
|
45
|
+
import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, mintHookInvocationIdentity, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
|
|
46
46
|
import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
|
|
47
47
|
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
48
48
|
import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
|
|
@@ -395,7 +395,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
395
395
|
const governance = prepared.usageGovernance;
|
|
396
396
|
try {
|
|
397
397
|
const at = Date.now();
|
|
398
|
-
await awaitChargeWithSlowDisclosure(governance.commit(stats.tokens, at), () => runnerHooks.onError?.(new Error("the usage ledger CHARGE has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on the end-of-task flush). A wedged ledger store wedges this boundary, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
|
|
398
|
+
await awaitChargeWithSlowDisclosure(governance.commit(stats.tokens, rs.telemetry.unpricedSpend ? undefined : stats.costMicroUsd, at), () => runnerHooks.onError?.(new Error("the usage ledger CHARGE has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on the end-of-task flush). A wedged ledger store wedges this boundary, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
|
|
399
399
|
const windowRead = governance.check(at);
|
|
400
400
|
let answer;
|
|
401
401
|
if (ledgerReadDeadline !== undefined) {
|
|
@@ -606,6 +606,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
606
606
|
const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
|
|
607
607
|
const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
|
|
608
608
|
const budgetUsdOn = rs.attach.attachmentsCfg?.budgetUsd === true && rs.budget.maxCostMicroUsd !== undefined && rs.turn.lastTurnHadToolCalls;
|
|
609
|
+
const totalTokensOn = rs.attach.attachmentsCfg?.totalTokensReminder === true && rs.turn.lastTurnHadToolCalls;
|
|
609
610
|
const toolSearchReminderOn = rs.attach.attachmentsCfg?.toolSearchReminder === true && prepared.deferredToolNames !== undefined;
|
|
610
611
|
const undiscoveredTools = toolSearchReminderOn
|
|
611
612
|
? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
|
|
@@ -623,6 +624,10 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
623
624
|
changedFiles: changedFilesOn,
|
|
624
625
|
planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
|
|
625
626
|
budgetUsd: budgetUsdOn,
|
|
627
|
+
totalTokensReminder: totalTokensOn,
|
|
628
|
+
...(rs.attach.attachmentsCfg?.totalTokensReminderMode !== undefined
|
|
629
|
+
? { totalTokensReminderMode: rs.attach.attachmentsCfg.totalTokensReminderMode }
|
|
630
|
+
: {}),
|
|
626
631
|
backgroundTasks: backgroundTasksOn,
|
|
627
632
|
toolsDelta: toolsDeltaOn,
|
|
628
633
|
agentListing: rs.attach.agentListingOn,
|
|
@@ -636,6 +641,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
636
641
|
...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
|
|
637
642
|
? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
|
|
638
643
|
: {}),
|
|
644
|
+
...(totalTokensOn && rs.budget.maxTokensWindow !== undefined
|
|
645
|
+
? { totalTokens: { used: stats.tokens, total: rs.budget.maxTokensWindow } }
|
|
646
|
+
: {}),
|
|
639
647
|
...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
|
|
640
648
|
...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
|
|
641
649
|
...(prepared.toolMaterializeStatic ? { newToolsStaticFace: true } : {}),
|
|
@@ -1131,6 +1139,9 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1131
1139
|
rs.turn.firstTokenAt = Date.now();
|
|
1132
1140
|
pushContent({ type: "text_delta", delta: ev.delta, ...ident() });
|
|
1133
1141
|
}
|
|
1142
|
+
else if (ev.type === "text_end" && typeof ev.content === "string" && ev.content.length !== 0) {
|
|
1143
|
+
pushContent({ type: "text_end", content: ev.content, ...ident() });
|
|
1144
|
+
}
|
|
1134
1145
|
else if (ev.type === "thinking_delta" && ev.delta) {
|
|
1135
1146
|
pushContent({ type: "reasoning_delta", delta: ev.delta, ...ident() });
|
|
1136
1147
|
}
|
|
@@ -1892,6 +1903,47 @@ export class Runner {
|
|
|
1892
1903
|
return;
|
|
1893
1904
|
}
|
|
1894
1905
|
}
|
|
1906
|
+
if (resultValue !== undefined || h.loop.ended)
|
|
1907
|
+
throw steeringError("the task is no longer running");
|
|
1908
|
+
if (mintsAFrame) {
|
|
1909
|
+
const screen = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
|
|
1910
|
+
if (screen !== undefined) {
|
|
1911
|
+
let decision;
|
|
1912
|
+
try {
|
|
1913
|
+
const seat = await runHookSeat("userPromptSubmit", { timeoutMs: h.hookTimeoutMs, signal: h.abortController.signal, abortEnds: true }, (sig) => screen(text, { identity: h.hookIdentity, signal: sig, source: "steer", inputId: effectiveInputId, ...(actor !== undefined ? { actor: snapshotActorAssertion(actor) } : {}) }));
|
|
1914
|
+
if (seat.expired) {
|
|
1915
|
+
if (seat.cause === "timeout") {
|
|
1916
|
+
try {
|
|
1917
|
+
this.deps.onError?.(hookSeatExpiredError("userPromptSubmit", h.hookTimeoutMs, seat.cause, "the steering input was NOT accepted (fail-closed) and the caller was refused typed"), { phase: "hook", sessionId: h.sessionId });
|
|
1918
|
+
}
|
|
1919
|
+
catch {
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
throw steeringError(seat.cause === "timeout"
|
|
1923
|
+
? `the deployment's userPromptSubmit hook did not answer within its ${h.hookTimeoutMs}ms bound while screening this steering input; the input was NOT accepted (fail-closed)`
|
|
1924
|
+
: `the task was cancelled while the deployment's userPromptSubmit hook was still screening this steering input; the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook");
|
|
1925
|
+
}
|
|
1926
|
+
decision = seat.value;
|
|
1927
|
+
}
|
|
1928
|
+
catch (hookErr) {
|
|
1929
|
+
if (hookErr instanceof Error && hookErr.code === "steering.blocked_by_hook")
|
|
1930
|
+
throw hookErr;
|
|
1931
|
+
const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
|
|
1932
|
+
try {
|
|
1933
|
+
this.deps.onError?.(err, { phase: "hook", sessionId: h.sessionId });
|
|
1934
|
+
}
|
|
1935
|
+
catch {
|
|
1936
|
+
}
|
|
1937
|
+
throw steeringError(`the deployment's userPromptSubmit hook crashed while screening this steering input (${inlineUntrusted(err.message)}); the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook");
|
|
1938
|
+
}
|
|
1939
|
+
if (decision?.block !== undefined && decision.block !== "") {
|
|
1940
|
+
throw steeringError(`the deployment's userPromptSubmit hook blocked this steering input: ${inlineUntrusted(decision.block)}`, "steering.blocked_by_hook");
|
|
1941
|
+
}
|
|
1942
|
+
if (decision?.additionalContext !== undefined && decision.additionalContext !== "") {
|
|
1943
|
+
payload = `${formatHookFeedback(decision.additionalContext, h.reminderMark)}\n\n${payload}`;
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1895
1947
|
const injectFramed = async () => {
|
|
1896
1948
|
const noteOptions = { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) };
|
|
1897
1949
|
if (priority === "later") {
|
|
@@ -2236,7 +2288,7 @@ export class Runner {
|
|
|
2236
2288
|
}
|
|
2237
2289
|
}
|
|
2238
2290
|
const loopLatch = { ended: false, userInterrupted: false };
|
|
2239
|
-
onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId });
|
|
2291
|
+
onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId, hookTimeoutMs: prepared.hookTimeoutMs, hookIdentity: prepared.hookIdentity });
|
|
2240
2292
|
const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
|
|
2241
2293
|
prepared.liveSpendRef.get = () => ({ costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) });
|
|
2242
2294
|
if (resume &&
|
|
@@ -2258,7 +2310,7 @@ export class Runner {
|
|
|
2258
2310
|
const rs = createRunState();
|
|
2259
2311
|
rs.telemetry.cacheFamily = cacheFamilyOf(prepared.model);
|
|
2260
2312
|
rs.telemetry.pricing = this.deps.pricing?.[prepared.model.id] ?? modelCostToPricing(prepared.model.cost);
|
|
2261
|
-
rs.telemetry.pricingConfigured =
|
|
2313
|
+
rs.telemetry.pricingConfigured = isModelPriced(prepared.model, this.deps.pricing);
|
|
2262
2314
|
if (spec.limits?.degrade) {
|
|
2263
2315
|
try {
|
|
2264
2316
|
rs.degrade.degradeToModel = resolveModel(spec.limits.degrade.to, modelCatalog);
|
|
@@ -2298,6 +2350,12 @@ export class Runner {
|
|
|
2298
2350
|
catch (err) {
|
|
2299
2351
|
discloseNoteTaskRunFailure(err);
|
|
2300
2352
|
}
|
|
2353
|
+
const noteUnevaluablePriceTable = (p) => {
|
|
2354
|
+
if (prepared.usageGovernance?.governsCost !== true)
|
|
2355
|
+
return;
|
|
2356
|
+
if (malformedPricingField(p) !== undefined)
|
|
2357
|
+
rs.telemetry.unpricedSpend = true;
|
|
2358
|
+
};
|
|
2301
2359
|
rs.degrade.recordDegraded = (info, toModel) => {
|
|
2302
2360
|
if (rs.degrade.degraded !== undefined)
|
|
2303
2361
|
return;
|
|
@@ -2312,13 +2370,15 @@ export class Runner {
|
|
|
2312
2370
|
}
|
|
2313
2371
|
if (m) {
|
|
2314
2372
|
rs.telemetry.pricing = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
|
|
2315
|
-
rs.telemetry.pricingConfigured = this.deps.pricing
|
|
2373
|
+
rs.telemetry.pricingConfigured = isModelPriced(m, this.deps.pricing);
|
|
2374
|
+
noteUnevaluablePriceTable(rs.telemetry.pricing);
|
|
2316
2375
|
rs.telemetry.cacheFamily = cacheFamilyOf(m);
|
|
2317
2376
|
}
|
|
2318
2377
|
else {
|
|
2319
2378
|
if (this.deps.pricing?.[info.to]) {
|
|
2320
2379
|
rs.telemetry.pricing = this.deps.pricing[info.to];
|
|
2321
2380
|
rs.telemetry.pricingConfigured = true;
|
|
2381
|
+
noteUnevaluablePriceTable(rs.telemetry.pricing);
|
|
2322
2382
|
}
|
|
2323
2383
|
else {
|
|
2324
2384
|
rs.telemetry.pricingConfigured = false;
|
|
@@ -2737,9 +2797,10 @@ export class Runner {
|
|
|
2737
2797
|
return;
|
|
2738
2798
|
const fam = cacheFamilyOf(m);
|
|
2739
2799
|
const price = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
|
|
2740
|
-
const priced = this.deps.pricing
|
|
2800
|
+
const priced = isModelPriced(m, this.deps.pricing);
|
|
2741
2801
|
if (!priced)
|
|
2742
2802
|
rs.telemetry.unpricedSpend = true;
|
|
2803
|
+
noteUnevaluablePriceTable(price);
|
|
2743
2804
|
const { totalInputTokens, uncachedInputTokens, costMicroUsd } = usageCostMicroUsd(fam, u, price);
|
|
2744
2805
|
stats.tokens += u.totalTokens || 0;
|
|
2745
2806
|
stats.promptTokens += uncachedInputTokens;
|
|
@@ -2969,6 +3030,105 @@ export class Runner {
|
|
|
2969
3030
|
return messages;
|
|
2970
3031
|
});
|
|
2971
3032
|
}
|
|
3033
|
+
const runForcedCompactionPass = async (lane, turnSignal) => {
|
|
3034
|
+
if (!(spec.compaction?.enabled ?? true))
|
|
3035
|
+
return false;
|
|
3036
|
+
if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES)
|
|
3037
|
+
return false;
|
|
3038
|
+
const passSignal = turnSignal !== undefined ? AbortSignal.any([prepared.abortController.signal, turnSignal]) : prepared.abortController.signal;
|
|
3039
|
+
try {
|
|
3040
|
+
const comp = await maybeCompact({
|
|
3041
|
+
session: prepared.session,
|
|
3042
|
+
epochDeclaredSections: prepared.epochDeclaredSections,
|
|
3043
|
+
...centerAdoptionOption(prepared),
|
|
3044
|
+
model: prepared.harness.getModel(),
|
|
3045
|
+
compactionModel: prepared.compModel,
|
|
3046
|
+
...forkContextOption(prepared, true),
|
|
3047
|
+
brain: compactionBrain,
|
|
3048
|
+
getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
|
|
3049
|
+
thinking: prepared.thinking,
|
|
3050
|
+
settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
|
|
3051
|
+
customInstructions: spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
|
|
3052
|
+
signal: passSignal,
|
|
3053
|
+
minTokens: 0,
|
|
3054
|
+
force: true,
|
|
3055
|
+
overheadTokens: prepared.promptOverheadTokens,
|
|
3056
|
+
...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
|
|
3057
|
+
onInputTruncated: emitInputTruncated(rs.telemetry.tracer, rs.telemetry.taskId),
|
|
3058
|
+
workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
|
|
3059
|
+
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
3060
|
+
...this.seamCCompactionOptions(prepared),
|
|
3061
|
+
...gitRestateOption(prepared),
|
|
3062
|
+
...windowSafetyOptions(prepared.harness.getModel()),
|
|
3063
|
+
...this.compactionHookOptions(spec, prepared.sessionId, "forced", prepared.hookIdentity, { timeoutMs: prepared.hookTimeoutMs, signal: passSignal }),
|
|
3064
|
+
});
|
|
3065
|
+
if (comp.compacted) {
|
|
3066
|
+
compactionBreaker.failures = 0;
|
|
3067
|
+
this.recordCompactionReuse(prepared, comp);
|
|
3068
|
+
if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
|
|
3069
|
+
const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
|
|
3070
|
+
rs.counters.compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
|
|
3071
|
+
}
|
|
3072
|
+
queue.push({
|
|
3073
|
+
type: "compacted",
|
|
3074
|
+
trigger: "forced",
|
|
3075
|
+
tokensBefore: comp.tokensBefore ?? 0,
|
|
3076
|
+
...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
|
|
3077
|
+
...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
|
|
3078
|
+
...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
|
|
3079
|
+
...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
|
|
3080
|
+
...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
|
|
3081
|
+
...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
|
|
3082
|
+
...(comp.modelFallback ? { modelFallback: true } : {}),
|
|
3083
|
+
...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
|
|
3084
|
+
...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
|
|
3085
|
+
...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
|
|
3086
|
+
...ident(),
|
|
3087
|
+
});
|
|
3088
|
+
if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
|
|
3089
|
+
const pd = comp.phaseDurations;
|
|
3090
|
+
const pdDur = comp.durationMs;
|
|
3091
|
+
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
|
|
3092
|
+
}
|
|
3093
|
+
prepared.cacheBreakDetector?.notifyCompaction();
|
|
3094
|
+
if (rs.attach.attachState !== undefined) {
|
|
3095
|
+
rs.attach.attachState.postCompactPending = true;
|
|
3096
|
+
rebaseCadenceWindows(rs.attach.attachState, rs.counters.cadenceTurns);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
else {
|
|
3100
|
+
if (comp.noop) {
|
|
3101
|
+
compactionBreaker.failures = 0;
|
|
3102
|
+
}
|
|
3103
|
+
else {
|
|
3104
|
+
compactionBreaker.failures += 1;
|
|
3105
|
+
}
|
|
3106
|
+
const declineReason = lane === "rejection" ? "prompt-too-long recovery pass did not land" : "guard-chain forced compaction pass did not land";
|
|
3107
|
+
if (!comp.noop) {
|
|
3108
|
+
this.deps.onError?.(new Error(declineReason), { phase: "compaction", sessionId: prepared.sessionId });
|
|
3109
|
+
}
|
|
3110
|
+
queue.push({
|
|
3111
|
+
type: "compaction_outcome",
|
|
3112
|
+
outcome: comp.noop ? "noop" : "failed",
|
|
3113
|
+
trigger: "forced",
|
|
3114
|
+
reason: declineReason,
|
|
3115
|
+
...ident(),
|
|
3116
|
+
});
|
|
3117
|
+
}
|
|
3118
|
+
return comp.compacted === true;
|
|
3119
|
+
}
|
|
3120
|
+
catch (err) {
|
|
3121
|
+
if (turnSignal?.aborted === true && !prepared.abortController.signal.aborted) {
|
|
3122
|
+
queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: "guard-chain forced compaction cut short by a turn interrupt", ...ident() });
|
|
3123
|
+
return false;
|
|
3124
|
+
}
|
|
3125
|
+
compactionBreaker.failures += 1;
|
|
3126
|
+
this.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "compaction", sessionId: prepared.sessionId });
|
|
3127
|
+
const msg = String(err instanceof Error ? err.message : err);
|
|
3128
|
+
queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: msg.length > 512 ? `${msg.slice(0, 512)}…` : msg, ...ident() });
|
|
3129
|
+
return false;
|
|
3130
|
+
}
|
|
3131
|
+
};
|
|
2972
3132
|
prepared.harness.setLoopRecovery({
|
|
2973
3133
|
truncatedOutput: {},
|
|
2974
3134
|
malformedToolUse: {},
|
|
@@ -3014,85 +3174,23 @@ export class Runner {
|
|
|
3014
3174
|
return true;
|
|
3015
3175
|
}
|
|
3016
3176
|
}
|
|
3017
|
-
|
|
3018
|
-
return false;
|
|
3019
|
-
if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES)
|
|
3020
|
-
return false;
|
|
3021
|
-
try {
|
|
3022
|
-
const comp = await maybeCompact({
|
|
3023
|
-
session: prepared.session,
|
|
3024
|
-
epochDeclaredSections: prepared.epochDeclaredSections,
|
|
3025
|
-
...centerAdoptionOption(prepared),
|
|
3026
|
-
model: prepared.harness.getModel(),
|
|
3027
|
-
compactionModel: prepared.compModel,
|
|
3028
|
-
...forkContextOption(prepared, true),
|
|
3029
|
-
brain: compactionBrain,
|
|
3030
|
-
getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
|
|
3031
|
-
thinking: prepared.thinking,
|
|
3032
|
-
settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
|
|
3033
|
-
customInstructions: spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
|
|
3034
|
-
signal: prepared.abortController.signal,
|
|
3035
|
-
minTokens: 0,
|
|
3036
|
-
force: true,
|
|
3037
|
-
overheadTokens: prepared.promptOverheadTokens,
|
|
3038
|
-
...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
|
|
3039
|
-
onInputTruncated: emitInputTruncated(rs.telemetry.tracer, rs.telemetry.taskId),
|
|
3040
|
-
workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
|
|
3041
|
-
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
3042
|
-
...this.seamCCompactionOptions(prepared),
|
|
3043
|
-
...gitRestateOption(prepared),
|
|
3044
|
-
...windowSafetyOptions(prepared.harness.getModel()),
|
|
3045
|
-
...this.compactionHookOptions(spec, prepared.sessionId, "forced", prepared.hookIdentity, { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }),
|
|
3046
|
-
});
|
|
3047
|
-
if (comp.compacted) {
|
|
3048
|
-
compactionBreaker.failures = 0;
|
|
3049
|
-
this.recordCompactionReuse(prepared, comp);
|
|
3050
|
-
if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
|
|
3051
|
-
const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
|
|
3052
|
-
rs.counters.compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
|
|
3053
|
-
}
|
|
3054
|
-
queue.push({
|
|
3055
|
-
type: "compacted",
|
|
3056
|
-
trigger: "forced",
|
|
3057
|
-
tokensBefore: comp.tokensBefore ?? 0,
|
|
3058
|
-
...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
|
|
3059
|
-
...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
|
|
3060
|
-
...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
|
|
3061
|
-
...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
|
|
3062
|
-
...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
|
|
3063
|
-
...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
|
|
3064
|
-
...(comp.modelFallback ? { modelFallback: true } : {}),
|
|
3065
|
-
...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
|
|
3066
|
-
...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
|
|
3067
|
-
...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
|
|
3068
|
-
...ident(),
|
|
3069
|
-
});
|
|
3070
|
-
if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
|
|
3071
|
-
const pd = comp.phaseDurations;
|
|
3072
|
-
const pdDur = comp.durationMs;
|
|
3073
|
-
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
|
|
3074
|
-
}
|
|
3075
|
-
prepared.cacheBreakDetector?.notifyCompaction();
|
|
3076
|
-
if (rs.attach.attachState !== undefined) {
|
|
3077
|
-
rs.attach.attachState.postCompactPending = true;
|
|
3078
|
-
rebaseCadenceWindows(rs.attach.attachState, rs.counters.cadenceTurns);
|
|
3079
|
-
}
|
|
3080
|
-
}
|
|
3081
|
-
else {
|
|
3082
|
-
compactionBreaker.failures += 1;
|
|
3083
|
-
queue.push({ type: "compaction_outcome", outcome: comp.noop ? "noop" : "failed", trigger: "forced", reason: "prompt-too-long recovery pass did not land", ...ident() });
|
|
3084
|
-
}
|
|
3085
|
-
return comp.compacted === true;
|
|
3086
|
-
}
|
|
3087
|
-
catch (err) {
|
|
3088
|
-
compactionBreaker.failures += 1;
|
|
3089
|
-
const msg = String(err instanceof Error ? err.message : err);
|
|
3090
|
-
queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: msg.length > 512 ? `${msg.slice(0, 512)}…` : msg, ...ident() });
|
|
3091
|
-
return false;
|
|
3092
|
-
}
|
|
3177
|
+
return runForcedCompactionPass("rejection");
|
|
3093
3178
|
},
|
|
3094
3179
|
},
|
|
3095
3180
|
});
|
|
3181
|
+
prepared.microCompact.inTurnCompactionRef.current = async (turnSignal, anchoredEstimate) => {
|
|
3182
|
+
if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined)
|
|
3183
|
+
return false;
|
|
3184
|
+
if (turnSignal?.aborted === true)
|
|
3185
|
+
return false;
|
|
3186
|
+
if (anchoredEstimate !== undefined && rs.counters.compactionFloor > 0 && anchoredEstimate < rs.counters.compactionFloor) {
|
|
3187
|
+
const floor = rs.counters.compactionFloor;
|
|
3188
|
+
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.suppressed", version: 1, taskId: rs.telemetry.taskId, estTokens: anchoredEstimate, floor, ts: Date.now() }));
|
|
3189
|
+
queue.push({ type: "compaction_outcome", outcome: "suppressed", trigger: "forced", ...ident() });
|
|
3190
|
+
return false;
|
|
3191
|
+
}
|
|
3192
|
+
return runForcedCompactionPass("guard", turnSignal);
|
|
3193
|
+
};
|
|
3096
3194
|
const rapidRefill = createRapidRefillState();
|
|
3097
3195
|
const drainManualCompact = (outcome) => {
|
|
3098
3196
|
manualCompactRef.requested = false;
|
|
@@ -3312,6 +3410,72 @@ export class Runner {
|
|
|
3312
3410
|
...(resume.wakeMessage !== undefined ? [{ entry: resume.wakeMessage, source: "wake" }] : []),
|
|
3313
3411
|
];
|
|
3314
3412
|
for (const { entry: steer, source } of resumeFrames) {
|
|
3413
|
+
if (source === "wake" && resume.wakeMessageHookContext !== undefined) {
|
|
3414
|
+
const hookStart = continuation.length;
|
|
3415
|
+
continuation += "\n\n" + formatHookFeedback(resume.wakeMessageHookContext, prepared.reminderMark);
|
|
3416
|
+
engineSegments.push({ start: hookStart, end: continuation.length });
|
|
3417
|
+
}
|
|
3418
|
+
if (source === "steer") {
|
|
3419
|
+
const parkedScreen = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
|
|
3420
|
+
if (parkedScreen !== undefined) {
|
|
3421
|
+
let outcome;
|
|
3422
|
+
try {
|
|
3423
|
+
const seat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => parkedScreen(steer.text, {
|
|
3424
|
+
identity: prepared.hookIdentity,
|
|
3425
|
+
signal: sig,
|
|
3426
|
+
source: "parked_redelivery",
|
|
3427
|
+
...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
|
|
3428
|
+
...(steer.actor !== undefined ? { actor: snapshotActorAssertion(steer.actor) } : {}),
|
|
3429
|
+
}));
|
|
3430
|
+
outcome = seat.expired
|
|
3431
|
+
? { blocked: seat.cause === "timeout" ? `the screen did not answer within its ${prepared.hookTimeoutMs}ms bound (fail-closed)` : "the run was cancelled while the screen was still deciding (fail-closed)" }
|
|
3432
|
+
: seat.value?.block !== undefined && seat.value.block !== ""
|
|
3433
|
+
? { blocked: inlineUntrusted(seat.value.block) }
|
|
3434
|
+
: { ...(seat.value?.additionalContext !== undefined && seat.value.additionalContext !== "" ? { context: seat.value.additionalContext } : {}) };
|
|
3435
|
+
}
|
|
3436
|
+
catch (hookErr) {
|
|
3437
|
+
const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
|
|
3438
|
+
try {
|
|
3439
|
+
this.deps.onError?.(err, { phase: "hook", sessionId: prepared.sessionId });
|
|
3440
|
+
}
|
|
3441
|
+
catch {
|
|
3442
|
+
}
|
|
3443
|
+
outcome = { blocked: `the screen crashed (${inlineUntrusted(err.message)}; fail-closed)` };
|
|
3444
|
+
}
|
|
3445
|
+
if ("blocked" in outcome) {
|
|
3446
|
+
deliverEngineNotice(this.deps.onNotice, {
|
|
3447
|
+
code: "steering.parked_input_blocked",
|
|
3448
|
+
message: "a parked steering input was withheld by the deployment's userPromptSubmit screen when this resume redelivered it: " +
|
|
3449
|
+
`${outcome.blocked}. The parked row was consumed with the checkpoint; the instruction never reached the model — ` +
|
|
3450
|
+
"re-issue it if it still applies.",
|
|
3451
|
+
detail: {
|
|
3452
|
+
...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
|
|
3453
|
+
sessionId: prepared.sessionId,
|
|
3454
|
+
...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
|
|
3455
|
+
},
|
|
3456
|
+
});
|
|
3457
|
+
queue.push({
|
|
3458
|
+
...buildHumanInputEvent({
|
|
3459
|
+
carrier: source,
|
|
3460
|
+
source,
|
|
3461
|
+
delivery: "blocked",
|
|
3462
|
+
sessionSeq: nextHumanInputSeq(prepared.harness),
|
|
3463
|
+
...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
|
|
3464
|
+
...(steer.actor !== undefined ? { actor: steer.actor } : {}),
|
|
3465
|
+
...(steer.actor?.issuer !== undefined ? { issuer: steer.actor.issuer } : {}),
|
|
3466
|
+
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
3467
|
+
}),
|
|
3468
|
+
...ident(),
|
|
3469
|
+
});
|
|
3470
|
+
continue;
|
|
3471
|
+
}
|
|
3472
|
+
if ("context" in outcome && outcome.context !== undefined) {
|
|
3473
|
+
const hookStart = continuation.length;
|
|
3474
|
+
continuation += "\n\n" + formatHookFeedback(outcome.context, prepared.reminderMark);
|
|
3475
|
+
engineSegments.push({ start: hookStart, end: continuation.length });
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3315
3479
|
const start = continuation.length;
|
|
3316
3480
|
const projected = projectHumanInput({ text: steer.text, actor: steer.actor, source });
|
|
3317
3481
|
continuation +=
|
|
@@ -3362,7 +3526,7 @@ export class Runner {
|
|
|
3362
3526
|
const userPromptSubmit = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
|
|
3363
3527
|
if (userPromptSubmit) {
|
|
3364
3528
|
try {
|
|
3365
|
-
const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, signal: sig }));
|
|
3529
|
+
const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, signal: sig, source: "objective", ...(objectiveActor !== undefined ? { actor: snapshotActorAssertion(objectiveActor) } : {}) }));
|
|
3366
3530
|
if (promptSeat.expired) {
|
|
3367
3531
|
if (promptSeat.cause === "timeout") {
|
|
3368
3532
|
try {
|
|
@@ -3586,7 +3750,7 @@ export class Runner {
|
|
|
3586
3750
|
}
|
|
3587
3751
|
if (prepared.usageGovernance !== undefined) {
|
|
3588
3752
|
try {
|
|
3589
|
-
await awaitChargeWithSlowDisclosure(prepared.usageGovernance.commit(stats.tokens, Date.now()), () => this.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 }));
|
|
3753
|
+
await awaitChargeWithSlowDisclosure(prepared.usageGovernance.commit(stats.tokens, rs.telemetry.unpricedSpend ? undefined : stats.costMicroUsd, Date.now()), () => this.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 }));
|
|
3590
3754
|
}
|
|
3591
3755
|
catch (flushErr) {
|
|
3592
3756
|
this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
|
|
@@ -4169,7 +4333,9 @@ export class Runner {
|
|
|
4169
4333
|
}
|
|
4170
4334
|
}
|
|
4171
4335
|
let wakeMessage;
|
|
4336
|
+
let wakeHookContext;
|
|
4172
4337
|
const outcomeGate = outcome.gate;
|
|
4338
|
+
const resumeSignal = taskConfig.signal;
|
|
4173
4339
|
let suppliedMessage;
|
|
4174
4340
|
if (outcomeGate === "wake") {
|
|
4175
4341
|
suppliedMessage = outcome.message;
|
|
@@ -4454,7 +4620,7 @@ export class Runner {
|
|
|
4454
4620
|
"resuming here would run the leg with that ceiling unenforced. Rejected pre-CAS (the checkpoint stays pending): finish it on a worker of the " +
|
|
4455
4621
|
"previous release, or start a fresh task bounded by resourceSuspend.totalTokens / totalBudgetUsd.");
|
|
4456
4622
|
}
|
|
4457
|
-
if (
|
|
4623
|
+
if (resumeSignal?.aborted === true && cp.status === "pending") {
|
|
4458
4624
|
throw new CheckpointError("checkpoint.resume_aborted", "the resume was handed an ALREADY-ABORTED signal — refusing to consume the approval on a leg that cannot run it (the checkpoint stays pending and is resumable with a live signal)");
|
|
4459
4625
|
}
|
|
4460
4626
|
if (cp.state.workspaceHandle !== undefined && this.deps.executionEnvFactory === undefined) {
|
|
@@ -4513,6 +4679,7 @@ export class Runner {
|
|
|
4513
4679
|
throw new CheckpointError("checkpoint.invalid_outcome", "TaskSpec.basePolicyForResumeEdit is administratively locked by this deployment (locked key \"toolPolicy\") — a task-supplied " +
|
|
4514
4680
|
"resume-edit policy is refused pre-CAS (the checkpoint stays pending); remove the field or change the deployment's lock configuration");
|
|
4515
4681
|
}
|
|
4682
|
+
let recheckGovernanceWindow;
|
|
4516
4683
|
{
|
|
4517
4684
|
const rowPrincipal = cp.principal || undefined;
|
|
4518
4685
|
const suppliedPrincipal = taskConfig.principal || undefined;
|
|
@@ -4524,16 +4691,79 @@ export class Runner {
|
|
|
4524
4691
|
const owesDelivery = outcomeGate !== "resource_limit" ||
|
|
4525
4692
|
readPendingSteerQueue(cp.state).length > 0 ||
|
|
4526
4693
|
(cp.state.runningBackgroundTasks?.length ?? 0) > 0;
|
|
4527
|
-
|
|
4694
|
+
const usageWindowStore = this.deps.usageWindowStore;
|
|
4695
|
+
if (owesDelivery && usageWindowStore !== undefined) {
|
|
4528
4696
|
const preCasWindows = resolveUsageWindows(this.deps.usageWindows);
|
|
4529
4697
|
if (preCasWindows !== undefined && preCasWindows.length > 0) {
|
|
4530
4698
|
const ledgerKey = (suppliedPrincipal ?? rowPrincipal) || GLOBAL_USAGE_KEY;
|
|
4531
|
-
const
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4699
|
+
const assertWindowOpen = async () => {
|
|
4700
|
+
const wait = usageRetryAfterMs(await usageWindowStore.read(ledgerKey, preCasWindows, Date.now()), preCasWindows);
|
|
4701
|
+
if (wait !== undefined) {
|
|
4702
|
+
throw new CheckpointError("resume.usage_window_exhausted", `a deployment usage window for ledger key ${JSON.stringify(ledgerKey)} is exhausted, and this checkpoint still owes a delivery a re-mint cannot carry — ` +
|
|
4703
|
+
`refused pre-CAS (nothing consumed, nothing unpinned): the same token and the same decision are redeemable once the window frees, in ${String(wait)}ms`, { retryAfterMs: wait });
|
|
4704
|
+
}
|
|
4705
|
+
};
|
|
4706
|
+
recheckGovernanceWindow = assertWindowOpen;
|
|
4707
|
+
await assertWindowOpen();
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
if (outcomeGate === "wake" && wakeMessage !== undefined) {
|
|
4712
|
+
const resumeScreenHooks = taskConfig.hooks ?? this.deps.hooks;
|
|
4713
|
+
const screen = resumeScreenHooks?.userPromptSubmit;
|
|
4714
|
+
if (screen !== undefined && resumeSignal?.aborted !== true) {
|
|
4715
|
+
const screenedMessage = wakeMessage;
|
|
4716
|
+
const identity = mintHookInvocationIdentity({
|
|
4717
|
+
sessionId: cp.sessionId,
|
|
4718
|
+
taskId: taskConfig.taskId ?? cp.sessionId,
|
|
4719
|
+
legKind: "resume",
|
|
4720
|
+
isDelegatedChild: effectiveDelegationFacts(internals, cp.state.isDelegatedChild).isDelegatedChild,
|
|
4721
|
+
...(internals?.insideFork === true ? { insideFork: true } : {}),
|
|
4722
|
+
...(internals?.agentName !== undefined ? { agentName: internals.agentName } : {}),
|
|
4723
|
+
...(internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId } : {}),
|
|
4724
|
+
});
|
|
4725
|
+
const screenTimeoutMs = resolveHookTimeoutMs(resumeScreenHooks?.timeoutMs, (badErr) => {
|
|
4726
|
+
try {
|
|
4727
|
+
this.deps.onError?.(badErr instanceof Error ? badErr : new Error(String(badErr)), { phase: "hook", sessionId: cp.sessionId });
|
|
4535
4728
|
}
|
|
4729
|
+
catch {
|
|
4730
|
+
}
|
|
4731
|
+
}, resumeScreenHooks);
|
|
4732
|
+
let decision;
|
|
4733
|
+
try {
|
|
4734
|
+
const seat = await runHookSeat("userPromptSubmit", { timeoutMs: screenTimeoutMs, ...(resumeSignal !== undefined ? { signal: resumeSignal } : {}), abortEnds: true }, (sig) => screen(screenedMessage.text, {
|
|
4735
|
+
identity,
|
|
4736
|
+
signal: sig,
|
|
4737
|
+
source: "resume_message",
|
|
4738
|
+
...(screenedMessage.inputId !== undefined ? { inputId: screenedMessage.inputId } : {}),
|
|
4739
|
+
...(screenedMessage.actor !== undefined ? { actor: snapshotActorAssertion(screenedMessage.actor) } : {}),
|
|
4740
|
+
}));
|
|
4741
|
+
if (seat.expired) {
|
|
4742
|
+
throw new CheckpointError("steering.blocked_by_hook", seat.cause === "timeout"
|
|
4743
|
+
? `the deployment's userPromptSubmit hook did not answer within its ${screenTimeoutMs}ms bound while screening this wake message; the resume was refused (fail-closed) and the checkpoint stays pending`
|
|
4744
|
+
: `the resume was cancelled while the deployment's userPromptSubmit hook was still screening this wake message; the checkpoint stays pending`);
|
|
4745
|
+
}
|
|
4746
|
+
decision = seat.value;
|
|
4747
|
+
}
|
|
4748
|
+
catch (hookErr) {
|
|
4749
|
+
if (hookErr instanceof CheckpointError)
|
|
4750
|
+
throw hookErr;
|
|
4751
|
+
const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
|
|
4752
|
+
try {
|
|
4753
|
+
this.deps.onError?.(err, { phase: "hook", sessionId: cp.sessionId });
|
|
4754
|
+
}
|
|
4755
|
+
catch {
|
|
4756
|
+
}
|
|
4757
|
+
throw new CheckpointError("steering.blocked_by_hook", `the deployment's userPromptSubmit hook crashed while screening this wake message (${inlineUntrusted(err.message)}); the resume was refused (fail-closed) and the checkpoint stays pending`);
|
|
4758
|
+
}
|
|
4759
|
+
if (decision?.block !== undefined && decision.block !== "") {
|
|
4760
|
+
throw new CheckpointError("steering.blocked_by_hook", `the deployment's userPromptSubmit hook blocked this wake message: ${inlineUntrusted(decision.block)}`);
|
|
4761
|
+
}
|
|
4762
|
+
if (decision?.additionalContext !== undefined && decision.additionalContext !== "") {
|
|
4763
|
+
wakeHookContext = decision.additionalContext;
|
|
4536
4764
|
}
|
|
4765
|
+
if (recheckGovernanceWindow !== undefined)
|
|
4766
|
+
await recheckGovernanceWindow();
|
|
4537
4767
|
}
|
|
4538
4768
|
}
|
|
4539
4769
|
if (plainPolicyOutcome !== undefined &&
|
|
@@ -4668,10 +4898,10 @@ export class Runner {
|
|
|
4668
4898
|
}
|
|
4669
4899
|
}
|
|
4670
4900
|
: undefined;
|
|
4671
|
-
if (
|
|
4901
|
+
if (resumeSignal?.aborted === true && consumeFlipDone) {
|
|
4672
4902
|
throw new CheckpointError("checkpoint.resume_aborted", "the resume was aborted after its row was already claimed — the checkpoint stays consumed and the caller's rollback settles the outcome-unknown terminal (reopening here would strand a pending approval no redemption path can reach)");
|
|
4673
4903
|
}
|
|
4674
|
-
if (
|
|
4904
|
+
if (resumeSignal?.aborted === true) {
|
|
4675
4905
|
let reopenOutcome = "refused";
|
|
4676
4906
|
if (onEnvRestoreFailed !== undefined) {
|
|
4677
4907
|
try {
|
|
@@ -4688,7 +4918,7 @@ export class Runner {
|
|
|
4688
4918
|
? new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run AND the store refused to reopen the checkpoint — the approval is terminally consumed and the suspended work was not executed; a retry needs a fresh approval")
|
|
4689
4919
|
: new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run and the reopen attempt FAILED IN FLIGHT — the checkpoint's state is unprovable from here: it may already be pending again. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead (the approved action did NOT run either way)");
|
|
4690
4920
|
}
|
|
4691
|
-
return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}) }, resumeInternals);
|
|
4921
|
+
return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}), ...(wakeHookContext !== undefined ? { wakeMessageHookContext: wakeHookContext } : {}) }, resumeInternals);
|
|
4692
4922
|
}
|
|
4693
4923
|
async applyResumeDecision(prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
|
|
4694
4924
|
const { pendingAction } = resume.cp;
|