@sema-agent/core 5.60.1 → 5.62.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 +125 -0
- package/dist/agents/subagent.d.ts +4 -2
- package/dist/agents/subagent.js +9 -9
- 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-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/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +4 -2
- package/dist/core/hooks.d.ts +83 -4
- package/dist/core/hooks.js +3 -3
- package/dist/core/memory-engine/consolidation-driver.d.ts +19 -1
- package/dist/core/memory-engine/consolidation-driver.js +75 -3
- package/dist/core/memory-engine/consolidation.d.ts +52 -5
- package/dist/core/memory-engine/consolidation.js +3 -1
- package/dist/core/memory-engine/distiller.d.ts +89 -1
- package/dist/core/memory-engine/distiller.js +94 -5
- package/dist/core/memory-engine/engine.d.ts +8 -0
- package/dist/core/memory-engine/engine.js +51 -8
- package/dist/core/memory-engine/index.d.ts +1 -1
- package/dist/core/memory-engine/index.js +1 -1
- 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 +34 -0
- package/dist/core/runner/prepare-config-doors.js +55 -0
- package/dist/core/runner/prepare-task.d.ts +52 -10
- package/dist/core/runner/prepare-task.js +77 -42
- package/dist/core/runner/runtask.d.ts +7 -0
- package/dist/core/runner/runtask.js +254 -38
- 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/task-notification.d.ts +50 -23
- package/dist/core/task-notification.js +20 -4
- package/dist/core/tool-errors.d.ts +2 -1
- package/dist/core/tool-policy.d.ts +27 -0
- package/dist/core/types.d.ts +214 -31
- 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.d.ts +58 -2
- package/dist/engine/harness/agent-harness.js +115 -5
- package/dist/engine/loop/agent-loop.js +153 -15
- package/dist/engine/loop/types.d.ts +32 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +9 -4
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow.d.ts +2 -2
- package/dist/prompt-assembly/event-registry.js +2 -0
- 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/monitor.d.ts +3 -3
- package/dist/tools/monitor.js +1 -1
- package/dist/tools/scheduler-tools.js +9 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +7 -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";
|
|
@@ -50,7 +50,7 @@ import { RunnerSharedToolResultStore } from "../tool-result-store.js";
|
|
|
50
50
|
import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
|
|
51
51
|
import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
|
|
52
52
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
53
|
-
import { discloseDroppedPending, isDelegatedAgentTerminal, isSystemInjectionPriority, PendingSessionNotifications, renderTaskNotificationXml, SYSTEM_INJECTION_PRIORITIES, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
53
|
+
import { discloseDroppedPending, isDelegatedAgentTerminal, isSystemInjectionPriority, isTerminalTaskNotification, PendingSessionNotifications, renderTaskNotificationXml, SYSTEM_INJECTION_PRIORITIES, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
54
54
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
55
55
|
import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
|
|
56
56
|
import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
|
|
@@ -78,6 +78,7 @@ function nextHumanInputSeq(key) {
|
|
|
78
78
|
function sameAcceptedSteerInput(a, b) {
|
|
79
79
|
return (a.payload === b.payload &&
|
|
80
80
|
a.trusted === b.trusted &&
|
|
81
|
+
a.priority === b.priority &&
|
|
81
82
|
a.actor?.id === b.actor?.id &&
|
|
82
83
|
a.actor?.hostAsserted === b.actor?.hostAsserted &&
|
|
83
84
|
a.actor?.issuer === b.actor?.issuer);
|
|
@@ -394,7 +395,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
394
395
|
const governance = prepared.usageGovernance;
|
|
395
396
|
try {
|
|
396
397
|
const at = Date.now();
|
|
397
|
-
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 }));
|
|
398
399
|
const windowRead = governance.check(at);
|
|
399
400
|
let answer;
|
|
400
401
|
if (ledgerReadDeadline !== undefined) {
|
|
@@ -605,6 +606,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
605
606
|
const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
|
|
606
607
|
const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
|
|
607
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;
|
|
608
610
|
const toolSearchReminderOn = rs.attach.attachmentsCfg?.toolSearchReminder === true && prepared.deferredToolNames !== undefined;
|
|
609
611
|
const undiscoveredTools = toolSearchReminderOn
|
|
610
612
|
? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
|
|
@@ -622,6 +624,10 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
622
624
|
changedFiles: changedFilesOn,
|
|
623
625
|
planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
|
|
624
626
|
budgetUsd: budgetUsdOn,
|
|
627
|
+
totalTokensReminder: totalTokensOn,
|
|
628
|
+
...(rs.attach.attachmentsCfg?.totalTokensReminderMode !== undefined
|
|
629
|
+
? { totalTokensReminderMode: rs.attach.attachmentsCfg.totalTokensReminderMode }
|
|
630
|
+
: {}),
|
|
625
631
|
backgroundTasks: backgroundTasksOn,
|
|
626
632
|
toolsDelta: toolsDeltaOn,
|
|
627
633
|
agentListing: rs.attach.agentListingOn,
|
|
@@ -635,6 +641,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
635
641
|
...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
|
|
636
642
|
? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
|
|
637
643
|
: {}),
|
|
644
|
+
...(totalTokensOn && rs.budget.maxTokensWindow !== undefined
|
|
645
|
+
? { totalTokens: { used: stats.tokens, total: rs.budget.maxTokensWindow } }
|
|
646
|
+
: {}),
|
|
638
647
|
...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
|
|
639
648
|
...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
|
|
640
649
|
...(prepared.toolMaterializeStatic ? { newToolsStaticFace: true } : {}),
|
|
@@ -1130,6 +1139,9 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1130
1139
|
rs.turn.firstTokenAt = Date.now();
|
|
1131
1140
|
pushContent({ type: "text_delta", delta: ev.delta, ...ident() });
|
|
1132
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
|
+
}
|
|
1133
1145
|
else if (ev.type === "thinking_delta" && ev.delta) {
|
|
1134
1146
|
pushContent({ type: "reasoning_delta", delta: ev.delta, ...ident() });
|
|
1135
1147
|
}
|
|
@@ -1829,6 +1841,11 @@ export class Runner {
|
|
|
1829
1841
|
throw steeringError(`inputId "${LEGACY_PENDING_STEER_INPUT_ID}" is reserved for a pre-queue parked steer and cannot be supplied by a caller`, "steering.invalid_content");
|
|
1830
1842
|
}
|
|
1831
1843
|
}
|
|
1844
|
+
const priorityIn = options?.priority;
|
|
1845
|
+
if (priorityIn !== undefined && !isSystemInjectionPriority(priorityIn)) {
|
|
1846
|
+
throw steeringError(`priority must be one of ${SYSTEM_INJECTION_PRIORITIES.join("/")} when supplied`, "steering.invalid_content");
|
|
1847
|
+
}
|
|
1848
|
+
const priority = priorityIn ?? "next";
|
|
1832
1849
|
const actorIn = options?.actor;
|
|
1833
1850
|
const actor = actorIn === undefined ? undefined : snapshotActorAssertion(actorIn);
|
|
1834
1851
|
const projected = projectHumanInput({ text, actor, source: "steer" });
|
|
@@ -1837,6 +1854,7 @@ export class Runner {
|
|
|
1837
1854
|
text,
|
|
1838
1855
|
trusted,
|
|
1839
1856
|
inputId: effectiveInputId,
|
|
1857
|
+
...(priorityIn !== undefined ? { priority } : {}),
|
|
1840
1858
|
...(actor !== undefined ? { actor } : {}),
|
|
1841
1859
|
};
|
|
1842
1860
|
let payload;
|
|
@@ -1872,7 +1890,7 @@ export class Runner {
|
|
|
1872
1890
|
throw steeringError("the task is not running");
|
|
1873
1891
|
payload = trusted ? formatHookFeedback(projected, h.reminderMark) : frameMidTurnUserInput(projected);
|
|
1874
1892
|
mintsAFrame = payload.trim().length !== 0;
|
|
1875
|
-
replay = { payload, trusted, ...(actor !== undefined ? { actor } : {}) };
|
|
1893
|
+
replay = { payload, trusted, priority, ...(actor !== undefined ? { actor } : {}) };
|
|
1876
1894
|
if (typeof inputId === "string") {
|
|
1877
1895
|
const prior = acceptedSteerInputs.get(inputId);
|
|
1878
1896
|
if (prior !== undefined) {
|
|
@@ -1885,9 +1903,73 @@ export class Runner {
|
|
|
1885
1903
|
return;
|
|
1886
1904
|
}
|
|
1887
1905
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
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
|
+
}
|
|
1947
|
+
const injectFramed = async () => {
|
|
1948
|
+
const noteOptions = { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) };
|
|
1949
|
+
if (priority === "later") {
|
|
1950
|
+
await h.harness.followUp(payload, noteOptions);
|
|
1951
|
+
noteAccepted(h);
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
const frame = await h.harness.steer(payload, { ...noteOptions, ...(priority === "now" ? { immediate: true } : {}) });
|
|
1890
1955
|
noteAccepted(h);
|
|
1956
|
+
if (priority === "now" && frame !== undefined && h.harness.interruptTurn(frame)) {
|
|
1957
|
+
deliverEngineNotice(this.deps.onNotice, {
|
|
1958
|
+
code: "task.turn_interrupted",
|
|
1959
|
+
message: "a caller-provenance steer with priority \"now\" interrupted the running turn: in-flight work was cut at " +
|
|
1960
|
+
"a manufactured boundary (finished tool calls keep their real results; never-started ones settle as " +
|
|
1961
|
+
"interrupted) and the run continues with the steer at the queue head.",
|
|
1962
|
+
detail: {
|
|
1963
|
+
inputId: effectiveInputId,
|
|
1964
|
+
sessionId: h.sessionId,
|
|
1965
|
+
...(actor?.id !== undefined ? { actorId: actor.id } : {}),
|
|
1966
|
+
...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
|
|
1967
|
+
},
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
try {
|
|
1972
|
+
await injectFramed();
|
|
1891
1973
|
return;
|
|
1892
1974
|
}
|
|
1893
1975
|
catch (e) {
|
|
@@ -1897,8 +1979,7 @@ export class Runner {
|
|
|
1897
1979
|
const birthDeadline = Date.now() + READY_TIMEOUT_MS;
|
|
1898
1980
|
while (resultValue === undefined && !h.loop.ended && Date.now() < birthDeadline) {
|
|
1899
1981
|
try {
|
|
1900
|
-
await
|
|
1901
|
-
noteAccepted(h);
|
|
1982
|
+
await injectFramed();
|
|
1902
1983
|
return;
|
|
1903
1984
|
}
|
|
1904
1985
|
catch (e2) {
|
|
@@ -1938,9 +2019,14 @@ export class Runner {
|
|
|
1938
2019
|
if (input.source !== undefined && typeof input.source !== "string") {
|
|
1939
2020
|
throw notifyError("input.source must be a string when present", "notify.invalid_payload");
|
|
1940
2021
|
}
|
|
1941
|
-
|
|
2022
|
+
const priorityIn = opts?.priority;
|
|
2023
|
+
if (priorityIn !== undefined && !isSystemInjectionPriority(priorityIn)) {
|
|
1942
2024
|
throw notifyError(`opts.priority must be one of ${SYSTEM_INJECTION_PRIORITIES.join("/")} when present`, "notify.invalid_payload");
|
|
1943
2025
|
}
|
|
2026
|
+
const priority = priorityIn;
|
|
2027
|
+
if (priority === "now") {
|
|
2028
|
+
throw notifyError('priority "now" (turn interrupt) is not a notification-lane power — it belongs to the steer face; use "next" for earliest-boundary delivery', "notify.invalid_priority");
|
|
2029
|
+
}
|
|
1944
2030
|
const payload = {
|
|
1945
2031
|
task_id: input.task_id,
|
|
1946
2032
|
task_type: "external",
|
|
@@ -1954,7 +2040,7 @@ export class Runner {
|
|
|
1954
2040
|
if (!h || notifyRef.inject === undefined) {
|
|
1955
2041
|
throw notifyError("the task is not running", "notify.not_running");
|
|
1956
2042
|
}
|
|
1957
|
-
notifyRef.inject(payload,
|
|
2043
|
+
notifyRef.inject(payload, priority !== undefined ? { priority } : undefined);
|
|
1958
2044
|
},
|
|
1959
2045
|
compact: async (opts) => {
|
|
1960
2046
|
if (resultValue)
|
|
@@ -2050,7 +2136,7 @@ export class Runner {
|
|
|
2050
2136
|
let descendantAnchorDisclosed = false;
|
|
2051
2137
|
const parkTaskNotification = (payload, priority) => {
|
|
2052
2138
|
if (notificationSessionId !== undefined)
|
|
2053
|
-
this.pendingSessionNotifications.pend(notificationSessionId, payload);
|
|
2139
|
+
this.pendingSessionNotifications.pend(notificationSessionId, payload, priority);
|
|
2054
2140
|
if (!isDelegatedAgentTerminal(payload))
|
|
2055
2141
|
return;
|
|
2056
2142
|
const uplink = internals?.parentNotify;
|
|
@@ -2065,7 +2151,7 @@ export class Runner {
|
|
|
2065
2151
|
}
|
|
2066
2152
|
const rootAnchor = internals?.rootSessionId;
|
|
2067
2153
|
if (rootAnchor !== undefined && rootAnchor !== notificationSessionId) {
|
|
2068
|
-
this.pendingSessionNotifications.pend(rootAnchor, payload);
|
|
2154
|
+
this.pendingSessionNotifications.pend(rootAnchor, payload, priority);
|
|
2069
2155
|
return;
|
|
2070
2156
|
}
|
|
2071
2157
|
if (internals?.parentSessionId !== undefined && !descendantAnchorDisclosed) {
|
|
@@ -2074,10 +2160,17 @@ export class Runner {
|
|
|
2074
2160
|
}
|
|
2075
2161
|
};
|
|
2076
2162
|
const unsubscribeTaskNotifications = taskNotificationQueue.subscribe((item) => {
|
|
2077
|
-
queue.push({ type: "task_notification", notification: item.payload, ...notificationIdent() });
|
|
2163
|
+
queue.push({ type: "task_notification", notification: item.payload, priority: item.priority, ...notificationIdent() });
|
|
2078
2164
|
if (notificationHarness && prepared.batchHaltRef.current === undefined) {
|
|
2079
2165
|
const xml = renderTaskNotificationXml(item.payload);
|
|
2080
|
-
const
|
|
2166
|
+
const noteOptions = {
|
|
2167
|
+
provenance: "engine-note",
|
|
2168
|
+
enginePayload: item.payload,
|
|
2169
|
+
...(item.payload.task_type !== "external" && isTerminalTaskNotification(item.payload) ? { capPreferred: true } : {}),
|
|
2170
|
+
};
|
|
2171
|
+
const deliver = item.priority === "later"
|
|
2172
|
+
? notificationHarness.followUp(xml, noteOptions)
|
|
2173
|
+
: notificationHarness.steer(xml, { ...noteOptions, ...(item.priority === "now" ? { immediate: true } : {}) });
|
|
2081
2174
|
void deliver.then(() => item.onDisposition?.("queued"), () => {
|
|
2082
2175
|
parkTaskNotification(item.payload, item.priority);
|
|
2083
2176
|
item.onDisposition?.("parked");
|
|
@@ -2090,18 +2183,7 @@ export class Runner {
|
|
|
2090
2183
|
});
|
|
2091
2184
|
const upstreamTaskNotification = internals?.onTaskNotification;
|
|
2092
2185
|
const deliveredAtTurnOpen = new Set();
|
|
2093
|
-
let priorityNowDisclosed = false;
|
|
2094
2186
|
const injectTaskNotification = (notification, opts) => {
|
|
2095
|
-
if (opts?.priority === "now" && !priorityNowDisclosed) {
|
|
2096
|
-
priorityNowDisclosed = true;
|
|
2097
|
-
deliverEngineNotice(this.deps.onNotice, {
|
|
2098
|
-
code: "task.injection_priority_unimplemented",
|
|
2099
|
-
message: `a notification was injected with priority "now", which this engine does not implement: all injection ` +
|
|
2100
|
-
`priorities deliver at the NEXT turn boundary and none aborts the running turn. The notification is ` +
|
|
2101
|
-
`delivered — only the interrupting semantics are absent.`,
|
|
2102
|
-
detail: { priority: "now", ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), sessionId: prepared.sessionId },
|
|
2103
|
-
});
|
|
2104
|
-
}
|
|
2105
2187
|
if (deliveredAtTurnOpen.has(taskNotificationDedupKey(notification)))
|
|
2106
2188
|
return Promise.resolve("dropped_duplicate");
|
|
2107
2189
|
if (!notificationLaneLive) {
|
|
@@ -2196,16 +2278,17 @@ export class Runner {
|
|
|
2196
2278
|
const pendingIdle = this.pendingSessionNotifications.drain(prepared.sessionId);
|
|
2197
2279
|
if (pendingIdle !== undefined) {
|
|
2198
2280
|
for (const payload of discloseDroppedPending(pendingIdle)) {
|
|
2281
|
+
const parkedPriority = pendingIdle.priorities?.get(payload);
|
|
2199
2282
|
deliveredAtTurnOpen.add(taskNotificationDedupKey(payload));
|
|
2200
|
-
queue.push({ type: "task_notification", notification: payload, ...ident() });
|
|
2283
|
+
queue.push({ type: "task_notification", notification: payload, ...(parkedPriority !== undefined ? { priority: parkedPriority } : {}), ...ident() });
|
|
2201
2284
|
void prepared.harness.nextTurn(renderTaskNotificationXml(payload), { provenance: "engine-note", enginePayload: payload }).catch(() => {
|
|
2202
|
-
this.pendingSessionNotifications.pend(prepared.sessionId, payload);
|
|
2285
|
+
this.pendingSessionNotifications.pend(prepared.sessionId, payload, parkedPriority);
|
|
2203
2286
|
});
|
|
2204
2287
|
}
|
|
2205
2288
|
}
|
|
2206
2289
|
}
|
|
2207
2290
|
const loopLatch = { ended: false, userInterrupted: false };
|
|
2208
|
-
onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark });
|
|
2291
|
+
onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId, hookTimeoutMs: prepared.hookTimeoutMs, hookIdentity: prepared.hookIdentity });
|
|
2209
2292
|
const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
|
|
2210
2293
|
prepared.liveSpendRef.get = () => ({ costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) });
|
|
2211
2294
|
if (resume &&
|
|
@@ -2227,7 +2310,7 @@ export class Runner {
|
|
|
2227
2310
|
const rs = createRunState();
|
|
2228
2311
|
rs.telemetry.cacheFamily = cacheFamilyOf(prepared.model);
|
|
2229
2312
|
rs.telemetry.pricing = this.deps.pricing?.[prepared.model.id] ?? modelCostToPricing(prepared.model.cost);
|
|
2230
|
-
rs.telemetry.pricingConfigured =
|
|
2313
|
+
rs.telemetry.pricingConfigured = isModelPriced(prepared.model, this.deps.pricing);
|
|
2231
2314
|
if (spec.limits?.degrade) {
|
|
2232
2315
|
try {
|
|
2233
2316
|
rs.degrade.degradeToModel = resolveModel(spec.limits.degrade.to, modelCatalog);
|
|
@@ -2267,6 +2350,12 @@ export class Runner {
|
|
|
2267
2350
|
catch (err) {
|
|
2268
2351
|
discloseNoteTaskRunFailure(err);
|
|
2269
2352
|
}
|
|
2353
|
+
const noteUnevaluablePriceTable = (p) => {
|
|
2354
|
+
if (prepared.usageGovernance?.governsCost !== true)
|
|
2355
|
+
return;
|
|
2356
|
+
if (malformedPricingField(p) !== undefined)
|
|
2357
|
+
rs.telemetry.unpricedSpend = true;
|
|
2358
|
+
};
|
|
2270
2359
|
rs.degrade.recordDegraded = (info, toModel) => {
|
|
2271
2360
|
if (rs.degrade.degraded !== undefined)
|
|
2272
2361
|
return;
|
|
@@ -2281,13 +2370,15 @@ export class Runner {
|
|
|
2281
2370
|
}
|
|
2282
2371
|
if (m) {
|
|
2283
2372
|
rs.telemetry.pricing = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
|
|
2284
|
-
rs.telemetry.pricingConfigured = this.deps.pricing
|
|
2373
|
+
rs.telemetry.pricingConfigured = isModelPriced(m, this.deps.pricing);
|
|
2374
|
+
noteUnevaluablePriceTable(rs.telemetry.pricing);
|
|
2285
2375
|
rs.telemetry.cacheFamily = cacheFamilyOf(m);
|
|
2286
2376
|
}
|
|
2287
2377
|
else {
|
|
2288
2378
|
if (this.deps.pricing?.[info.to]) {
|
|
2289
2379
|
rs.telemetry.pricing = this.deps.pricing[info.to];
|
|
2290
2380
|
rs.telemetry.pricingConfigured = true;
|
|
2381
|
+
noteUnevaluablePriceTable(rs.telemetry.pricing);
|
|
2291
2382
|
}
|
|
2292
2383
|
else {
|
|
2293
2384
|
rs.telemetry.pricingConfigured = false;
|
|
@@ -2706,9 +2797,10 @@ export class Runner {
|
|
|
2706
2797
|
return;
|
|
2707
2798
|
const fam = cacheFamilyOf(m);
|
|
2708
2799
|
const price = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
|
|
2709
|
-
const priced = this.deps.pricing
|
|
2800
|
+
const priced = isModelPriced(m, this.deps.pricing);
|
|
2710
2801
|
if (!priced)
|
|
2711
2802
|
rs.telemetry.unpricedSpend = true;
|
|
2803
|
+
noteUnevaluablePriceTable(price);
|
|
2712
2804
|
const { totalInputTokens, uncachedInputTokens, costMicroUsd } = usageCostMicroUsd(fam, u, price);
|
|
2713
2805
|
stats.tokens += u.totalTokens || 0;
|
|
2714
2806
|
stats.promptTokens += uncachedInputTokens;
|
|
@@ -3281,6 +3373,72 @@ export class Runner {
|
|
|
3281
3373
|
...(resume.wakeMessage !== undefined ? [{ entry: resume.wakeMessage, source: "wake" }] : []),
|
|
3282
3374
|
];
|
|
3283
3375
|
for (const { entry: steer, source } of resumeFrames) {
|
|
3376
|
+
if (source === "wake" && resume.wakeMessageHookContext !== undefined) {
|
|
3377
|
+
const hookStart = continuation.length;
|
|
3378
|
+
continuation += "\n\n" + formatHookFeedback(resume.wakeMessageHookContext, prepared.reminderMark);
|
|
3379
|
+
engineSegments.push({ start: hookStart, end: continuation.length });
|
|
3380
|
+
}
|
|
3381
|
+
if (source === "steer") {
|
|
3382
|
+
const parkedScreen = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
|
|
3383
|
+
if (parkedScreen !== undefined) {
|
|
3384
|
+
let outcome;
|
|
3385
|
+
try {
|
|
3386
|
+
const seat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => parkedScreen(steer.text, {
|
|
3387
|
+
identity: prepared.hookIdentity,
|
|
3388
|
+
signal: sig,
|
|
3389
|
+
source: "parked_redelivery",
|
|
3390
|
+
...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
|
|
3391
|
+
...(steer.actor !== undefined ? { actor: snapshotActorAssertion(steer.actor) } : {}),
|
|
3392
|
+
}));
|
|
3393
|
+
outcome = seat.expired
|
|
3394
|
+
? { 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)" }
|
|
3395
|
+
: seat.value?.block !== undefined && seat.value.block !== ""
|
|
3396
|
+
? { blocked: inlineUntrusted(seat.value.block) }
|
|
3397
|
+
: { ...(seat.value?.additionalContext !== undefined && seat.value.additionalContext !== "" ? { context: seat.value.additionalContext } : {}) };
|
|
3398
|
+
}
|
|
3399
|
+
catch (hookErr) {
|
|
3400
|
+
const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
|
|
3401
|
+
try {
|
|
3402
|
+
this.deps.onError?.(err, { phase: "hook", sessionId: prepared.sessionId });
|
|
3403
|
+
}
|
|
3404
|
+
catch {
|
|
3405
|
+
}
|
|
3406
|
+
outcome = { blocked: `the screen crashed (${inlineUntrusted(err.message)}; fail-closed)` };
|
|
3407
|
+
}
|
|
3408
|
+
if ("blocked" in outcome) {
|
|
3409
|
+
deliverEngineNotice(this.deps.onNotice, {
|
|
3410
|
+
code: "steering.parked_input_blocked",
|
|
3411
|
+
message: "a parked steering input was withheld by the deployment's userPromptSubmit screen when this resume redelivered it: " +
|
|
3412
|
+
`${outcome.blocked}. The parked row was consumed with the checkpoint; the instruction never reached the model — ` +
|
|
3413
|
+
"re-issue it if it still applies.",
|
|
3414
|
+
detail: {
|
|
3415
|
+
...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
|
|
3416
|
+
sessionId: prepared.sessionId,
|
|
3417
|
+
...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
|
|
3418
|
+
},
|
|
3419
|
+
});
|
|
3420
|
+
queue.push({
|
|
3421
|
+
...buildHumanInputEvent({
|
|
3422
|
+
carrier: source,
|
|
3423
|
+
source,
|
|
3424
|
+
delivery: "blocked",
|
|
3425
|
+
sessionSeq: nextHumanInputSeq(prepared.harness),
|
|
3426
|
+
...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
|
|
3427
|
+
...(steer.actor !== undefined ? { actor: steer.actor } : {}),
|
|
3428
|
+
...(steer.actor?.issuer !== undefined ? { issuer: steer.actor.issuer } : {}),
|
|
3429
|
+
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
3430
|
+
}),
|
|
3431
|
+
...ident(),
|
|
3432
|
+
});
|
|
3433
|
+
continue;
|
|
3434
|
+
}
|
|
3435
|
+
if ("context" in outcome && outcome.context !== undefined) {
|
|
3436
|
+
const hookStart = continuation.length;
|
|
3437
|
+
continuation += "\n\n" + formatHookFeedback(outcome.context, prepared.reminderMark);
|
|
3438
|
+
engineSegments.push({ start: hookStart, end: continuation.length });
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3284
3442
|
const start = continuation.length;
|
|
3285
3443
|
const projected = projectHumanInput({ text: steer.text, actor: steer.actor, source });
|
|
3286
3444
|
continuation +=
|
|
@@ -3331,7 +3489,7 @@ export class Runner {
|
|
|
3331
3489
|
const userPromptSubmit = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
|
|
3332
3490
|
if (userPromptSubmit) {
|
|
3333
3491
|
try {
|
|
3334
|
-
const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, signal: sig }));
|
|
3492
|
+
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) } : {}) }));
|
|
3335
3493
|
if (promptSeat.expired) {
|
|
3336
3494
|
if (promptSeat.cause === "timeout") {
|
|
3337
3495
|
try {
|
|
@@ -3555,7 +3713,7 @@ export class Runner {
|
|
|
3555
3713
|
}
|
|
3556
3714
|
if (prepared.usageGovernance !== undefined) {
|
|
3557
3715
|
try {
|
|
3558
|
-
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 }));
|
|
3716
|
+
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 }));
|
|
3559
3717
|
}
|
|
3560
3718
|
catch (flushErr) {
|
|
3561
3719
|
this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
|
|
@@ -4138,7 +4296,9 @@ export class Runner {
|
|
|
4138
4296
|
}
|
|
4139
4297
|
}
|
|
4140
4298
|
let wakeMessage;
|
|
4299
|
+
let wakeHookContext;
|
|
4141
4300
|
const outcomeGate = outcome.gate;
|
|
4301
|
+
const resumeSignal = taskConfig.signal;
|
|
4142
4302
|
let suppliedMessage;
|
|
4143
4303
|
if (outcomeGate === "wake") {
|
|
4144
4304
|
suppliedMessage = outcome.message;
|
|
@@ -4176,6 +4336,62 @@ export class Runner {
|
|
|
4176
4336
|
}
|
|
4177
4337
|
if (suppliedMessage !== undefined) {
|
|
4178
4338
|
wakeMessage = validatePendingSteer(suppliedMessage);
|
|
4339
|
+
{
|
|
4340
|
+
const resumeScreenHooks = taskConfig.hooks ?? this.deps.hooks;
|
|
4341
|
+
const screen = resumeScreenHooks?.userPromptSubmit;
|
|
4342
|
+
if (screen !== undefined && resumeSignal?.aborted !== true) {
|
|
4343
|
+
const screenedMessage = wakeMessage;
|
|
4344
|
+
const identity = mintHookInvocationIdentity({
|
|
4345
|
+
sessionId: cp.sessionId,
|
|
4346
|
+
taskId: taskConfig.taskId ?? cp.sessionId,
|
|
4347
|
+
legKind: "resume",
|
|
4348
|
+
isDelegatedChild: effectiveDelegationFacts(internals, cp.state.isDelegatedChild).isDelegatedChild,
|
|
4349
|
+
...(internals?.insideFork === true ? { insideFork: true } : {}),
|
|
4350
|
+
...(internals?.agentName !== undefined ? { agentName: internals.agentName } : {}),
|
|
4351
|
+
...(internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId } : {}),
|
|
4352
|
+
});
|
|
4353
|
+
const screenTimeoutMs = resolveHookTimeoutMs(resumeScreenHooks?.timeoutMs, (badErr) => {
|
|
4354
|
+
try {
|
|
4355
|
+
this.deps.onError?.(badErr instanceof Error ? badErr : new Error(String(badErr)), { phase: "hook", sessionId: cp.sessionId });
|
|
4356
|
+
}
|
|
4357
|
+
catch {
|
|
4358
|
+
}
|
|
4359
|
+
}, resumeScreenHooks);
|
|
4360
|
+
let decision;
|
|
4361
|
+
try {
|
|
4362
|
+
const seat = await runHookSeat("userPromptSubmit", { timeoutMs: screenTimeoutMs, ...(resumeSignal !== undefined ? { signal: resumeSignal } : {}), abortEnds: true }, (sig) => screen(screenedMessage.text, {
|
|
4363
|
+
identity,
|
|
4364
|
+
signal: sig,
|
|
4365
|
+
source: "resume_message",
|
|
4366
|
+
...(screenedMessage.inputId !== undefined ? { inputId: screenedMessage.inputId } : {}),
|
|
4367
|
+
...(screenedMessage.actor !== undefined ? { actor: snapshotActorAssertion(screenedMessage.actor) } : {}),
|
|
4368
|
+
}));
|
|
4369
|
+
if (seat.expired) {
|
|
4370
|
+
throw new CheckpointError("steering.blocked_by_hook", seat.cause === "timeout"
|
|
4371
|
+
? `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`
|
|
4372
|
+
: `the resume was cancelled while the deployment's userPromptSubmit hook was still screening this wake message; the checkpoint stays pending`);
|
|
4373
|
+
}
|
|
4374
|
+
decision = seat.value;
|
|
4375
|
+
}
|
|
4376
|
+
catch (hookErr) {
|
|
4377
|
+
if (hookErr instanceof CheckpointError)
|
|
4378
|
+
throw hookErr;
|
|
4379
|
+
const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
|
|
4380
|
+
try {
|
|
4381
|
+
this.deps.onError?.(err, { phase: "hook", sessionId: cp.sessionId });
|
|
4382
|
+
}
|
|
4383
|
+
catch {
|
|
4384
|
+
}
|
|
4385
|
+
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`);
|
|
4386
|
+
}
|
|
4387
|
+
if (decision?.block !== undefined && decision.block !== "") {
|
|
4388
|
+
throw new CheckpointError("steering.blocked_by_hook", `the deployment's userPromptSubmit hook blocked this wake message: ${inlineUntrusted(decision.block)}`);
|
|
4389
|
+
}
|
|
4390
|
+
if (decision?.additionalContext !== undefined && decision.additionalContext !== "") {
|
|
4391
|
+
wakeHookContext = decision.additionalContext;
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4179
4395
|
}
|
|
4180
4396
|
else if (readPendingSteerQueue(cp.state).length === 0) {
|
|
4181
4397
|
throw new CheckpointError("wake.nothing_to_deliver", "cannot wake: no message was supplied and the checkpoint holds no parked pendingSteer — an empty " +
|
|
@@ -4423,7 +4639,7 @@ export class Runner {
|
|
|
4423
4639
|
"resuming here would run the leg with that ceiling unenforced. Rejected pre-CAS (the checkpoint stays pending): finish it on a worker of the " +
|
|
4424
4640
|
"previous release, or start a fresh task bounded by resourceSuspend.totalTokens / totalBudgetUsd.");
|
|
4425
4641
|
}
|
|
4426
|
-
if (
|
|
4642
|
+
if (resumeSignal?.aborted === true && cp.status === "pending") {
|
|
4427
4643
|
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)");
|
|
4428
4644
|
}
|
|
4429
4645
|
if (cp.state.workspaceHandle !== undefined && this.deps.executionEnvFactory === undefined) {
|
|
@@ -4497,7 +4713,7 @@ export class Runner {
|
|
|
4497
4713
|
const preCasWindows = resolveUsageWindows(this.deps.usageWindows);
|
|
4498
4714
|
if (preCasWindows !== undefined && preCasWindows.length > 0) {
|
|
4499
4715
|
const ledgerKey = (suppliedPrincipal ?? rowPrincipal) || GLOBAL_USAGE_KEY;
|
|
4500
|
-
const wait = usageRetryAfterMs(await this.deps.usageWindowStore.read(ledgerKey, preCasWindows, Date.now()));
|
|
4716
|
+
const wait = usageRetryAfterMs(await this.deps.usageWindowStore.read(ledgerKey, preCasWindows, Date.now()), preCasWindows);
|
|
4501
4717
|
if (wait !== undefined) {
|
|
4502
4718
|
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 — ` +
|
|
4503
4719
|
`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 });
|
|
@@ -4657,7 +4873,7 @@ export class Runner {
|
|
|
4657
4873
|
? 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")
|
|
4658
4874
|
: 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)");
|
|
4659
4875
|
}
|
|
4660
|
-
return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}) }, resumeInternals);
|
|
4876
|
+
return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}), ...(wakeHookContext !== undefined ? { wakeMessageHookContext: wakeHookContext } : {}) }, resumeInternals);
|
|
4661
4877
|
}
|
|
4662
4878
|
async applyResumeDecision(prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
|
|
4663
4879
|
const { pendingAction } = resume.cp;
|