@sema-agent/core 7.6.2 → 7.7.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 +36 -6
- package/dist/agents/peer-admission.d.ts +1 -1
- package/dist/brain/anthropic.js +8 -2
- package/dist/brain/open-responses.js +5 -3
- package/dist/brain/openai.js +31 -8
- package/dist/brain/reasoning.d.ts +32 -0
- package/dist/brain/reasoning.js +18 -0
- package/dist/core/auto-mode-defaults.d.ts +16 -0
- package/dist/core/auto-mode-defaults.js +1 -0
- package/dist/core/auto-mode.d.ts +19 -0
- package/dist/core/auto-mode.js +74 -56
- package/dist/core/checkpoint-execution-record.d.ts +110 -0
- package/dist/core/checkpoint-execution-record.js +49 -0
- package/dist/core/checkpoint-store.d.ts +88 -10
- package/dist/core/checkpoint-store.js +35 -2
- package/dist/core/engine-notice.d.ts +11 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/runner/clock-and-limits.d.ts +117 -0
- package/dist/core/runner/clock-and-limits.js +118 -0
- package/dist/core/runner/contracts.d.ts +10 -0
- package/dist/core/runner/decide-continuation.d.ts +98 -0
- package/dist/core/runner/decide-continuation.js +133 -0
- package/dist/core/runner/execution-record.d.ts +26 -0
- package/dist/core/runner/execution-record.js +19 -0
- package/dist/core/runner/git-leg-delivery.d.ts +28 -0
- package/dist/core/runner/git-leg-delivery.js +94 -0
- package/dist/core/runner/initial-run-state.d.ts +14 -0
- package/dist/core/runner/initial-run-state.js +11 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +17 -0
- package/dist/core/runner/prepare-run-refs.d.ts +0 -12
- package/dist/core/runner/prepare-run-refs.js +0 -5
- package/dist/core/runner/runtask.d.ts +0 -68
- package/dist/core/runner/runtask.js +28 -450
- package/dist/core/runner/steer-admission.d.ts +17 -0
- package/dist/core/runner/steer-admission.js +17 -0
- package/dist/core/runner/tool-end-body.d.ts +71 -0
- package/dist/core/runner/tool-end-body.js +74 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +85 -0
- package/dist/core/trace.d.ts +24 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/stores/file/checkpoint-store.d.ts +7 -0
- package/dist/stores/file/checkpoint-store.js +20 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +37 -1
|
@@ -7,13 +7,13 @@ 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
9
|
import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, resolveCheckpointStore, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, realApprovalOrgFact } from "../checkpoint-store.js";
|
|
10
|
-
import { GIT_STATUS_ECHO_PREVIEW,
|
|
11
|
-
import {
|
|
10
|
+
import { GIT_STATUS_ECHO_PREVIEW, stripGitStatusUnits } from "./git-status-frame.js";
|
|
11
|
+
import { settleExecutionRecord } from "./execution-record.js";
|
|
12
12
|
import { engineVersion } from "../version.js";
|
|
13
13
|
import { CONFIG_CATALOG_VERSION, declarationReasons, resolveEffectiveConfig } from "../../config/catalog.js";
|
|
14
14
|
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
|
-
import { ASK_USER_QUESTION_TOOL_NAME, canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace
|
|
16
|
+
import { ASK_USER_QUESTION_TOOL_NAME, canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace } from "../ask-question.js";
|
|
17
17
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
18
18
|
import { computeCostMicroUsd, isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
|
|
19
19
|
import { emitTrace } from "../trace.js";
|
|
@@ -39,12 +39,10 @@ import { amendTerminal, terminalProjection } from "./terminal-projection.js";
|
|
|
39
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";
|
|
40
40
|
import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
|
|
41
41
|
import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
|
|
42
|
-
import { drainForwardedFramesBeforeDone } from "./prepare-run-refs.js";
|
|
43
42
|
import { gatedCallIdOf } from "./park-commit.js";
|
|
44
43
|
import { placementValueOrAbsent } from "./checkpoint-scope.js";
|
|
45
44
|
import { SessionReadFileStates } from "./prepare-hands-readface.js";
|
|
46
45
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
47
|
-
import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
|
|
48
46
|
import { hasVerifiableStructureSignal } from "./grounding-signal.js";
|
|
49
47
|
import { hasDestroy, isIsolated } from "../remote-env.js";
|
|
50
48
|
import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
|
|
@@ -59,333 +57,29 @@ import { isAskOrigin } from "../ask-origin.js";
|
|
|
59
57
|
import { isGateKind, PAUSE_REGISTRY, resumeGateMatches } from "../pause-registry.js";
|
|
60
58
|
import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOfSuppliedLayer, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
|
|
61
59
|
import { mintGateOutcome } from "./gate-exit.js";
|
|
62
|
-
import { MCP_DELIVERY_VERDICTS } from "../mcp-failure.js";
|
|
63
60
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
64
61
|
import { discloseDroppedPending, isDelegatedAgentTerminal, isSystemInjectionPriority, isTerminalTaskNotification, PendingSessionNotifications, renderTaskNotificationXml, SYSTEM_INJECTION_PRIORITIES, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
65
62
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
66
63
|
import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
|
|
67
64
|
import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
|
|
68
|
-
import {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
budget: { remainingMicroUsd: undefined, maxCostMicroUsd: undefined, remainingTokens: undefined, maxTokensWindow: undefined, overBudget: () => undefined, streamCancel: false, callOutputChars: 0, lastStreamBudgetCheck: 0, projectedOverBudget: () => undefined },
|
|
75
|
-
turn: { callStartAt: undefined, firstTokenAt: undefined, turnUsage: undefined, turnUsageMissing: false, turnStopReason: undefined, lastTurnHadToolCalls: false, toolBatch: [] },
|
|
76
|
-
counters: { approachNoticesSent: 0, walltimeSyncBackstopFired: false, compactionFloor: 0, trimForceBackoff: false, repetitionCuts: 0, repetitionSpared: 0, repetitionEvents: [], REPETITION_EVENTS_CAP: 0, preemptIgnoredReported: false, wroteThisRun: false, finalVerifyInjections: 0, groundingSignalPreR9: false, groundingSignalPostR9: false, cadenceTurns: 0 },
|
|
77
|
-
attach: { attachmentsCfg: undefined, agentListingOn: false, skillsListingOn: false, attachState: undefined, dateState: undefined, instrProbe: undefined, instrState: undefined, sizeGuidelineState: undefined, attachmentsInjected: 0 },
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
const humanInputOrdinals = new WeakMap();
|
|
81
|
-
function nextHumanInputSeq(key) {
|
|
82
|
-
let box = humanInputOrdinals.get(key);
|
|
83
|
-
if (box === undefined) {
|
|
84
|
-
box = { n: 0 };
|
|
85
|
-
humanInputOrdinals.set(key, box);
|
|
86
|
-
}
|
|
87
|
-
return ++box.n;
|
|
88
|
-
}
|
|
89
|
-
function sameAcceptedSteerInput(a, b) {
|
|
90
|
-
return (a.payload === b.payload &&
|
|
91
|
-
a.trusted === b.trusted &&
|
|
92
|
-
a.priority === b.priority &&
|
|
93
|
-
a.actor?.id === b.actor?.id &&
|
|
94
|
-
a.actor?.hostAsserted === b.actor?.hostAsserted &&
|
|
95
|
-
a.actor?.issuer === b.actor?.issuer);
|
|
96
|
-
}
|
|
65
|
+
import { createRunState } from "./initial-run-state.js";
|
|
66
|
+
import { nextHumanInputSeq, sameAcceptedSteerInput } from "./steer-admission.js";
|
|
67
|
+
import { batchResponseDigest, reconciledToolEndBody, toolEndBodyFrom, toolResultMsg, writeFamilyOfCanonical } from "./tool-end-body.js";
|
|
68
|
+
import { answerFaceForRedeemedCall, assertOutcomeText, deepJsonEqual, DEFERRED_REISSUE, describeSuppliedValue, pendingContentAskCallId, resumeContinuation, resumeDecisionWasNegative, sameWinner } from "./decide-continuation.js";
|
|
69
|
+
import { awaitChargeWithSlowDisclosure, DEFAULT_PRECALL_OUTPUT_TOKENS, discloseUnevaluableWindow, ENV_DUE_GOVERNANCE_READ_BUDGET_MS, GOVERNANCE_READ_STALLED, LIMIT_APPROACH_DEFAULT_THRESHOLDS, limitApproachFrames, platformLimitTerminal, raceUntilDeadline, resolveMaxTurns, startTimeout, TIMER_LATENESS_REPORT_MS } from "./clock-and-limits.js";
|
|
70
|
+
import { gitRestateOption, resolveGitLegDelivery, wrapGitFrame } from "./git-leg-delivery.js";
|
|
97
71
|
const MAX_CONSECUTIVE_COMPACTION_FAILURES = 3;
|
|
98
72
|
const RESUME_PREFLIGHT_DEFAULT_TIMEOUT_MS = 10_000;
|
|
99
73
|
const STOP_HOOK_BLOCK_CAP = 8;
|
|
100
74
|
const COMPACTION_REGROWTH_FACTOR = 1.5;
|
|
101
75
|
const COMPACTION_FREED_EPSILON = 256;
|
|
102
|
-
const BATCH_RESPONSE_MAX_CHARS = 500;
|
|
103
76
|
const ORG_DISCLOSURE_MAX_CHARS = 600;
|
|
104
|
-
function batchResponseDigest(result) {
|
|
105
|
-
const content = result !== null && typeof result === "object" ? result.content : result;
|
|
106
|
-
if (content === undefined || content === null)
|
|
107
|
-
return undefined;
|
|
108
|
-
let text;
|
|
109
|
-
if (typeof content === "string") {
|
|
110
|
-
text = content;
|
|
111
|
-
}
|
|
112
|
-
else if (Array.isArray(content)) {
|
|
113
|
-
text = content
|
|
114
|
-
.map((c) => (c !== null && typeof c === "object" && typeof c.text === "string" ? c.text : ""))
|
|
115
|
-
.filter((t) => t.length > 0)
|
|
116
|
-
.join("\n");
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
try {
|
|
120
|
-
text = JSON.stringify(content) ?? "";
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (text.length === 0)
|
|
127
|
-
return undefined;
|
|
128
|
-
return text.length > BATCH_RESPONSE_MAX_CHARS
|
|
129
|
-
? `${text.slice(0, BATCH_RESPONSE_MAX_CHARS)}…[+${text.length - BATCH_RESPONSE_MAX_CHARS} chars truncated]`
|
|
130
|
-
: text;
|
|
131
|
-
}
|
|
132
77
|
const BATCH_CONTEXT_MIN_KEEP_BYTES = 160;
|
|
133
78
|
const BATCH_TRUNCATION_MARKER = "\n…[truncated]";
|
|
134
79
|
const SUGGESTIONS_DEFAULT_COUNT = 3;
|
|
135
80
|
const SUGGESTIONS_MAX_COUNT = 8;
|
|
136
81
|
const SUGGESTIONS_TIMEOUT_MS = 30_000;
|
|
137
82
|
const SUGGESTIONS_TRANSCRIPT_MESSAGES = 12;
|
|
138
|
-
const LIMIT_APPROACH_DEFAULT_THRESHOLDS = [0.8, 0.95];
|
|
139
|
-
const DEFAULT_PRECALL_OUTPUT_TOKENS = 4096;
|
|
140
|
-
function limitApproachFrames(index, axis, percent, mark) {
|
|
141
|
-
return index === 0
|
|
142
|
-
? `${openSystemReminder(mark)}[limit approach] This task has used about ${percent}% of its ${axis}. ` +
|
|
143
|
-
`Start converging: finish the step you are on, drop work that is not required for the deliverable, and ` +
|
|
144
|
-
`avoid starting anything you cannot complete within what is left.</system-reminder>`
|
|
145
|
-
: `${openSystemReminder(mark)}[limit approach] About ${percent}% of the ${axis} is gone. Deliver now: ` +
|
|
146
|
-
`stop exploring, write up what you have — including what is incomplete and what you would do next — ` +
|
|
147
|
-
`and end the task. Work that is not reported before the allowance runs out is lost.</system-reminder>`;
|
|
148
|
-
}
|
|
149
|
-
function resolveMaxTurns(limits) {
|
|
150
|
-
return limits?.maxTurns;
|
|
151
|
-
}
|
|
152
|
-
function resumeDecisionWasNegative(resume) {
|
|
153
|
-
const o = resume.outcome;
|
|
154
|
-
return o.decision === "reject" || o.decision === "deny";
|
|
155
|
-
}
|
|
156
|
-
const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
|
|
157
|
-
"NOT executed on resume. If you still need it, issue it again now.";
|
|
158
|
-
function toolEndBodyFrom(result, isError, gate, gatedCallIdOfRun) {
|
|
159
|
-
const o = toolOutputFrom(result);
|
|
160
|
-
const st = structuredFrom(result);
|
|
161
|
-
const det = isError ? result?.details : undefined;
|
|
162
|
-
const codeRaw = det?.code;
|
|
163
|
-
const kindRaw = det?.errorKind;
|
|
164
|
-
const code = typeof codeRaw === "string" ? codeRaw : typeof kindRaw === "string" ? kindRaw : undefined;
|
|
165
|
-
const deliveredRaw = det?.delivered;
|
|
166
|
-
const delivered = typeof deliveredRaw === "string" && MCP_DELIVERY_VERDICTS.includes(deliveredRaw) ? deliveredRaw : undefined;
|
|
167
|
-
const gatedCallId = code === "gate.parked" ? gatedCallIdOfRun : undefined;
|
|
168
|
-
return {
|
|
169
|
-
...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}),
|
|
170
|
-
...(st !== undefined ? { structured: st } : {}),
|
|
171
|
-
...(typeof code === "string" ? { errorCode: code } : {}),
|
|
172
|
-
...(delivered !== undefined ? { delivered } : {}),
|
|
173
|
-
...(gatedCallId !== undefined ? { gatedCallId } : {}),
|
|
174
|
-
...(gate !== undefined ? { gate } : {}),
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
export function reconciledToolEndBody(orphan) {
|
|
178
|
-
return toolEndBodyFrom({ content: orphan.text, details: { code: orphan.errorKind } }, true);
|
|
179
|
-
}
|
|
180
|
-
function deepJsonEqual(a, b) {
|
|
181
|
-
if (a === b)
|
|
182
|
-
return true;
|
|
183
|
-
if (typeof a !== typeof b || a === null || b === null || typeof a !== "object") {
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
const aArr = Array.isArray(a);
|
|
187
|
-
const bArr = Array.isArray(b);
|
|
188
|
-
if (aArr !== bArr)
|
|
189
|
-
return false;
|
|
190
|
-
if (aArr && bArr) {
|
|
191
|
-
if (a.length !== b.length)
|
|
192
|
-
return false;
|
|
193
|
-
for (let i = 0; i < a.length; i++) {
|
|
194
|
-
if (!deepJsonEqual(a[i], b[i]))
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
const aExtra = Object.keys(a).filter((k) => !isCanonicalIndexKey(k, a.length));
|
|
198
|
-
const bExtra = Object.keys(b).filter((k) => !isCanonicalIndexKey(k, b.length));
|
|
199
|
-
if (aExtra.length !== bExtra.length)
|
|
200
|
-
return false;
|
|
201
|
-
return aExtra.every((k) => Object.prototype.hasOwnProperty.call(b, k) && deepJsonEqual(Reflect.get(a, k), Reflect.get(b, k)));
|
|
202
|
-
}
|
|
203
|
-
const ao = a;
|
|
204
|
-
const bo = b;
|
|
205
|
-
const aKeys = Object.keys(ao);
|
|
206
|
-
const bKeys = Object.keys(bo);
|
|
207
|
-
if (aKeys.length !== bKeys.length)
|
|
208
|
-
return false;
|
|
209
|
-
return aKeys.every((k) => Object.prototype.hasOwnProperty.call(bo, k) && deepJsonEqual(ao[k], bo[k]));
|
|
210
|
-
}
|
|
211
|
-
function isCanonicalIndexKey(key, length) {
|
|
212
|
-
return /^(0|[1-9]\d*)$/.test(key) && Number(key) < length;
|
|
213
|
-
}
|
|
214
|
-
function sameWinner(incoming, persisted) {
|
|
215
|
-
return (incoming.boundCallId === persisted.boundCallId &&
|
|
216
|
-
incoming.decision === persisted.decision &&
|
|
217
|
-
deepJsonEqual(incoming.updatedInput, persisted.updatedInput) &&
|
|
218
|
-
deepJsonEqual(incoming.answer, persisted.answer) &&
|
|
219
|
-
(persisted.reason === undefined || incoming.reason === persisted.reason));
|
|
220
|
-
}
|
|
221
|
-
function pendingContentAskCallId(cp) {
|
|
222
|
-
return cp.pendingAction.kind === "tool_approval" && cp.pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME
|
|
223
|
-
? cp.pendingAction.toolCallId
|
|
224
|
-
: undefined;
|
|
225
|
-
}
|
|
226
|
-
function answerFaceForRedeemedCall(answer, redeemedCallId, base, redeemedQuestionsHash) {
|
|
227
|
-
let consumed = false;
|
|
228
|
-
const face = async (req, signal) => {
|
|
229
|
-
if (!consumed && req.toolCallId === redeemedCallId && boundInputHashOf(req.questions) === redeemedQuestionsHash) {
|
|
230
|
-
consumed = true;
|
|
231
|
-
return answer;
|
|
232
|
-
}
|
|
233
|
-
if (base !== undefined)
|
|
234
|
-
return base(req, signal);
|
|
235
|
-
throw new Error("this resumed leg's answer was bound to the decided question only — a new question has no answer on this leg");
|
|
236
|
-
};
|
|
237
|
-
return isLiveQuestionFace(base) ? face : markBoundOnlyQuestionFace(face);
|
|
238
|
-
}
|
|
239
|
-
function writeFamilyOfCanonical(name) {
|
|
240
|
-
if (name === "TaskCreate" || name === "TaskUpdate")
|
|
241
|
-
return "task";
|
|
242
|
-
if (name === "TodoWrite")
|
|
243
|
-
return "todo";
|
|
244
|
-
if (name === TOOL_SEARCH_NAME)
|
|
245
|
-
return "tool_search";
|
|
246
|
-
return undefined;
|
|
247
|
-
}
|
|
248
|
-
function toolResultMsg(toolCallId, toolName, text, isError) {
|
|
249
|
-
return {
|
|
250
|
-
role: "toolResult",
|
|
251
|
-
toolCallId,
|
|
252
|
-
toolName,
|
|
253
|
-
content: [{ type: "text", text }],
|
|
254
|
-
isError,
|
|
255
|
-
timestamp: Date.now(),
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
function describeSuppliedValue(value) {
|
|
259
|
-
return typeof value === "string" ? value : value === null ? "null" : typeof value;
|
|
260
|
-
}
|
|
261
|
-
function assertOutcomeText(value, field) {
|
|
262
|
-
if (value !== undefined && typeof value !== "string") {
|
|
263
|
-
throw new CheckpointError("checkpoint.invalid_outcome", `resume \`${field}\` is not a plain string (got ${typeof value}) — a decide's text payload is an operator's plain data, not a live object; refusing pre-CAS, the checkpoint stays pending`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function resumeContinuation(resume, mark) {
|
|
267
|
-
if (resume.outcome.gate === "wake") {
|
|
268
|
-
return formatHookFeedback("You were WOKEN from a parked pause by an operator. Before continuing, re-orient from the workspace: " +
|
|
269
|
-
"run `git status` and review your recent changes / last commits to confirm what is already done, then " +
|
|
270
|
-
"continue the remaining work. Do NOT restart the task or re-run work that is already committed.", mark);
|
|
271
|
-
}
|
|
272
|
-
if (resume.outcome.gate === "resource_limit") {
|
|
273
|
-
return formatHookFeedback("You were resumed after a pause. Before continuing, re-orient from the workspace: run `git status` and " +
|
|
274
|
-
"review your recent changes / last commits to confirm what is already done, then continue the " +
|
|
275
|
-
"remaining work. Do NOT restart the task or re-run work that is already committed.", mark);
|
|
276
|
-
}
|
|
277
|
-
if (resume.outcome.gate === "dry_run_review") {
|
|
278
|
-
const verdict = resume.outcome.decision === "approve"
|
|
279
|
-
? "Your predicted change was REVIEWED and APPROVED; it has been applied"
|
|
280
|
-
: `Your predicted change was REVIEWED and REJECTED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : ""}; it was NOT applied`;
|
|
281
|
-
return formatHookFeedback(`You were resumed after a dry-run review. ${verdict}. Before continuing, re-orient from the workspace ` +
|
|
282
|
-
"(run `git status` and review your recent changes) to confirm the current state, then continue the " +
|
|
283
|
-
"remaining work. Do NOT restart the task or re-run work that is already done.", mark);
|
|
284
|
-
}
|
|
285
|
-
if (resume.outcome.gate === "plan_review") {
|
|
286
|
-
if (resume.outcome.decision === "approve") {
|
|
287
|
-
return formatHookFeedback("Your proposed PLAN was REVIEWED and APPROVED. Proceed with that plan now — begin executing it. " +
|
|
288
|
-
"This is a RESUMED task; do NOT re-plan or restart from scratch, just carry out the approved plan.", mark);
|
|
289
|
-
}
|
|
290
|
-
if (resume.outcome.decision === "edit") {
|
|
291
|
-
if (!resume.outcome.editedPlan) {
|
|
292
|
-
return formatHookFeedback("Your proposed PLAN was REVIEWED and EDITED, but no revised plan text was supplied — proceed with your " +
|
|
293
|
-
"ORIGINAL plan as-is, begin executing it now. This is a RESUMED task; do NOT re-plan or restart from scratch.", mark);
|
|
294
|
-
}
|
|
295
|
-
return formatHookFeedback("Your proposed PLAN was REVIEWED and EDITED by a human reviewer. Proceed with the REVISED plan below " +
|
|
296
|
-
"(it supersedes your earlier plan); begin executing it now. This is a RESUMED task; do NOT re-plan " +
|
|
297
|
-
"or restart from scratch, just carry out the revised plan.\n\nThe REVISED plan to follow is:\n" +
|
|
298
|
-
delimitUntrusted("revised plan", resume.outcome.editedPlan), mark);
|
|
299
|
-
}
|
|
300
|
-
const why = resume.outcome.reason ? ` Reviewer note: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : "";
|
|
301
|
-
return formatHookFeedback(`Your proposed PLAN was REVIEWED and REJECTED; it was NOT executed.${why} Produce a NEW plan that ` +
|
|
302
|
-
"addresses the concern, then continue. This is a RESUMED task — re-plan from the current state; do " +
|
|
303
|
-
"NOT execute the rejected plan.", mark);
|
|
304
|
-
}
|
|
305
|
-
const { pendingAction } = resume.cp;
|
|
306
|
-
if (pendingAction.kind !== "tool_approval") {
|
|
307
|
-
return formatHookFeedback("This is a RESUMED task — continue from where you left off without restarting or re-running prior work.", mark);
|
|
308
|
-
}
|
|
309
|
-
const decided = resume.outcome.decision === "allow"
|
|
310
|
-
? "was APPROVED and has now been executed — its result is in the tool results above"
|
|
311
|
-
: `was DENIED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : ""}`;
|
|
312
|
-
const remaining = pendingAction.batchToolCallIds.filter((id) => id !== pendingAction.toolCallId && !pendingAction.completedCallIds.includes(id)).length;
|
|
313
|
-
const batchNote = remaining > 0
|
|
314
|
-
? ` ${remaining} other tool call(s) from that batch were returned as deferred and not run — re-issue any you still need.`
|
|
315
|
-
: "";
|
|
316
|
-
return formatHookFeedback(`The pending tool call "${pendingAction.toolName}" ${decided}.${batchNote} ` +
|
|
317
|
-
`This is a RESUMED task — every step before this point is already done and its results are in the ` +
|
|
318
|
-
`conversation above. Do NOT restart the task or re-run any tool you already ran; continue from this ` +
|
|
319
|
-
`exact point, building on the existing results, and finish the remaining work.`, mark);
|
|
320
|
-
}
|
|
321
|
-
const ENV_DUE_GOVERNANCE_READ_BUDGET_MS = 5_000;
|
|
322
|
-
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
323
|
-
const DEADLINE_TICK = Symbol("deadline-tick");
|
|
324
|
-
export const GOVERNANCE_READ_STALLED = Symbol("governance-read-stalled");
|
|
325
|
-
const CHARGE_SETTLE_DISCLOSE_MS = 10_000;
|
|
326
|
-
export async function awaitChargeWithSlowDisclosure(charge, onSlow, discloseAfterMs = CHARGE_SETTLE_DISCLOSE_MS) {
|
|
327
|
-
let timer = setTimeout(() => {
|
|
328
|
-
timer = undefined;
|
|
329
|
-
try {
|
|
330
|
-
onSlow();
|
|
331
|
-
}
|
|
332
|
-
catch {
|
|
333
|
-
}
|
|
334
|
-
}, discloseAfterMs);
|
|
335
|
-
try {
|
|
336
|
-
return await charge;
|
|
337
|
-
}
|
|
338
|
-
finally {
|
|
339
|
-
if (timer !== undefined)
|
|
340
|
-
clearTimeout(timer);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
export async function raceUntilDeadline(p, deadline) {
|
|
344
|
-
for (let firstPass = true;; firstPass = false) {
|
|
345
|
-
const remaining = deadline - Date.now();
|
|
346
|
-
if (remaining <= 0 && !firstPass)
|
|
347
|
-
return GOVERNANCE_READ_STALLED;
|
|
348
|
-
let timer;
|
|
349
|
-
const tick = new Promise((res) => {
|
|
350
|
-
timer = setTimeout(() => res(DEADLINE_TICK), Math.max(0, Math.min(remaining, MAX_TIMER_DELAY_MS)));
|
|
351
|
-
});
|
|
352
|
-
const out = await Promise.race([p, tick]).finally(() => clearTimeout(timer));
|
|
353
|
-
if (out !== DEADLINE_TICK)
|
|
354
|
-
return out;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
function platformLimitTerminal(reason, retryAfterMs, moment = "turn_boundary") {
|
|
358
|
-
const message = moment === "entry"
|
|
359
|
-
? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the task was refused before its first model call — nothing ran and nothing was spent. There is no checkpoint to suspend into at this point, so the caller re-submits after the window frees, in ${String(retryAfterMs ?? 0)}ms.`
|
|
360
|
-
: moment === "resume"
|
|
361
|
-
? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the resumed leg was stopped BEFORE its first model call — nothing was delivered or executed on this leg. The window frees in ${String(retryAfterMs ?? 0)}ms.`
|
|
362
|
-
: reason === "env_lifetime"
|
|
363
|
-
? "the execution environment's declared lifetime is expiring and the run could not be suspended durably (not eligible, or the suspend was refused — see remoteEnvFailures / onError). It was stopped at a clean turn boundary instead of being killed mid-turn when the platform reclaims the environment."
|
|
364
|
-
: `a deployment usage window is exhausted (RunnerDeps.usageWindows) and the run could not be suspended durably, so it was stopped at a clean turn boundary. The window frees in ${String(retryAfterMs ?? 0)}ms.`;
|
|
365
|
-
const e = new Error(message);
|
|
366
|
-
e.code = reason === "env_lifetime" ? "env.lifetime_expired" : "usage.window_exhausted";
|
|
367
|
-
if (retryAfterMs !== undefined)
|
|
368
|
-
e.retryAfterMs = retryAfterMs;
|
|
369
|
-
return e;
|
|
370
|
-
}
|
|
371
|
-
const disclosedUnevaluableWindow = new WeakSet();
|
|
372
|
-
function discloseUnevaluableWindow(u, prepared, tracer, taskId, onError) {
|
|
373
|
-
if (disclosedUnevaluableWindow.has(prepared))
|
|
374
|
-
return;
|
|
375
|
-
disclosedUnevaluableWindow.add(prepared);
|
|
376
|
-
emitTrace(tracer, () => ({ kind: "compaction.unevaluable", version: 1, taskId, estTokens: u.estTokens, ts: Date.now() }));
|
|
377
|
-
const modelClause = u.modelId !== undefined ? ` "${u.modelId}"` : "";
|
|
378
|
-
const cause = u.windowValue !== undefined
|
|
379
|
-
? `its \`${u.windowField}\` is ${u.windowValue}, which is not a positive number of tokens — that field takes precedence over the ` +
|
|
380
|
-
`others, so fixing or removing it is what restores the threshold`
|
|
381
|
-
: `it declares no context window at all (\`autoCompactTokens\` / \`contextTokens\` / \`contextWindow\` are all absent) — declare one to restore the threshold`;
|
|
382
|
-
try {
|
|
383
|
-
onError?.(new Error(`auto-compaction cannot run for model${modelClause}: ${cause}. Until then there is no threshold to compare against, so the ` +
|
|
384
|
-
`context will grow until the provider refuses the request. The conversation is already about ${u.estTokens} estimated tokens.`), { phase: "config", sessionId: prepared.sessionId });
|
|
385
|
-
}
|
|
386
|
-
catch {
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
83
|
function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
390
84
|
const { spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, walltimeMonotonicDeadline, timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact, runnerHooks } = deps;
|
|
391
85
|
let finalVerifySeenAtLastBoundary = 0;
|
|
@@ -1059,96 +753,6 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
1059
753
|
};
|
|
1060
754
|
return onTurnBoundary;
|
|
1061
755
|
}
|
|
1062
|
-
function wrapGitFrame(body, mark) {
|
|
1063
|
-
return mintSystemReminder(sanitizeUntrustedText(body, SHELLED_BODY_ENVELOPE_TAGS), mark);
|
|
1064
|
-
}
|
|
1065
|
-
async function resolveGitLegDelivery(prepared, cpMirror, report) {
|
|
1066
|
-
const ref = prepared.gitStatusRef;
|
|
1067
|
-
const frame = ref.frame;
|
|
1068
|
-
if (frame === undefined)
|
|
1069
|
-
return undefined;
|
|
1070
|
-
let prior;
|
|
1071
|
-
let pending = false;
|
|
1072
|
-
try {
|
|
1073
|
-
const branchReadout = await prepared.session.getGitAnnouncement?.();
|
|
1074
|
-
if (branchReadout !== undefined) {
|
|
1075
|
-
if (branchReadout.status === "pending") {
|
|
1076
|
-
pending = true;
|
|
1077
|
-
prior = { kind: branchReadout.kind, hash: branchReadout.hash };
|
|
1078
|
-
}
|
|
1079
|
-
else {
|
|
1080
|
-
prior = { kind: branchReadout.kind, hash: branchReadout.hash, ...(branchReadout.entryId !== undefined ? { entryId: branchReadout.entryId } : {}) };
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
else {
|
|
1084
|
-
const shaped = normalizeGitAnnouncement(cpMirror);
|
|
1085
|
-
if (shaped !== undefined) {
|
|
1086
|
-
if (shaped.pending === true || shaped.entryId === undefined) {
|
|
1087
|
-
pending = true;
|
|
1088
|
-
prior = { kind: shaped.kind, hash: shaped.hash };
|
|
1089
|
-
}
|
|
1090
|
-
else if (gitFrameContextVisible(await prepared.session.getBranch(), shaped.entryId)) {
|
|
1091
|
-
prior = { kind: shaped.kind, hash: shaped.hash, entryId: shaped.entryId };
|
|
1092
|
-
}
|
|
1093
|
-
else {
|
|
1094
|
-
pending = true;
|
|
1095
|
-
prior = { kind: shaped.kind, hash: shaped.hash };
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
catch (err) {
|
|
1101
|
-
pending = true;
|
|
1102
|
-
report(err instanceof Error ? err : new Error(String(err)));
|
|
1103
|
-
}
|
|
1104
|
-
if (prior !== undefined && !pending && prior.entryId !== undefined) {
|
|
1105
|
-
try {
|
|
1106
|
-
const newest = newestEngineGitFrame(await prepared.session.getBranch(), prepared.reminderMark);
|
|
1107
|
-
if (newest === undefined || newest.entryId !== prior.entryId)
|
|
1108
|
-
pending = true;
|
|
1109
|
-
}
|
|
1110
|
-
catch (err) {
|
|
1111
|
-
pending = true;
|
|
1112
|
-
report(err instanceof Error ? err : new Error(String(err)));
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
if (prior !== undefined)
|
|
1116
|
-
ref.announced = pending ? { ...prior, pending: true } : { ...prior };
|
|
1117
|
-
const negative = frame.kind === "unavailable" || frame.kind === "non-repo";
|
|
1118
|
-
if (negative) {
|
|
1119
|
-
if (prior === undefined && !pending) {
|
|
1120
|
-
let mustDisown;
|
|
1121
|
-
try {
|
|
1122
|
-
mustDisown = branchCarriesVisiblePositiveGitFrame(await prepared.session.getBranch(), prepared.reminderMark);
|
|
1123
|
-
}
|
|
1124
|
-
catch (err) {
|
|
1125
|
-
mustDisown = true;
|
|
1126
|
-
report(err instanceof Error ? err : new Error(String(err)));
|
|
1127
|
-
}
|
|
1128
|
-
return mustDisown ? frame.body : undefined;
|
|
1129
|
-
}
|
|
1130
|
-
if (prior !== undefined && !pending && prior.kind === frame.kind && prior.hash === frame.hash)
|
|
1131
|
-
return undefined;
|
|
1132
|
-
return frame.body;
|
|
1133
|
-
}
|
|
1134
|
-
if (!pending && prior !== undefined && prior.kind === frame.kind && prior.hash === frame.hash) {
|
|
1135
|
-
ref.protectedText = wrapGitFrame(frame.body, prepared.reminderMark);
|
|
1136
|
-
if (frame.kind === "full" && frame.shrunk !== undefined) {
|
|
1137
|
-
ref.wrappedShrink = { find: ref.protectedText, replace: wrapGitFrame(frame.shrunk.body, prepared.reminderMark) };
|
|
1138
|
-
}
|
|
1139
|
-
return undefined;
|
|
1140
|
-
}
|
|
1141
|
-
return frame.body;
|
|
1142
|
-
}
|
|
1143
|
-
function gitRestateOption(prepared) {
|
|
1144
|
-
const ref = prepared.gitStatusRef;
|
|
1145
|
-
if (ref.frame === undefined || ref.announced === undefined || ref.reassert === undefined)
|
|
1146
|
-
return {};
|
|
1147
|
-
const use = ref.overBudgetShrunk && ref.frame.shrunk !== undefined
|
|
1148
|
-
? { kind: "degraded", hash: ref.frame.shrunk.hash }
|
|
1149
|
-
: { kind: ref.frame.kind, hash: ref.frame.hash };
|
|
1150
|
-
return { gitRestate: { pending: use, land: ref.reassert } };
|
|
1151
|
-
}
|
|
1152
756
|
function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
1153
757
|
const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
|
|
1154
758
|
const internalsNotifier = createSafeNotifier({
|
|
@@ -1838,7 +1442,6 @@ export class Runner {
|
|
|
1838
1442
|
onError: (f) => console.warn(`[sema-core] ${f.site}: delegation-lifecycle observer threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
|
|
1839
1443
|
}), "runtask.onDelegationLifecycle");
|
|
1840
1444
|
}
|
|
1841
|
-
await drainForwardedFramesBeforeDone(spec);
|
|
1842
1445
|
queue.push({ type: "done", result: resultValue });
|
|
1843
1446
|
queue.close();
|
|
1844
1447
|
});
|
|
@@ -4278,7 +3881,6 @@ export class Runner {
|
|
|
4278
3881
|
drainManualCompact("mooted");
|
|
4279
3882
|
}
|
|
4280
3883
|
manualCompactRef.emitMooted = undefined;
|
|
4281
|
-
await drainForwardedFramesBeforeDone(spec);
|
|
4282
3884
|
queue.push({ type: "done", result });
|
|
4283
3885
|
queue.close();
|
|
4284
3886
|
await prepared.fileHistoryBoundary?.settle();
|
|
@@ -4986,7 +4588,7 @@ export class Runner {
|
|
|
4986
4588
|
if (plainPolicyOutcome !== undefined) {
|
|
4987
4589
|
const parkedOrigin = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.origin : undefined;
|
|
4988
4590
|
if (!isAskOrigin(parkedOrigin)) {
|
|
4989
|
-
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"
|
|
4591
|
+
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");
|
|
4990
4592
|
}
|
|
4991
4593
|
const facts = plainPolicyOutcome.hostDecision;
|
|
4992
4594
|
const approverCell = facts.approver !== undefined ? { approver: facts.approver } : {};
|
|
@@ -5150,6 +4752,7 @@ export class Runner {
|
|
|
5150
4752
|
...(answerFace !== undefined ? { onQuestion: answerFace } : {}),
|
|
5151
4753
|
};
|
|
5152
4754
|
const reopenFn = store.reopen?.bind(store);
|
|
4755
|
+
const recordExecutionOutcome = (gate) => store.recordExecutionOutcome(token, cp.scope, gate);
|
|
5153
4756
|
const onEnvRestoreFailed = reopenFn
|
|
5154
4757
|
? async (reason) => {
|
|
5155
4758
|
const reopened = await reopenFn(token, cp.scope, reason);
|
|
@@ -5178,7 +4781,7 @@ export class Runner {
|
|
|
5178
4781
|
? 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")
|
|
5179
4782
|
: 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)");
|
|
5180
4783
|
}
|
|
5181
|
-
return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}), ...(wakeHookContext !== undefined ? { wakeMessageHookContext: wakeHookContext } : {}) }, resumeInternals);
|
|
4784
|
+
return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, recordExecutionOutcome, ...(wakeMessage !== undefined ? { wakeMessage } : {}), ...(wakeHookContext !== undefined ? { wakeMessageHookContext: wakeHookContext } : {}) }, resumeInternals);
|
|
5182
4785
|
}
|
|
5183
4786
|
async applyResumeDecision(prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
|
|
5184
4787
|
const { pendingAction } = resume.cp;
|
|
@@ -5234,10 +4837,18 @@ export class Runner {
|
|
|
5234
4837
|
const { outcome } = resume;
|
|
5235
4838
|
if (pendingAction.kind !== "tool_approval" || outcome.gate !== "policy_ask")
|
|
5236
4839
|
return;
|
|
4840
|
+
const decidedGate = outcome.gateOutcome;
|
|
4841
|
+
if (decidedGate === undefined) {
|
|
4842
|
+
throw new CheckpointError("checkpoint.invalid_outcome", "pending action reached the resolver with no settlement record on its decision (gateOutcome) — refusing to execute or settle a decision that carries no record");
|
|
4843
|
+
}
|
|
5237
4844
|
const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput !== undefined ? outcome.updatedInput : pendingAction.args);
|
|
5238
4845
|
const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
|
|
5239
4846
|
emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
|
|
5240
|
-
const
|
|
4847
|
+
const settleEnd = async (isError, result, gate = decidedGate) => {
|
|
4848
|
+
emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, gate) });
|
|
4849
|
+
const word = await resume.recordExecutionOutcome(gate);
|
|
4850
|
+
settleExecutionRecord(this.deps.onNotice, word, { toolName: pendingAction.toolName, gate, sessionId: prepared.sessionId, runId: prepared.runId, scope: resume.cp.scope, ...(resume.cp.checkpointId !== undefined ? { checkpointId: resume.cp.checkpointId } : {}) });
|
|
4851
|
+
};
|
|
5241
4852
|
const vetoed = (layer) => mintGateOutcome({
|
|
5242
4853
|
deniedBy: layer,
|
|
5243
4854
|
...(outcome.gateOutcome?.settlement !== undefined && outcome.gateOutcome.origin !== undefined ? { settled: { settlement: outcome.gateOutcome.settlement, origin: outcome.gateOutcome.origin } } : {}),
|
|
@@ -5247,7 +4858,7 @@ export class Runner {
|
|
|
5247
4858
|
? `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.`
|
|
5248
4859
|
: `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
|
|
5249
4860
|
const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason, REVIEWER_NOTE_MAX_BODY) : defaultDenial;
|
|
5250
|
-
|
|
4861
|
+
await settleEnd(true, { content: formatHookFeedback(reason, prepared.reminderMark) });
|
|
5251
4862
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason, prepared.reminderMark), true));
|
|
5252
4863
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5253
4864
|
return;
|
|
@@ -5272,7 +4883,7 @@ export class Runner {
|
|
|
5272
4883
|
(rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, resolvedArgs));
|
|
5273
4884
|
if (beltUnsafe) {
|
|
5274
4885
|
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);
|
|
5275
|
-
|
|
4886
|
+
await settleEnd(true, { content: editedDenial }, vetoed("policy"));
|
|
5276
4887
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
|
|
5277
4888
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5278
4889
|
return;
|
|
@@ -5282,7 +4893,7 @@ export class Runner {
|
|
|
5282
4893
|
const gateOrgGoverned = realApprovalOrgFact(gateRealApproval) !== undefined;
|
|
5283
4894
|
if (gateOrgGoverned && prepared.permissionRuleOrg === undefined) {
|
|
5284
4895
|
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);
|
|
5285
|
-
|
|
4896
|
+
await settleEnd(true, { content: unwiredDenial }, vetoed("org"));
|
|
5286
4897
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, unwiredDenial, true));
|
|
5287
4898
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5288
4899
|
return;
|
|
@@ -5313,7 +4924,7 @@ export class Runner {
|
|
|
5313
4924
|
? ` Governance disclosures: ${inlineUntrusted(org.disclosures.join("; "), ORG_DISCLOSURE_MAX_CHARS)}.`
|
|
5314
4925
|
: "";
|
|
5315
4926
|
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);
|
|
5316
|
-
|
|
4927
|
+
await settleEnd(true, { content: orgDenial }, vetoed("org"));
|
|
5317
4928
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, orgDenial, true));
|
|
5318
4929
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5319
4930
|
return;
|
|
@@ -5329,7 +4940,7 @@ export class Runner {
|
|
|
5329
4940
|
}, prepared.abortController.signal));
|
|
5330
4941
|
if (narrowed.action === "deny") {
|
|
5331
4942
|
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);
|
|
5332
|
-
|
|
4943
|
+
await settleEnd(true, { content: narrowedDenial }, vetoed("policy"));
|
|
5333
4944
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
|
|
5334
4945
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5335
4946
|
return;
|
|
@@ -5362,13 +4973,13 @@ export class Runner {
|
|
|
5362
4973
|
d = { ...(d ?? {}), errorKind: src.errorKind };
|
|
5363
4974
|
return d;
|
|
5364
4975
|
})();
|
|
5365
|
-
|
|
4976
|
+
await settleEnd(true, { content: execError, ...(marks !== undefined ? { details: marks } : {}) });
|
|
5366
4977
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, execError, true));
|
|
5367
4978
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5368
4979
|
return;
|
|
5369
4980
|
}
|
|
5370
4981
|
const executedIsError = res.isError === true;
|
|
5371
|
-
|
|
4982
|
+
await settleEnd(executedIsError, res);
|
|
5372
4983
|
onResolvedToolSuccess?.(pendingAction.toolName, executedIsError ? undefined : res.details);
|
|
5373
4984
|
const eid = await prepared.session.appendMessage({
|
|
5374
4985
|
role: "toolResult",
|
|
@@ -5550,36 +5161,3 @@ export class Runner {
|
|
|
5550
5161
|
export function runTask(spec, deps) {
|
|
5551
5162
|
return new Runner(deps).runTask(spec);
|
|
5552
5163
|
}
|
|
5553
|
-
const WALLTIME_SUSPEND_GRACE_SEC = 120;
|
|
5554
|
-
const TIMER_LATENESS_REPORT_MS = 5_000;
|
|
5555
|
-
function startTimeout(harness, abortController, remainingMs, softSuspendable = false) {
|
|
5556
|
-
const state = { fired: false, clear: () => { } };
|
|
5557
|
-
if (remainingMs !== undefined) {
|
|
5558
|
-
const hardMs = Math.max(0, remainingMs + (softSuspendable ? WALLTIME_SUSPEND_GRACE_SEC * 1000 : 0));
|
|
5559
|
-
const scheduledAtMs = Date.now() + hardMs;
|
|
5560
|
-
let leftMs = hardMs;
|
|
5561
|
-
let timer;
|
|
5562
|
-
const armChunk = () => {
|
|
5563
|
-
const chunk = Math.max(0, Math.min(leftMs, MAX_TIMER_DELAY_MS));
|
|
5564
|
-
timer = setTimeout(() => {
|
|
5565
|
-
leftMs -= chunk;
|
|
5566
|
-
if (leftMs > 0) {
|
|
5567
|
-
armChunk();
|
|
5568
|
-
return;
|
|
5569
|
-
}
|
|
5570
|
-
fireHardAbort();
|
|
5571
|
-
}, chunk);
|
|
5572
|
-
};
|
|
5573
|
-
const fireHardAbort = () => {
|
|
5574
|
-
if (state.fired)
|
|
5575
|
-
return;
|
|
5576
|
-
state.fired = true;
|
|
5577
|
-
state.latenessMs = Math.max(0, Date.now() - scheduledAtMs);
|
|
5578
|
-
abortController.abort();
|
|
5579
|
-
void harness.abort();
|
|
5580
|
-
};
|
|
5581
|
-
armChunk();
|
|
5582
|
-
state.clear = () => clearTimeout(timer);
|
|
5583
|
-
}
|
|
5584
|
-
return state;
|
|
5585
|
-
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admission bookkeeping for HUMAN INPUTS accepted mid-run (design/171 §6.2–§6.3): the per-leg ordinal an
|
|
3
|
+
* accepted input is stamped with, and the field-for-field "is this repeat the same instruction" test the
|
|
4
|
+
* live steer verb applies under an already-used `inputId`. Two accepting arms share both — the stream's
|
|
5
|
+
* `steer` verb and the run body — which is why the ordinal is keyed on the leg's harness in a module-level
|
|
6
|
+
* WeakMap rather than held by either arm: one counter per leg, one ascending sequence across both.
|
|
7
|
+
*
|
|
8
|
+
* Run-loop machinery (layer 1), not a driven lane: nothing here takes an Input or hands back a Result, and
|
|
9
|
+
* nothing here reads host state. Named without the `run-` family prefix for that reason.
|
|
10
|
+
*/
|
|
11
|
+
import type { AcceptedSteerInput } from "./contracts.js";
|
|
12
|
+
export declare function nextHumanInputSeq(key: object): number;
|
|
13
|
+
/** design/171 §6.3 parity, LIVE leg — is a repeat under an already-accepted `inputId` the SAME
|
|
14
|
+
* instruction (an idempotent retry) or a different one wearing a used key? Compared field for field,
|
|
15
|
+
* exactly like the parked leg's replay check: the key alone is never evidence of a replay, and two
|
|
16
|
+
* callers colliding on one id must not silently lose the second instruction. */
|
|
17
|
+
export declare function sameAcceptedSteerInput(a: AcceptedSteerInput, b: AcceptedSteerInput): boolean;
|