@sema-agent/core 7.6.3 → 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 +33 -10
- 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 -16
- package/dist/core/runner/prepare-run-refs.js +0 -6
- package/dist/core/runner/runtask.d.ts +0 -68
- package/dist/core/runner/runtask.js +28 -452
- 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, forwardsSubagentEvents } 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,8 +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
|
-
if (forwardsSubagentEvents(spec))
|
|
1842
|
-
await drainForwardedFramesBeforeDone();
|
|
1843
1445
|
queue.push({ type: "done", result: resultValue });
|
|
1844
1446
|
queue.close();
|
|
1845
1447
|
});
|
|
@@ -4279,8 +3881,6 @@ export class Runner {
|
|
|
4279
3881
|
drainManualCompact("mooted");
|
|
4280
3882
|
}
|
|
4281
3883
|
manualCompactRef.emitMooted = undefined;
|
|
4282
|
-
if (forwardsSubagentEvents(spec))
|
|
4283
|
-
await drainForwardedFramesBeforeDone();
|
|
4284
3884
|
queue.push({ type: "done", result });
|
|
4285
3885
|
queue.close();
|
|
4286
3886
|
await prepared.fileHistoryBoundary?.settle();
|
|
@@ -4988,7 +4588,7 @@ export class Runner {
|
|
|
4988
4588
|
if (plainPolicyOutcome !== undefined) {
|
|
4989
4589
|
const parkedOrigin = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.origin : undefined;
|
|
4990
4590
|
if (!isAskOrigin(parkedOrigin)) {
|
|
4991
|
-
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");
|
|
4992
4592
|
}
|
|
4993
4593
|
const facts = plainPolicyOutcome.hostDecision;
|
|
4994
4594
|
const approverCell = facts.approver !== undefined ? { approver: facts.approver } : {};
|
|
@@ -5152,6 +4752,7 @@ export class Runner {
|
|
|
5152
4752
|
...(answerFace !== undefined ? { onQuestion: answerFace } : {}),
|
|
5153
4753
|
};
|
|
5154
4754
|
const reopenFn = store.reopen?.bind(store);
|
|
4755
|
+
const recordExecutionOutcome = (gate) => store.recordExecutionOutcome(token, cp.scope, gate);
|
|
5155
4756
|
const onEnvRestoreFailed = reopenFn
|
|
5156
4757
|
? async (reason) => {
|
|
5157
4758
|
const reopened = await reopenFn(token, cp.scope, reason);
|
|
@@ -5180,7 +4781,7 @@ export class Runner {
|
|
|
5180
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")
|
|
5181
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)");
|
|
5182
4783
|
}
|
|
5183
|
-
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);
|
|
5184
4785
|
}
|
|
5185
4786
|
async applyResumeDecision(prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
|
|
5186
4787
|
const { pendingAction } = resume.cp;
|
|
@@ -5236,10 +4837,18 @@ export class Runner {
|
|
|
5236
4837
|
const { outcome } = resume;
|
|
5237
4838
|
if (pendingAction.kind !== "tool_approval" || outcome.gate !== "policy_ask")
|
|
5238
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
|
+
}
|
|
5239
4844
|
const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput !== undefined ? outcome.updatedInput : pendingAction.args);
|
|
5240
4845
|
const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
|
|
5241
4846
|
emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
|
|
5242
|
-
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
|
+
};
|
|
5243
4852
|
const vetoed = (layer) => mintGateOutcome({
|
|
5244
4853
|
deniedBy: layer,
|
|
5245
4854
|
...(outcome.gateOutcome?.settlement !== undefined && outcome.gateOutcome.origin !== undefined ? { settled: { settlement: outcome.gateOutcome.settlement, origin: outcome.gateOutcome.origin } } : {}),
|
|
@@ -5249,7 +4858,7 @@ export class Runner {
|
|
|
5249
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.`
|
|
5250
4859
|
: `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
|
|
5251
4860
|
const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason, REVIEWER_NOTE_MAX_BODY) : defaultDenial;
|
|
5252
|
-
|
|
4861
|
+
await settleEnd(true, { content: formatHookFeedback(reason, prepared.reminderMark) });
|
|
5253
4862
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason, prepared.reminderMark), true));
|
|
5254
4863
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5255
4864
|
return;
|
|
@@ -5274,7 +4883,7 @@ export class Runner {
|
|
|
5274
4883
|
(rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, resolvedArgs));
|
|
5275
4884
|
if (beltUnsafe) {
|
|
5276
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);
|
|
5277
|
-
|
|
4886
|
+
await settleEnd(true, { content: editedDenial }, vetoed("policy"));
|
|
5278
4887
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
|
|
5279
4888
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5280
4889
|
return;
|
|
@@ -5284,7 +4893,7 @@ export class Runner {
|
|
|
5284
4893
|
const gateOrgGoverned = realApprovalOrgFact(gateRealApproval) !== undefined;
|
|
5285
4894
|
if (gateOrgGoverned && prepared.permissionRuleOrg === undefined) {
|
|
5286
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);
|
|
5287
|
-
|
|
4896
|
+
await settleEnd(true, { content: unwiredDenial }, vetoed("org"));
|
|
5288
4897
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, unwiredDenial, true));
|
|
5289
4898
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5290
4899
|
return;
|
|
@@ -5315,7 +4924,7 @@ export class Runner {
|
|
|
5315
4924
|
? ` Governance disclosures: ${inlineUntrusted(org.disclosures.join("; "), ORG_DISCLOSURE_MAX_CHARS)}.`
|
|
5316
4925
|
: "";
|
|
5317
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);
|
|
5318
|
-
|
|
4927
|
+
await settleEnd(true, { content: orgDenial }, vetoed("org"));
|
|
5319
4928
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, orgDenial, true));
|
|
5320
4929
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5321
4930
|
return;
|
|
@@ -5331,7 +4940,7 @@ export class Runner {
|
|
|
5331
4940
|
}, prepared.abortController.signal));
|
|
5332
4941
|
if (narrowed.action === "deny") {
|
|
5333
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);
|
|
5334
|
-
|
|
4943
|
+
await settleEnd(true, { content: narrowedDenial }, vetoed("policy"));
|
|
5335
4944
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
|
|
5336
4945
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5337
4946
|
return;
|
|
@@ -5364,13 +4973,13 @@ export class Runner {
|
|
|
5364
4973
|
d = { ...(d ?? {}), errorKind: src.errorKind };
|
|
5365
4974
|
return d;
|
|
5366
4975
|
})();
|
|
5367
|
-
|
|
4976
|
+
await settleEnd(true, { content: execError, ...(marks !== undefined ? { details: marks } : {}) });
|
|
5368
4977
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, execError, true));
|
|
5369
4978
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
5370
4979
|
return;
|
|
5371
4980
|
}
|
|
5372
4981
|
const executedIsError = res.isError === true;
|
|
5373
|
-
|
|
4982
|
+
await settleEnd(executedIsError, res);
|
|
5374
4983
|
onResolvedToolSuccess?.(pendingAction.toolName, executedIsError ? undefined : res.details);
|
|
5375
4984
|
const eid = await prepared.session.appendMessage({
|
|
5376
4985
|
role: "toolResult",
|
|
@@ -5552,36 +5161,3 @@ export class Runner {
|
|
|
5552
5161
|
export function runTask(spec, deps) {
|
|
5553
5162
|
return new Runner(deps).runTask(spec);
|
|
5554
5163
|
}
|
|
5555
|
-
const WALLTIME_SUSPEND_GRACE_SEC = 120;
|
|
5556
|
-
const TIMER_LATENESS_REPORT_MS = 5_000;
|
|
5557
|
-
function startTimeout(harness, abortController, remainingMs, softSuspendable = false) {
|
|
5558
|
-
const state = { fired: false, clear: () => { } };
|
|
5559
|
-
if (remainingMs !== undefined) {
|
|
5560
|
-
const hardMs = Math.max(0, remainingMs + (softSuspendable ? WALLTIME_SUSPEND_GRACE_SEC * 1000 : 0));
|
|
5561
|
-
const scheduledAtMs = Date.now() + hardMs;
|
|
5562
|
-
let leftMs = hardMs;
|
|
5563
|
-
let timer;
|
|
5564
|
-
const armChunk = () => {
|
|
5565
|
-
const chunk = Math.max(0, Math.min(leftMs, MAX_TIMER_DELAY_MS));
|
|
5566
|
-
timer = setTimeout(() => {
|
|
5567
|
-
leftMs -= chunk;
|
|
5568
|
-
if (leftMs > 0) {
|
|
5569
|
-
armChunk();
|
|
5570
|
-
return;
|
|
5571
|
-
}
|
|
5572
|
-
fireHardAbort();
|
|
5573
|
-
}, chunk);
|
|
5574
|
-
};
|
|
5575
|
-
const fireHardAbort = () => {
|
|
5576
|
-
if (state.fired)
|
|
5577
|
-
return;
|
|
5578
|
-
state.fired = true;
|
|
5579
|
-
state.latenessMs = Math.max(0, Date.now() - scheduledAtMs);
|
|
5580
|
-
abortController.abort();
|
|
5581
|
-
void harness.abort();
|
|
5582
|
-
};
|
|
5583
|
-
armChunk();
|
|
5584
|
-
state.clear = () => clearTimeout(timer);
|
|
5585
|
-
}
|
|
5586
|
-
return state;
|
|
5587
|
-
}
|
|
@@ -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;
|