@sema-agent/core 7.5.2 → 7.6.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 +41 -0
- package/dist/agents/cascade.d.ts +2 -2
- package/dist/agents/cascade.js +12 -10
- package/dist/agents/repair-loop.d.ts +5 -3
- package/dist/agents/repair-loop.js +13 -15
- package/dist/agents/subagent.d.ts +24 -42
- package/dist/agents/subagent.js +119 -105
- package/dist/agents/suspend-guard.d.ts +31 -19
- package/dist/agents/suspend-guard.js +14 -8
- package/dist/agents/teacher.js +9 -9
- package/dist/agents/team.d.ts +4 -3
- package/dist/agents/team.js +10 -8
- package/dist/agents/verify.d.ts +3 -3
- package/dist/agents/verify.js +17 -17
- package/dist/core/a2a.js +2 -1
- package/dist/core/ask-origin.d.ts +60 -7
- package/dist/core/ask-origin.js +26 -1
- package/dist/core/checkpoint-store.d.ts +78 -76
- package/dist/core/checkpoint-store.js +17 -1
- package/dist/core/gate-outcome.d.ts +189 -0
- package/dist/core/gate-outcome.js +70 -0
- package/dist/core/hooks.d.ts +18 -92
- package/dist/core/hooks.js +88 -85
- package/dist/core/mcp-failure.d.ts +104 -0
- package/dist/core/mcp-failure.js +128 -0
- package/dist/core/mcp.d.ts +21 -77
- package/dist/core/mcp.js +76 -150
- package/dist/core/pause-registry.d.ts +131 -0
- package/dist/core/pause-registry.js +27 -0
- package/dist/core/runner/assemble-result.d.ts +32 -41
- package/dist/core/runner/assemble-result.js +55 -74
- package/dist/core/runner/contracts.d.ts +46 -64
- package/dist/core/runner/denial-limit-arms.d.ts +1 -1
- package/dist/core/runner/denial-limit-arms.js +3 -3
- package/dist/core/runner/gate-exit.d.ts +74 -0
- package/dist/core/runner/gate-exit.js +55 -0
- package/dist/core/runner/park-commit.d.ts +17 -23
- package/dist/core/runner/park-commit.js +14 -15
- package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
- package/dist/core/runner/prepare-ask-lane.js +3 -5
- package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
- package/dist/core/runner/prepare-boundary-parks.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
- package/dist/core/runner/prepare-gate-stations.js +29 -54
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +44 -26
- package/dist/core/runner/prepare-park-ask.d.ts +2 -4
- package/dist/core/runner/prepare-park-ask.js +5 -5
- package/dist/core/runner/prepare-task.js +8 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
- package/dist/core/runner/prepare-wiring-manifest.js +9 -10
- package/dist/core/runner/runtask.d.ts +16 -31
- package/dist/core/runner/runtask.js +109 -120
- package/dist/core/runner/terminal-projection.d.ts +22 -0
- package/dist/core/runner/terminal-projection.js +28 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
- package/dist/core/terminal-cause.d.ts +137 -0
- package/dist/core/terminal-cause.js +9 -0
- package/dist/core/tool-policy.d.ts +43 -139
- package/dist/core/tool-policy.js +79 -112
- package/dist/core/types.d.ts +67 -164
- package/dist/core/wiring-manifest.d.ts +6 -3
- package/dist/core/workflow-journal-store.js +3 -4
- package/dist/engine/harness/agent-harness.d.ts +1 -1
- package/dist/index.d.ts +10 -7
- package/dist/index.js +8 -5
- package/dist/orchestration/builtin-workflows.d.ts +2 -2
- package/dist/orchestration/builtin-workflows.js +1 -1
- package/dist/orchestration/goal.js +8 -7
- package/dist/orchestration/run-spec.js +5 -3
- package/dist/orchestration/run-workflow-tool.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.js +4 -4
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-governance.js +4 -2
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +1 -1
- package/dist/orchestration/workflow.d.ts +11 -0
- package/dist/orchestration/workflow.js +64 -39
- package/dist/prompts/supervisor.d.ts +1 -1
- package/dist/prompts/supervisor.js +3 -3
- package/dist/scenarios/scenario-registry.js +1 -1
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +74 -22
|
@@ -6,7 +6,7 @@ import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNoti
|
|
|
6
6
|
import { planRejectionClears, resolveTriggerWindow } from "../context-edit.js";
|
|
7
7
|
import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, isSyntheticApiErrorMessage, uuidv7 } from "../../internal/harness.js";
|
|
8
8
|
import { snapshotActorAssertion } from "../../internal/llm.js";
|
|
9
|
-
import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
|
|
9
|
+
import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, realApprovalOrgFact } from "../checkpoint-store.js";
|
|
10
10
|
import { GIT_STATUS_ECHO_PREVIEW, branchCarriesVisiblePositiveGitFrame, newestEngineGitFrame, stripGitStatusUnits } from "./git-status-frame.js";
|
|
11
11
|
import { gitFrameContextVisible, normalizeGitAnnouncement } from "../../internal/harness.js";
|
|
12
12
|
import { engineVersion } from "../version.js";
|
|
@@ -35,6 +35,7 @@ import { toImageContent } from "./image.js";
|
|
|
35
35
|
import { OUTPUT_TOOL_NAME, SKILLS_LISTING_PROBE_HEADER, resolveOutputRetries } from "./synthetic-tools.js";
|
|
36
36
|
import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
|
|
37
37
|
import { assembleResult, errorCodeOf } from "./assemble-result.js";
|
|
38
|
+
import { amendTerminal, terminalProjection } from "./terminal-projection.js";
|
|
38
39
|
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, 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";
|
|
39
40
|
import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
|
|
40
41
|
import { effectiveDelegationFacts, gatedCallIdOf, placementValueOrAbsent, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
|
|
@@ -51,7 +52,11 @@ import { droppedToolDigest, withAnnounceDigests } from "./announce-once-ledger.j
|
|
|
51
52
|
import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
|
|
52
53
|
import { RunnerSharedToolResultStore } from "../tool-result-store.js";
|
|
53
54
|
import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
|
|
54
|
-
import {
|
|
55
|
+
import { isAskOrigin } from "../ask-origin.js";
|
|
56
|
+
import { isGateKind, PAUSE_REGISTRY, resumeGateMatches } from "../pause-registry.js";
|
|
57
|
+
import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOfSuppliedLayer, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
|
|
58
|
+
import { mintGateOutcome } from "./gate-exit.js";
|
|
59
|
+
import { MCP_DELIVERY_VERDICTS } from "../mcp-failure.js";
|
|
55
60
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
56
61
|
import { discloseDroppedPending, isDelegatedAgentTerminal, isSystemInjectionPriority, isTerminalTaskNotification, PendingSessionNotifications, renderTaskNotificationXml, SYSTEM_INJECTION_PRIORITIES, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
57
62
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
@@ -147,23 +152,23 @@ function resumeDecisionWasNegative(resume) {
|
|
|
147
152
|
}
|
|
148
153
|
const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
|
|
149
154
|
"NOT executed on resume. If you still need it, issue it again now.";
|
|
150
|
-
function toolEndBodyFrom(result, isError,
|
|
155
|
+
function toolEndBodyFrom(result, isError, gate, gatedCallIdOfRun) {
|
|
151
156
|
const o = toolOutputFrom(result);
|
|
152
157
|
const st = structuredFrom(result);
|
|
153
158
|
const det = isError ? result?.details : undefined;
|
|
154
159
|
const codeRaw = det?.code;
|
|
155
160
|
const kindRaw = det?.errorKind;
|
|
156
161
|
const code = typeof codeRaw === "string" ? codeRaw : typeof kindRaw === "string" ? kindRaw : undefined;
|
|
162
|
+
const deliveredRaw = det?.delivered;
|
|
163
|
+
const delivered = typeof deliveredRaw === "string" && MCP_DELIVERY_VERDICTS.includes(deliveredRaw) ? deliveredRaw : undefined;
|
|
157
164
|
const gatedCallId = code === "gate.parked" ? gatedCallIdOfRun : undefined;
|
|
158
165
|
return {
|
|
159
166
|
...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}),
|
|
160
167
|
...(st !== undefined ? { structured: st } : {}),
|
|
161
168
|
...(typeof code === "string" ? { errorCode: code } : {}),
|
|
169
|
+
...(delivered !== undefined ? { delivered } : {}),
|
|
162
170
|
...(gatedCallId !== undefined ? { gatedCallId } : {}),
|
|
163
|
-
...(
|
|
164
|
-
...(approver !== undefined ? { approver } : {}),
|
|
165
|
-
...(resolution !== undefined ? { resolution } : {}),
|
|
166
|
-
...(autoDenied !== undefined ? { autoDenied } : {}),
|
|
171
|
+
...(gate !== undefined ? { gate } : {}),
|
|
167
172
|
};
|
|
168
173
|
}
|
|
169
174
|
export function reconciledToolEndBody(orphan) {
|
|
@@ -393,7 +398,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
393
398
|
let platformCause;
|
|
394
399
|
let platformRetryAfterMs;
|
|
395
400
|
const preemptWillSuspend = preemptHit && prepared.suspendForResource !== undefined;
|
|
396
|
-
if (!preemptWillSuspend && prepared.
|
|
401
|
+
if (!preemptWillSuspend && prepared.pausedRef.current === undefined && !prepared.abortController.signal.aborted) {
|
|
397
402
|
const ledgerReadDeadline = prepared.envLifetimeSuspendAt !== undefined ? prepared.envLifetimeSuspendAt + ENV_DUE_GOVERNANCE_READ_BUDGET_MS : undefined;
|
|
398
403
|
if (prepared.usageGovernance !== undefined) {
|
|
399
404
|
const governance = prepared.usageGovernance;
|
|
@@ -461,7 +466,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
461
466
|
}
|
|
462
467
|
if (prepared.suspendForResource &&
|
|
463
468
|
(rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || walltimeHit || preemptHit) &&
|
|
464
|
-
prepared.
|
|
469
|
+
prepared.pausedRef.current === undefined) {
|
|
465
470
|
const reason = preemptHit
|
|
466
471
|
? "preempt"
|
|
467
472
|
: rs.limits.budgetHit !== undefined
|
|
@@ -540,8 +545,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
540
545
|
prepared.reviewRequestRef.pending = undefined;
|
|
541
546
|
const honorable = prepared.suspendForReview !== undefined &&
|
|
542
547
|
!prepared.abortController.signal.aborted &&
|
|
543
|
-
prepared.
|
|
544
|
-
prepared.reviewRef.token === undefined;
|
|
548
|
+
prepared.pausedRef.current === undefined;
|
|
545
549
|
if (honorable && prepared.suspendForReview) {
|
|
546
550
|
await prepared.suspendForReview(reason);
|
|
547
551
|
return undefined;
|
|
@@ -838,8 +842,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
838
842
|
const forceManual = manualCompactRef.requested;
|
|
839
843
|
if ((!withinTaskCompaction && !forceManual) ||
|
|
840
844
|
prepared.abortController.signal.aborted ||
|
|
841
|
-
prepared.
|
|
842
|
-
prepared.reviewRef.token !== undefined) {
|
|
845
|
+
prepared.pausedRef.current !== undefined) {
|
|
843
846
|
if (forceManual) {
|
|
844
847
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
|
|
845
848
|
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
|
|
@@ -1386,16 +1389,16 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1386
1389
|
prepared.lspDiagnostics.nudge(p);
|
|
1387
1390
|
}
|
|
1388
1391
|
}
|
|
1389
|
-
const
|
|
1390
|
-
if (
|
|
1391
|
-
prepared.
|
|
1392
|
+
const gate = prepared.gateOutcomes.get(event.toolCallId);
|
|
1393
|
+
if (gate !== undefined)
|
|
1394
|
+
prepared.gateOutcomes.delete(event.toolCallId);
|
|
1392
1395
|
pushContent({
|
|
1393
1396
|
type: "tool_end",
|
|
1394
1397
|
toolCallId: event.toolCallId,
|
|
1395
1398
|
toolName: event.toolName,
|
|
1396
1399
|
...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
|
|
1397
1400
|
isError: event.isError,
|
|
1398
|
-
...toolEndBodyFrom(event.result, event.isError,
|
|
1401
|
+
...toolEndBodyFrom(event.result, event.isError, gate, prepared.abortController.signal.aborted ? gatedCallIdOf(prepared) : undefined),
|
|
1399
1402
|
...ident(),
|
|
1400
1403
|
});
|
|
1401
1404
|
announceWorkspaceMove();
|
|
@@ -1698,17 +1701,18 @@ export class Runner {
|
|
|
1698
1701
|
const pcs = internals?.inheritedGate?.parentConstraints;
|
|
1699
1702
|
if (resume !== undefined)
|
|
1700
1703
|
this.locallyClaimedTokens.delete(resume.cp.token);
|
|
1701
|
-
|
|
1704
|
+
const rToken = r.terminal.kind === "paused" ? r.terminal.token : undefined;
|
|
1705
|
+
if (resume !== undefined && rToken !== resume.cp.token) {
|
|
1702
1706
|
this.parentConstraintRegistry.delete(resume.cp.token);
|
|
1703
1707
|
this.suspendedEnvReaps.delete(resume.cp.token);
|
|
1704
1708
|
}
|
|
1705
|
-
if (r.
|
|
1709
|
+
if (r.terminal.kind === "paused" && PAUSE_REGISTRY[r.terminal.gate.kind].taskStatus === "suspended" && pcs !== undefined && pcs.length > 0) {
|
|
1706
1710
|
if (this.parentConstraintRegistry.size >= Runner.PARENT_CONSTRAINT_REGISTRY_CAP) {
|
|
1707
1711
|
const oldest = this.parentConstraintRegistry.keys().next().value;
|
|
1708
1712
|
if (oldest !== undefined)
|
|
1709
1713
|
this.parentConstraintRegistry.delete(oldest);
|
|
1710
1714
|
}
|
|
1711
|
-
this.parentConstraintRegistry.set(r.
|
|
1715
|
+
this.parentConstraintRegistry.set(r.terminal.token, pcs);
|
|
1712
1716
|
}
|
|
1713
1717
|
}, (p) => {
|
|
1714
1718
|
suggestionsDone = p;
|
|
@@ -1790,10 +1794,8 @@ export class Runner {
|
|
|
1790
1794
|
taskId: taskIdRef.current ?? "unknown",
|
|
1791
1795
|
sessionId: taskIdRef.sessionId ?? "unknown",
|
|
1792
1796
|
...(taskIdRef.runId !== undefined ? { runId: taskIdRef.runId } : {}),
|
|
1793
|
-
|
|
1797
|
+
terminal: { kind: "failed", ...(code !== undefined ? { code } : {}), message: err instanceof Error ? err.message : String(err) },
|
|
1794
1798
|
result: "",
|
|
1795
|
-
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1796
|
-
errorCode: code,
|
|
1797
1799
|
...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
|
|
1798
1800
|
...(taskIdRef.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: taskIdRef.effectiveMemoryScopes } : {}),
|
|
1799
1801
|
...(taskIdRef.effectiveReasoning !== undefined ? { effectiveReasoning: taskIdRef.effectiveReasoning } : {}),
|
|
@@ -2404,7 +2406,7 @@ export class Runner {
|
|
|
2404
2406
|
prepared.harness.engineInjectionsHeld = () => prepared.batchHaltRef.current !== undefined;
|
|
2405
2407
|
let undrainedUserAtEnd;
|
|
2406
2408
|
prepared.harness.onUndrainedUserInputs = (counts) => {
|
|
2407
|
-
if (prepared.
|
|
2409
|
+
if (prepared.pausedRef.current !== undefined) {
|
|
2408
2410
|
undrainedUserAtEnd = counts;
|
|
2409
2411
|
return;
|
|
2410
2412
|
}
|
|
@@ -3059,7 +3061,7 @@ export class Runner {
|
|
|
3059
3061
|
if (stopHook || finalVerificationOn) {
|
|
3060
3062
|
let consecutiveBlocks = 0;
|
|
3061
3063
|
prepared.harness.setStopGate(async () => {
|
|
3062
|
-
if (prepared.abortController.signal.aborted || prepared.
|
|
3064
|
+
if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined || prepared.batchHaltRef.current !== undefined)
|
|
3063
3065
|
return [];
|
|
3064
3066
|
if (finalVerificationOn &&
|
|
3065
3067
|
(rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
|
|
@@ -3300,7 +3302,7 @@ export class Runner {
|
|
|
3300
3302
|
degenerateOutput: { detect: (m) => isDegenerateCutMessage(m) },
|
|
3301
3303
|
promptTooLong: {
|
|
3302
3304
|
recover: async (attempt, turnSignal) => {
|
|
3303
|
-
if (prepared.abortController.signal.aborted || prepared.
|
|
3305
|
+
if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined)
|
|
3304
3306
|
return false;
|
|
3305
3307
|
if (turnSignal?.aborted === true)
|
|
3306
3308
|
return false;
|
|
@@ -3345,7 +3347,7 @@ export class Runner {
|
|
|
3345
3347
|
},
|
|
3346
3348
|
});
|
|
3347
3349
|
prepared.microCompact.inTurnCompactionRef.current = async (turnSignal, anchoredEstimate) => {
|
|
3348
|
-
if (prepared.abortController.signal.aborted || prepared.
|
|
3350
|
+
if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined)
|
|
3349
3351
|
return false;
|
|
3350
3352
|
if (turnSignal?.aborted === true)
|
|
3351
3353
|
return false;
|
|
@@ -3926,9 +3928,8 @@ export class Runner {
|
|
|
3926
3928
|
this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
|
|
3927
3929
|
}
|
|
3928
3930
|
}
|
|
3929
|
-
const
|
|
3930
|
-
const
|
|
3931
|
-
const durablyPaused = suspended || reviewPaused;
|
|
3931
|
+
const committedPause = prepared.pausedRef.current;
|
|
3932
|
+
const durablyPaused = committedPause !== undefined;
|
|
3932
3933
|
const interrupted = !durablyPaused &&
|
|
3933
3934
|
(threw !== undefined || timeout.fired || rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || abortedLive || final?.stopReason === "aborted");
|
|
3934
3935
|
let orphansClosed = 0;
|
|
@@ -3972,9 +3973,9 @@ export class Runner {
|
|
|
3972
3973
|
}
|
|
3973
3974
|
}
|
|
3974
3975
|
let migratedParked = { steer: 0, followUp: 0 };
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3976
|
+
if (committedPause !== undefined && this.deps.checkpointStore !== undefined) {
|
|
3977
|
+
const parkToken = committedPause.cause.token;
|
|
3978
|
+
const parkScope = committedPause.scope;
|
|
3978
3979
|
const store = this.deps.checkpointStore;
|
|
3979
3980
|
const carried = [];
|
|
3980
3981
|
for (const record of prepared.harness.readParkableUserInputs()) {
|
|
@@ -4067,7 +4068,7 @@ export class Runner {
|
|
|
4067
4068
|
}
|
|
4068
4069
|
}
|
|
4069
4070
|
if (prepared.resourceLedger &&
|
|
4070
|
-
(resume?.cp.gate.kind === "resource_limit" || prepared.
|
|
4071
|
+
(resume?.cp.gate.kind === "resource_limit" || prepared.pausedRef.current?.cause.gate.kind === "resource_limit")) {
|
|
4071
4072
|
stats.costMicroUsd += prepared.resourceLedger.spentMicroUsd;
|
|
4072
4073
|
stats.tokens += prepared.resourceLedger.spentTokens;
|
|
4073
4074
|
stats.turns += prepared.resourceLedger.spentTurns;
|
|
@@ -4118,46 +4119,26 @@ export class Runner {
|
|
|
4118
4119
|
gitCoreOverBudget: prepared.gitStatusRef.terminalCode === "irreducible_core_over_budget",
|
|
4119
4120
|
outputInvalid: rs.degrade.outputInvalid,
|
|
4120
4121
|
suspendLoop: prepared.suspendLoopRef.hit,
|
|
4121
|
-
|
|
4122
|
-
? {
|
|
4123
|
-
token: prepared.suspendRef.token,
|
|
4124
|
-
...(prepared.suspendRef.checkpointId !== undefined ? { checkpointId: prepared.suspendRef.checkpointId } : {}),
|
|
4125
|
-
gate: prepared.suspendRef.gate,
|
|
4126
|
-
...(prepared.suspendRef.restoreMode !== undefined ? { restoreMode: prepared.suspendRef.restoreMode } : {}),
|
|
4127
|
-
}
|
|
4128
|
-
: undefined,
|
|
4129
|
-
reviewRef: prepared.reviewRef.token !== undefined && prepared.reviewRef.gate !== undefined
|
|
4130
|
-
? {
|
|
4131
|
-
token: prepared.reviewRef.token,
|
|
4132
|
-
...(prepared.reviewRef.checkpointId !== undefined ? { checkpointId: prepared.reviewRef.checkpointId } : {}),
|
|
4133
|
-
gate: prepared.reviewRef.gate,
|
|
4134
|
-
...(prepared.reviewRef.restoreMode !== undefined ? { restoreMode: prepared.reviewRef.restoreMode } : {}),
|
|
4135
|
-
}
|
|
4136
|
-
: undefined,
|
|
4122
|
+
...(prepared.pausedRef.current !== undefined ? { paused: prepared.pausedRef.current.cause } : {}),
|
|
4137
4123
|
});
|
|
4138
4124
|
if (resume !== undefined &&
|
|
4139
4125
|
resume.pendingActionStarted !== true &&
|
|
4140
|
-
result.
|
|
4141
|
-
result.
|
|
4142
|
-
result.status !== "needs_review" &&
|
|
4126
|
+
result.terminal.kind !== "completed" &&
|
|
4127
|
+
result.terminal.kind !== "paused" &&
|
|
4143
4128
|
!(resumeDecisionWasNegative(resume) && resume.decisionDelivered === true) &&
|
|
4144
4129
|
resume.onEnvRestoreFailed !== undefined) {
|
|
4145
4130
|
try {
|
|
4146
4131
|
await resume.onEnvRestoreFailed("env_failed");
|
|
4147
|
-
|
|
4148
|
-
r.errorCode ??= "resume.reopened_unstarted";
|
|
4149
|
-
r.errorMessage = `${r.errorMessage ?? "the resume leg did not complete"} — the approved action never started, so the checkpoint was REOPENED (pending): the same approval can be resumed again, do not ask for a fresh one`;
|
|
4132
|
+
amendTerminal(result, "resume.reopened_unstarted", (m) => `${m ?? "the resume leg did not complete"} — the approved action never started, so the checkpoint was REOPENED (pending): the same approval can be resumed again, do not ask for a fresh one`);
|
|
4150
4133
|
}
|
|
4151
4134
|
catch {
|
|
4152
|
-
|
|
4153
|
-
r.errorCode ??= "resume.reopen_failed_unstarted";
|
|
4154
|
-
r.errorMessage = `${r.errorMessage ?? "the resume leg did not complete"} — the approved action never started, and reopening the checkpoint FAILED: its state is unprovable from here. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead`;
|
|
4135
|
+
amendTerminal(result, "resume.reopen_failed_unstarted", (m) => `${m ?? "the resume leg did not complete"} — the approved action never started, and reopening the checkpoint FAILED: its state is unprovable from here. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead`);
|
|
4155
4136
|
}
|
|
4156
4137
|
}
|
|
4157
4138
|
prepared.sealReadStateSeat?.(ownCommittedTailRef.current);
|
|
4158
4139
|
{
|
|
4159
4140
|
const wss = prepared.workspaceStateSettle;
|
|
4160
|
-
if (wss !== undefined && result.
|
|
4141
|
+
if (wss !== undefined && result.terminal.kind !== "paused") {
|
|
4161
4142
|
const curCwd = prepared.cwdRef?.current !== undefined && prepared.cwdRef.current !== wss.rootCanonical ? prepared.cwdRef.current : undefined;
|
|
4162
4143
|
const curWt = prepared.worktreeSessionRef?.current;
|
|
4163
4144
|
const changed = curCwd !== wss.restoredHandsCwd || curWt?.worktreeDir !== wss.restoredWorktreeDir;
|
|
@@ -4172,7 +4153,7 @@ export class Runner {
|
|
|
4172
4153
|
}
|
|
4173
4154
|
const stopFailureHook = (spec.hooks ?? this.deps.hooks)?.stopFailure;
|
|
4174
4155
|
if (stopFailureHook !== undefined &&
|
|
4175
|
-
result.
|
|
4156
|
+
result.terminal.kind === "failed" &&
|
|
4176
4157
|
final?.stopReason === "error" &&
|
|
4177
4158
|
!isDegenerateCutMessage(final) &&
|
|
4178
4159
|
threw === undefined &&
|
|
@@ -4180,12 +4161,13 @@ export class Runner {
|
|
|
4180
4161
|
!rs.degrade.outputInvalid &&
|
|
4181
4162
|
!prepared.suspendLoopRef.hit &&
|
|
4182
4163
|
!abortedLive &&
|
|
4183
|
-
result.
|
|
4164
|
+
result.terminal.code !== "conflict") {
|
|
4165
|
+
const failedCause = result.terminal;
|
|
4184
4166
|
try {
|
|
4185
4167
|
const failureSeat = await runHookSeat("stopFailure", { timeoutMs: prepared.hookTimeoutMs }, (sig) => stopFailureHook({
|
|
4186
4168
|
identity: prepared.hookIdentity,
|
|
4187
|
-
error:
|
|
4188
|
-
...(
|
|
4169
|
+
error: failedCause.message ?? "model error",
|
|
4170
|
+
...(failedCause.code !== undefined ? { errorKind: failedCause.code } : {}),
|
|
4189
4171
|
turns: stats.turns,
|
|
4190
4172
|
signal: sig,
|
|
4191
4173
|
}));
|
|
@@ -4205,12 +4187,13 @@ export class Runner {
|
|
|
4205
4187
|
}
|
|
4206
4188
|
}
|
|
4207
4189
|
}
|
|
4190
|
+
const terminalFace = terminalProjection(result.terminal);
|
|
4208
4191
|
emitDelegationLifecycle({
|
|
4209
4192
|
phase: "terminal",
|
|
4210
4193
|
identity: prepared.hookIdentity,
|
|
4211
|
-
status:
|
|
4194
|
+
status: terminalFace.status,
|
|
4212
4195
|
turns: stats.turns,
|
|
4213
|
-
...(
|
|
4196
|
+
...(terminalFace.errorCode !== undefined ? { errorCode: terminalFace.errorCode } : {}),
|
|
4214
4197
|
});
|
|
4215
4198
|
if (taskIdRef !== undefined)
|
|
4216
4199
|
taskIdRef.delegationTerminalOwed = undefined;
|
|
@@ -4218,8 +4201,8 @@ export class Runner {
|
|
|
4218
4201
|
result.degraded = rs.degrade.degraded;
|
|
4219
4202
|
if (prepared.outputRef.set)
|
|
4220
4203
|
result.structuredOutput = prepared.outputRef.value;
|
|
4221
|
-
const committedToken = prepared.
|
|
4222
|
-
const committedScope = prepared.
|
|
4204
|
+
const committedToken = prepared.pausedRef.current?.cause.token;
|
|
4205
|
+
const committedScope = prepared.pausedRef.current?.scope;
|
|
4223
4206
|
if (committedToken !== undefined) {
|
|
4224
4207
|
const store = resolveCheckpointStore(spec, this.deps);
|
|
4225
4208
|
if (store && committedScope !== undefined) {
|
|
@@ -4244,8 +4227,8 @@ export class Runner {
|
|
|
4244
4227
|
version: 1,
|
|
4245
4228
|
taskId: rs.telemetry.taskId,
|
|
4246
4229
|
runId: rs.telemetry.runId,
|
|
4247
|
-
status:
|
|
4248
|
-
errorCode:
|
|
4230
|
+
status: terminalFace.status,
|
|
4231
|
+
errorCode: terminalFace.errorCode,
|
|
4249
4232
|
turns: stats.turns,
|
|
4250
4233
|
tokens: stats.tokens,
|
|
4251
4234
|
...(rs.telemetry.unpricedSpend ? {} : { costMicroUsd: stats.costMicroUsd }),
|
|
@@ -4261,7 +4244,7 @@ export class Runner {
|
|
|
4261
4244
|
ts: Date.now(),
|
|
4262
4245
|
}));
|
|
4263
4246
|
setResult(result);
|
|
4264
|
-
if (parentToolCallId !== undefined &&
|
|
4247
|
+
if (parentToolCallId !== undefined && result.terminal.kind !== "paused") {
|
|
4265
4248
|
const terminalTick = {
|
|
4266
4249
|
type: "task_progress",
|
|
4267
4250
|
taskId: rs.telemetry.taskId,
|
|
@@ -4271,7 +4254,7 @@ export class Runner {
|
|
|
4271
4254
|
...(subagentName ? { name: subagentName } : {}),
|
|
4272
4255
|
model: prepared.model.id,
|
|
4273
4256
|
usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
|
|
4274
|
-
status: result.
|
|
4257
|
+
status: result.terminal.kind === "completed" ? "completed" : "failed",
|
|
4275
4258
|
...ident(),
|
|
4276
4259
|
};
|
|
4277
4260
|
queue.push(terminalTick);
|
|
@@ -4289,7 +4272,7 @@ export class Runner {
|
|
|
4289
4272
|
queue.push({ type: "done", result });
|
|
4290
4273
|
queue.close();
|
|
4291
4274
|
await prepared.fileHistoryBoundary?.settle();
|
|
4292
|
-
if (spec.suggestNextPrompts && result.
|
|
4275
|
+
if (spec.suggestNextPrompts && result.terminal.kind === "completed") {
|
|
4293
4276
|
onSuggestions(this.suggestNextPrompts(spec, prepared, result));
|
|
4294
4277
|
}
|
|
4295
4278
|
}
|
|
@@ -4297,15 +4280,13 @@ export class Runner {
|
|
|
4297
4280
|
const ownedEnvDying = prepared.ownedEnv !== undefined &&
|
|
4298
4281
|
hasDestroy(prepared.ownedEnv) &&
|
|
4299
4282
|
isIsolated(prepared.ownedEnv) &&
|
|
4300
|
-
prepared.
|
|
4301
|
-
prepared.reviewRef.token === undefined;
|
|
4283
|
+
prepared.pausedRef.current === undefined;
|
|
4302
4284
|
await settleTeardownLeg(() => defaultTaskRegistry.settleKilledForOwner({ owner: spec.taskId ?? prepared.sessionId, scope: spec.principal ?? "default", sessionId: prepared.sessionId }, {
|
|
4303
4285
|
source: abortedLive &&
|
|
4304
4286
|
!timeout.fired &&
|
|
4305
4287
|
!rs.limits.turnsExceeded &&
|
|
4306
4288
|
rs.limits.budgetHit === undefined &&
|
|
4307
|
-
prepared.
|
|
4308
|
-
prepared.reviewRef.token === undefined
|
|
4289
|
+
prepared.pausedRef.current === undefined
|
|
4309
4290
|
? "user"
|
|
4310
4291
|
: "parent",
|
|
4311
4292
|
skipSessionScoped: true,
|
|
@@ -4485,6 +4466,7 @@ export class Runner {
|
|
|
4485
4466
|
break;
|
|
4486
4467
|
case "tool_approval":
|
|
4487
4468
|
case "resource_limit":
|
|
4469
|
+
case "review":
|
|
4488
4470
|
case "plan_review": {
|
|
4489
4471
|
throw new CheckpointError("wake.gate_pending", `cannot wake: this checkpoint still carries a pending gate-typed action (pendingAction "${pa.kind}" ` +
|
|
4490
4472
|
`on a "${gk}" gate — inconsistent row) — wake is not a decision channel; resolve it through its ` +
|
|
@@ -4505,14 +4487,12 @@ export class Runner {
|
|
|
4505
4487
|
"wake would burn the checkpoint on a blank continuation; supply `message` or park a steer first");
|
|
4506
4488
|
}
|
|
4507
4489
|
}
|
|
4508
|
-
const gateMatch =
|
|
4509
|
-
(cp.gate.kind === "human" && outcomeGate === "policy_ask") ||
|
|
4510
|
-
(cp.gate.kind === "irreversible_ask" && outcomeGate === "policy_ask") ||
|
|
4511
|
-
(cp.gate.kind === "resource_limit" && outcomeGate === "resource_limit") ||
|
|
4512
|
-
(cp.gate.kind === "needs_review" && outcomeGate === "dry_run_review") ||
|
|
4513
|
-
(cp.gate.kind === "plan_review" && outcomeGate === "plan_review");
|
|
4490
|
+
const gateMatch = isGateKind(cp.gate.kind) && resumeGateMatches(cp.gate.kind, outcomeGate);
|
|
4514
4491
|
if (!gateMatch) {
|
|
4515
|
-
throw new CheckpointError("checkpoint.gate_mismatch", `resume outcome (gate "${describeSuppliedValue(outcomeGate)}") does not match checkpoint gate "${cp.gate.kind}" — resume serves
|
|
4492
|
+
throw new CheckpointError("checkpoint.gate_mismatch", `resume outcome (gate "${describeSuppliedValue(outcomeGate)}") does not match checkpoint gate "${cp.gate.kind}" — resume serves ${Object.entries(PAUSE_REGISTRY)
|
|
4493
|
+
.filter(([, row]) => !row.terminal)
|
|
4494
|
+
.map(([k, row]) => `${k}/${row.resume}`)
|
|
4495
|
+
.join(", ")}`);
|
|
4516
4496
|
}
|
|
4517
4497
|
let plainReviewOutcome;
|
|
4518
4498
|
if (cp.gate.kind === "plan_review") {
|
|
@@ -4619,25 +4599,27 @@ export class Runner {
|
|
|
4619
4599
|
if (decision !== "allow" && decision !== "deny") {
|
|
4620
4600
|
throw new CheckpointError("checkpoint.invalid_outcome", `resume decision "${describeSuppliedValue(decision)}" is outside the policy_ask domain — a decide is exactly "allow" or "deny"; refusing pre-CAS, the checkpoint stays pending`);
|
|
4621
4601
|
}
|
|
4622
|
-
const
|
|
4623
|
-
if (
|
|
4624
|
-
throw new CheckpointError("checkpoint.invalid_outcome", `resume
|
|
4602
|
+
const hostDecisionRaw = decide.hostDecision;
|
|
4603
|
+
if (typeof hostDecisionRaw !== "object" || hostDecisionRaw === null) {
|
|
4604
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries no hostDecision — a policy_ask decide names who decided it ({ decidedBy: "person" | "sla_timeout", approver? }); refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
|
|
4605
|
+
}
|
|
4606
|
+
const decidedBy = hostDecisionRaw.decidedBy;
|
|
4607
|
+
if (decidedBy !== "person" && decidedBy !== "sla_timeout") {
|
|
4608
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume hostDecision.decidedBy "${describeSuppliedValue(decidedBy)}" is outside the closed set — it is exactly "person" or "sla_timeout"; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
|
|
4625
4609
|
}
|
|
4626
|
-
if (
|
|
4627
|
-
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow"
|
|
4628
|
-
"supply \"human\", or omit the field if the allow came from configuration rather than a person; refusing pre-CAS, the checkpoint stays pending", { field: "settledBy" });
|
|
4610
|
+
if (decidedBy === "sla_timeout" && decision === "allow") {
|
|
4611
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" decided by "sla_timeout" — an elapsed SLA window cannot be what approved an action that EXECUTES; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
|
|
4629
4612
|
}
|
|
4630
|
-
|
|
4631
|
-
|
|
4613
|
+
if (decide.gateOutcome !== undefined) {
|
|
4614
|
+
throw new CheckpointError("checkpoint.invalid_outcome", "resume carries a gateOutcome — the settlement record is minted by core from the host's decision facts and the row's origin, never supplied; refusing pre-CAS, the checkpoint stays pending", { field: "hostDecision" });
|
|
4615
|
+
}
|
|
4616
|
+
const attribution = screenApproverAttribution(hostDecisionRaw.approver);
|
|
4632
4617
|
if (attribution.defect !== undefined) {
|
|
4633
4618
|
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries an approver attribution this engine refuses: ${attribution.defect}; refusing pre-CAS, the checkpoint stays pending`, { field: "approver" });
|
|
4634
4619
|
}
|
|
4635
|
-
if (attribution.approver !== undefined && settledBy === undefined) {
|
|
4636
|
-
throw new CheckpointError("checkpoint.invalid_outcome", `resume names an approver but no settledBy — an attribution says WHOSE settlement it was and needs the word that says what KIND of end it was beside it; ` +
|
|
4637
|
-
`supply settledBy ("human" / "timeout" / "aborted"), or omit the approver; refusing pre-CAS, the checkpoint stays pending`, { field: "approver" });
|
|
4638
|
-
}
|
|
4639
4620
|
const reason = decide.reason;
|
|
4640
4621
|
assertOutcomeText(reason, "reason");
|
|
4622
|
+
const approverCell = attribution.approver !== undefined ? { approver: attribution.approver } : {};
|
|
4641
4623
|
plainPolicyOutcome = {
|
|
4642
4624
|
gate: "policy_ask",
|
|
4643
4625
|
boundCallId: decide.boundCallId,
|
|
@@ -4646,8 +4628,7 @@ export class Runner {
|
|
|
4646
4628
|
...(updatedInput !== undefined ? { updatedInput } : {}),
|
|
4647
4629
|
...(reason !== undefined ? { reason } : {}),
|
|
4648
4630
|
...(redeemedAnswer !== undefined ? { answer: redeemedAnswer } : {}),
|
|
4649
|
-
|
|
4650
|
-
...(attribution.approver !== undefined ? { approver: attribution.approver } : {}),
|
|
4631
|
+
hostDecision: { decidedBy, ...approverCell },
|
|
4651
4632
|
};
|
|
4652
4633
|
if (plainPolicyOutcome.decision === "deny" && plainPolicyOutcome.reason && sanitizeUntrustedText(plainPolicyOutcome.reason) !== plainPolicyOutcome.reason) {
|
|
4653
4634
|
throw new CheckpointError("checkpoint.invalid_outcome", "resume deny/reject reason must not contain a </system-reminder> tag");
|
|
@@ -4701,11 +4682,7 @@ export class Runner {
|
|
|
4701
4682
|
throw new CheckpointError("checkpoint.unsupported_version", `checkpoint version ${checkpointVersionOf(cp)} is newer than this worker supports (max ${MAX_SUPPORTED_CHECKPOINT_VERSION})`, { reason: "version_newer" });
|
|
4702
4683
|
}
|
|
4703
4684
|
const preCasGateBit = cp.gate.kind === "irreversible_ask" ? cp.gate.realApproval : undefined;
|
|
4704
|
-
const preCasBitWellFormed = preCasGateBit !== undefined &&
|
|
4705
|
-
typeof preCasGateBit === "object" &&
|
|
4706
|
-
(preCasGateBit.origin === "org_rule" ||
|
|
4707
|
-
preCasGateBit.origin === "org_unavailable" ||
|
|
4708
|
-
preCasGateBit.origin === "policy");
|
|
4685
|
+
const preCasBitWellFormed = preCasGateBit !== undefined && typeof preCasGateBit === "object" && isAskOrigin(preCasGateBit.origin);
|
|
4709
4686
|
const faceSaysGoverned = (cp.state.readFace?.realApproval) === true;
|
|
4710
4687
|
const preCasBitViolation = checkpointVersionOf(cp) === REAL_APPROVAL_CHECKPOINT_VERSION
|
|
4711
4688
|
? !preCasBitWellFormed
|
|
@@ -4716,7 +4693,7 @@ export class Runner {
|
|
|
4716
4693
|
: preCasGateBit !== undefined && !preCasBitWellFormed;
|
|
4717
4694
|
if (preCasBitViolation) {
|
|
4718
4695
|
throw new CheckpointError("checkpoint.invalid_outcome", checkpointVersionOf(cp) >= REAL_APPROVAL_CHECKPOINT_VERSION
|
|
4719
|
-
? `a v${checkpointVersionOf(cp)} checkpoint's realApproval gate bit must be well-formed (origin
|
|
4696
|
+
? `a v${checkpointVersionOf(cp)} checkpoint's realApproval gate bit must be well-formed (origin a member of the closed ask-origin set)${checkpointVersionOf(cp) === REAL_APPROVAL_CHECKPOINT_VERSION ? " and present on an irreversible_ask gate" : ""} — this row's is not; refusing to resume a damaged real-approval row (corruption / downgrade guard), the checkpoint stays pending`
|
|
4720
4697
|
: `a v${checkpointVersionOf(cp)} checkpoint carries a realApproval gate bit no release of that version ever minted — refusing to honor a fabricated origin (corruption / forgery guard), the checkpoint stays pending`, { reason: checkpointVersionOf(cp) >= REAL_APPROVAL_CHECKPOINT_VERSION ? "real_approval_damaged" : "real_approval_forged" });
|
|
4721
4698
|
}
|
|
4722
4699
|
{
|
|
@@ -4736,9 +4713,8 @@ export class Runner {
|
|
|
4736
4713
|
: `a v${checkpointVersionOf(cp)} checkpoint carries a read-face section no release of that version ever minted — refusing to honor a fabricated read-face state (corruption / forgery guard), the checkpoint stays pending`, { reason: checkpointVersionOf(cp) >= FACE_CHECKPOINT_VERSION ? "read_face_damaged" : "read_face_forged" });
|
|
4737
4714
|
}
|
|
4738
4715
|
}
|
|
4739
|
-
if ((preCasGateBit
|
|
4740
|
-
this
|
|
4741
|
-
throw new CheckpointError("checkpoint.unsupported_version", `this checkpoint's approval was minted under organization governance (${preCasGateBit.origin}) and this worker has no org adjudication wiring (its permission-rule store carries no org partition) — a governed approval may only be redeemed where governance can be enforced; the checkpoint stays pending, resume it on an org-wired worker`, { reason: "governed_unwired" });
|
|
4716
|
+
if (realApprovalOrgFact(preCasGateBit) !== undefined && this.deps.permissionRuleStore?.partitions.org !== true) {
|
|
4717
|
+
throw new CheckpointError("checkpoint.unsupported_version", `this checkpoint's approval was minted under organization governance (org ${String(realApprovalOrgFact(preCasGateBit))}, routed as ${String(preCasGateBit?.origin)}) and this worker has no org adjudication wiring (its permission-rule store carries no org partition) — a governed approval may only be redeemed where governance can be enforced; the checkpoint stays pending, resume it on an org-wired worker`, { reason: "governed_unwired" });
|
|
4742
4718
|
}
|
|
4743
4719
|
const retiredWalltimeTotal = cp.resourceLedger?.totalWalltimeSec;
|
|
4744
4720
|
if (retiredWalltimeTotal !== undefined) {
|
|
@@ -4995,6 +4971,18 @@ export class Runner {
|
|
|
4995
4971
|
}
|
|
4996
4972
|
}
|
|
4997
4973
|
}
|
|
4974
|
+
if (plainPolicyOutcome !== undefined) {
|
|
4975
|
+
const parkedOrigin = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.origin : undefined;
|
|
4976
|
+
if (!isAskOrigin(parkedOrigin)) {
|
|
4977
|
+
throw new CheckpointError("checkpoint.unsupported_version", "this checkpoint's pending approval carries no origin word (a row minted before the ask's origin was persisted on the park) — its settlement cannot be recorded; the checkpoint stays pending");
|
|
4978
|
+
}
|
|
4979
|
+
const facts = plainPolicyOutcome.hostDecision;
|
|
4980
|
+
const approverCell = facts.approver !== undefined ? { approver: facts.approver } : {};
|
|
4981
|
+
const settlement = facts.decidedBy === "sla_timeout"
|
|
4982
|
+
? { kind: "park_sla_expired", who: { party: "host", ...approverCell }, when: Date.now() }
|
|
4983
|
+
: { kind: plainPolicyOutcome.decision === "allow" ? "human_allowed" : "human_refused", who: { party: "person", ...approverCell }, when: Date.now(), ...(plainPolicyOutcome.decision === "deny" && plainPolicyOutcome.reason !== undefined ? { note: plainPolicyOutcome.reason } : {}) };
|
|
4984
|
+
plainPolicyOutcome = { ...plainPolicyOutcome, gateOutcome: mintGateOutcome({ ...(plainPolicyOutcome.decision === "deny" ? { deniedBy: "ask_resolution" } : {}), settled: { settlement, origin: parkedOrigin } }) };
|
|
4985
|
+
}
|
|
4998
4986
|
const outcomeForStore = plainPolicyOutcome ?? plainReviewOutcome ?? plainParkOutcome;
|
|
4999
4987
|
if (outcomeForStore === undefined) {
|
|
5000
4988
|
throw new CheckpointError("checkpoint.invalid_outcome", `resume outcome (gate "${describeSuppliedValue(outcomeGate)}") matched the checkpoint gate but no lane captured it — refusing pre-CAS rather than passing the caller's live object to the store and the resumed run`);
|
|
@@ -5237,13 +5225,15 @@ export class Runner {
|
|
|
5237
5225
|
const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput !== undefined ? outcome.updatedInput : pendingAction.args);
|
|
5238
5226
|
const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
|
|
5239
5227
|
emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
|
|
5240
|
-
const emitEnd = (isError, result) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError,
|
|
5228
|
+
const emitEnd = (isError, result, gate = outcome.gateOutcome) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, gate) });
|
|
5229
|
+
const vetoed = (layer) => mintGateOutcome({
|
|
5230
|
+
deniedBy: layer,
|
|
5231
|
+
...(outcome.gateOutcome?.settlement !== undefined && outcome.gateOutcome.origin !== undefined ? { settled: { settlement: outcome.gateOutcome.settlement, origin: outcome.gateOutcome.origin } } : {}),
|
|
5232
|
+
});
|
|
5241
5233
|
if (outcome.decision === "deny") {
|
|
5242
|
-
const defaultDenial = outcome.
|
|
5234
|
+
const defaultDenial = outcome.gateOutcome?.settlement?.kind === "park_sla_expired"
|
|
5243
5235
|
? `No one answered the approval request for the pending tool call "${pendingAction.toolName}" — the approval window elapsed with no answer, so it was not executed.`
|
|
5244
|
-
:
|
|
5245
|
-
? `The approval for the pending tool call "${pendingAction.toolName}" ended without anyone deciding it (it was cancelled or could not be delivered), so it was not executed.`
|
|
5246
|
-
: `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
|
|
5236
|
+
: `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
|
|
5247
5237
|
const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason, REVIEWER_NOTE_MAX_BODY) : defaultDenial;
|
|
5248
5238
|
emitEnd(true, { content: formatHookFeedback(reason, prepared.reminderMark) });
|
|
5249
5239
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason, prepared.reminderMark), true));
|
|
@@ -5270,17 +5260,17 @@ export class Runner {
|
|
|
5270
5260
|
(rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, resolvedArgs));
|
|
5271
5261
|
if (beltUnsafe) {
|
|
5272
5262
|
const editedDenial = formatHookFeedback(`The approver EDITED this call's input; the edited call is refused by the deployment's tool policy and was not executed${rechecked.action === "deny" && rechecked.message ? `: ${rechecked.message}` : " (the policy rewrote the edit to a value that was not approved)"}.`, prepared.reminderMark);
|
|
5273
|
-
emitEnd(true, { content: editedDenial });
|
|
5263
|
+
emitEnd(true, { content: editedDenial }, vetoed("policy"));
|
|
5274
5264
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
|
|
5275
5265
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5276
5266
|
return;
|
|
5277
5267
|
}
|
|
5278
5268
|
}
|
|
5279
5269
|
const gateRealApproval = resume.cp.gate.kind === "irreversible_ask" ? resume.cp.gate.realApproval : undefined;
|
|
5280
|
-
const gateOrgGoverned = gateRealApproval
|
|
5270
|
+
const gateOrgGoverned = realApprovalOrgFact(gateRealApproval) !== undefined;
|
|
5281
5271
|
if (gateOrgGoverned && prepared.permissionRuleOrg === undefined) {
|
|
5282
5272
|
const unwiredDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: its approval was minted under organization governance (${gateRealApproval.origin}), and this worker has no org adjudication wiring — a governed approval may only be redeemed where governance can be enforced. This approval is spent; re-issue the call on an org-wired worker.`, prepared.reminderMark);
|
|
5283
|
-
emitEnd(true, { content: unwiredDenial });
|
|
5273
|
+
emitEnd(true, { content: unwiredDenial }, vetoed("org"));
|
|
5284
5274
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, unwiredDenial, true));
|
|
5285
5275
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5286
5276
|
return;
|
|
@@ -5298,7 +5288,7 @@ export class Runner {
|
|
|
5298
5288
|
},
|
|
5299
5289
|
});
|
|
5300
5290
|
const blocked = org.status === "unavailable"
|
|
5301
|
-
? gateRealApproval
|
|
5291
|
+
? realApprovalOrgFact(gateRealApproval) === "unavailable"
|
|
5302
5292
|
? undefined
|
|
5303
5293
|
: orgWaitCancelled
|
|
5304
5294
|
? "this deployment is org-governed and the task was cancelled before the organization policy snapshot could be adjudicated"
|
|
@@ -5311,7 +5301,7 @@ export class Runner {
|
|
|
5311
5301
|
? ` Governance disclosures: ${inlineUntrusted(org.disclosures.join("; "), ORG_DISCLOSURE_MAX_CHARS)}.`
|
|
5312
5302
|
: "";
|
|
5313
5303
|
const orgDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${blocked}.${orgDisclosures} This approval is spent — the call has to be re-issued and approved again once organization policy permits it.`, prepared.reminderMark);
|
|
5314
|
-
emitEnd(true, { content: orgDenial });
|
|
5304
|
+
emitEnd(true, { content: orgDenial }, vetoed("org"));
|
|
5315
5305
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, orgDenial, true));
|
|
5316
5306
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5317
5307
|
return;
|
|
@@ -5327,7 +5317,7 @@ export class Runner {
|
|
|
5327
5317
|
}, prepared.abortController.signal));
|
|
5328
5318
|
if (narrowed.action === "deny") {
|
|
5329
5319
|
const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`, prepared.reminderMark);
|
|
5330
|
-
emitEnd(true, { content: narrowedDenial });
|
|
5320
|
+
emitEnd(true, { content: narrowedDenial }, vetoed("policy"));
|
|
5331
5321
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
|
|
5332
5322
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5333
5323
|
return;
|
|
@@ -5535,8 +5525,7 @@ export class Runner {
|
|
|
5535
5525
|
async teardownOwnedEnv(prepared) {
|
|
5536
5526
|
if (prepared.ownedEnv &&
|
|
5537
5527
|
hasDestroy(prepared.ownedEnv) &&
|
|
5538
|
-
prepared.
|
|
5539
|
-
prepared.reviewRef.token === undefined) {
|
|
5528
|
+
prepared.pausedRef.current === undefined) {
|
|
5540
5529
|
try {
|
|
5541
5530
|
await prepared.ownedEnv.destroy();
|
|
5542
5531
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CheckpointGate, CheckpointToken } from "../checkpoint-store.js";
|
|
2
|
+
import type { TaskResult, TaskStatus, TerminalCause } from "../types.js";
|
|
3
|
+
/** The plane words a {@link TerminalCause} reads as — one derivation, called by the faces that speak
|
|
4
|
+
* them. The paused arm's `status` is its gate kind's registry row (`suspended` for an approval,
|
|
5
|
+
* a resource slice or a platform pause; `needs_review` for a plan or dry-run review, which also
|
|
6
|
+
* carries the `review.pending` code the review consumers key on). */
|
|
7
|
+
export declare function terminalProjection(terminal: TerminalCause): {
|
|
8
|
+
status: TaskStatus;
|
|
9
|
+
errorCode?: string;
|
|
10
|
+
errorMessage?: string;
|
|
11
|
+
blockedReason?: string;
|
|
12
|
+
checkpointToken?: CheckpointToken;
|
|
13
|
+
checkpointId?: string;
|
|
14
|
+
checkpointGate?: CheckpointGate;
|
|
15
|
+
workspaceRestoreMode?: "snapshot" | "park_only";
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A compensation that changes WHY a settled result ended (a resume leg's approval reopened / could not
|
|
19
|
+
* be reopened) amends the CAUSE — the one record there is. The code fills only an empty seat (the leg's
|
|
20
|
+
* own terminal code, when it had one, still names the ending); the message is extended, not replaced.
|
|
21
|
+
*/
|
|
22
|
+
export declare function amendTerminal(result: TaskResult, code: string, message: (prior: string | undefined) => string): void;
|