@sema-agent/core 5.9.0 → 5.10.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 +40 -0
- package/dist/agents/roster-store.d.ts +1 -0
- package/dist/agents/send-message-tool.js +6 -0
- package/dist/agents/subagent.d.ts +30 -0
- package/dist/agents/subagent.js +61 -19
- package/dist/agents/teacher.js +15 -3
- package/dist/agents/team.js +10 -0
- package/dist/agents/verify.js +7 -0
- package/dist/brain/anthropic.js +27 -10
- package/dist/brain/open-responses.js +19 -4
- package/dist/brain/openai.js +32 -5
- package/dist/core/a2a.js +1 -1
- package/dist/core/memory-recall.js +8 -3
- package/dist/core/memory.d.ts +5 -0
- package/dist/core/memory.js +6 -4
- package/dist/core/runner/prepare-task.d.ts +8 -1
- package/dist/core/runner/prepare-task.js +45 -11
- package/dist/core/runner/runtask.d.ts +12 -0
- package/dist/core/runner/runtask.js +127 -22
- package/dist/core/runner/session-file-state-replay.d.ts +7 -0
- package/dist/core/runner/session-file-state-replay.js +56 -0
- package/dist/core/runner/synthetic-tools.js +1 -1
- package/dist/core/runner/tool-output-projection.js +5 -4
- package/dist/core/session-reconcile.d.ts +7 -3
- package/dist/core/session-reconcile.js +3 -2
- package/dist/core/strategy-store.d.ts +1 -1
- package/dist/core/strategy-store.js +27 -4
- package/dist/core/tools.js +9 -1
- package/dist/core/types.d.ts +5 -1
- package/dist/engine/loop/agent-loop.js +168 -22
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-governance.js +19 -0
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +4 -1
- package/dist/orchestration/workflow.js +1 -1
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/coordinator.js +1 -1
- package/dist/stores/file/memory-store.js +3 -7
- package/dist/tools/fs/fs-bash.js +3 -3
- package/dist/tools/fs/fs-shared.d.ts +1 -0
- package/dist/tools/fs/fs-shared.js +4 -0
- package/dist/tools/web.js +0 -1
- package/package.json +2 -2
|
@@ -30,6 +30,11 @@ export const RECALL_CAVEAT = "These notes were recalled as relevant, but they ar
|
|
|
30
30
|
"- If the user is about to act on your recommendation (not just asking about history), verify first.\n" +
|
|
31
31
|
"- If a note names a file, check the file exists; if it names a function or symbol, grep for it; if it names a flag or value, read the current source.\n" +
|
|
32
32
|
"- Memory is a snapshot from when it was written, not a live view — prefer `git log` or reading the code over recalling the snapshot.";
|
|
33
|
+
function isNewerNote(candidate, incumbent) {
|
|
34
|
+
if (candidate.timestampMissing || incumbent.timestampMissing)
|
|
35
|
+
return false;
|
|
36
|
+
return candidate.mtimeMs > incumbent.mtimeMs;
|
|
37
|
+
}
|
|
33
38
|
export function resolveLinkedIds(headers, selected, max) {
|
|
34
39
|
if (max <= 0 || selected.length === 0)
|
|
35
40
|
return [];
|
|
@@ -38,7 +43,7 @@ export function resolveLinkedIds(headers, selected, max) {
|
|
|
38
43
|
if (!h.name)
|
|
39
44
|
continue;
|
|
40
45
|
const prev = byName.get(h.name);
|
|
41
|
-
if (!prev || h
|
|
46
|
+
if (!prev || isNewerNote(h, prev))
|
|
42
47
|
byName.set(h.name, h);
|
|
43
48
|
}
|
|
44
49
|
const selectedIds = new Set(selected.map((r) => r.id));
|
|
@@ -100,8 +105,8 @@ export function validateSelectedIds(headers, ids, max) {
|
|
|
100
105
|
export function composeSelectiveBody(manifestText, selected, nowMs, linked = [], recallable = true) {
|
|
101
106
|
const renderNote = (r, label) => {
|
|
102
107
|
const ageMs = nowMs - r.mtimeMs;
|
|
103
|
-
const verify = ageMs > ONE_DAY_MS ? " — verify it's still current" : "";
|
|
104
|
-
const stale = ` (written ${formatMemoryAge(ageMs)}${verify})`;
|
|
108
|
+
const verify = r.timestampMissing || ageMs > ONE_DAY_MS ? " — verify it's still current" : "";
|
|
109
|
+
const stale = r.timestampMissing ? ` (write time unknown${verify})` : ` (written ${formatMemoryAge(ageMs)}${verify})`;
|
|
105
110
|
const prefix = label ? `${sanitizeUntrustedText(label, ["user_memory"])} ` : "";
|
|
106
111
|
return `- ${prefix}${sanitizeUntrustedText(r.text, ["user_memory"])}${stale}`;
|
|
107
112
|
};
|
package/dist/core/memory.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface MemoryNoteHeader {
|
|
|
40
40
|
id: string;
|
|
41
41
|
description: string;
|
|
42
42
|
mtimeMs: number;
|
|
43
|
+
timestampMissing?: true;
|
|
43
44
|
name?: string;
|
|
44
45
|
type?: string;
|
|
45
46
|
consolidationGenerated?: boolean;
|
|
@@ -84,6 +85,10 @@ export declare class InMemoryMemoryStore implements MemoryStore {
|
|
|
84
85
|
export declare function expandLexicalTerms(term: string): string[];
|
|
85
86
|
export declare function lexicalSearchMatch(query: string, text: string): boolean;
|
|
86
87
|
export declare function firstSentence(text: string): string;
|
|
88
|
+
export declare function parseNoteTimestamp(ts: unknown): {
|
|
89
|
+
mtimeMs: number;
|
|
90
|
+
timestampMissing?: true;
|
|
91
|
+
};
|
|
87
92
|
export interface NormalizedMemorySpec {
|
|
88
93
|
scopes: string[];
|
|
89
94
|
writeScope: string | null;
|
package/dist/core/memory.js
CHANGED
|
@@ -268,7 +268,7 @@ export class InMemoryMemoryStore {
|
|
|
268
268
|
return entries.map((e) => ({
|
|
269
269
|
id: e.id,
|
|
270
270
|
description: e.description ?? firstSentence(e.text),
|
|
271
|
-
|
|
271
|
+
...parseNoteTimestamp(e.ts),
|
|
272
272
|
...(e.name ? { name: e.name } : {}),
|
|
273
273
|
...(e.type ? { type: e.type } : {}),
|
|
274
274
|
...(e.consolidationGenerated ? { consolidationGenerated: true } : {}),
|
|
@@ -291,7 +291,7 @@ export class InMemoryMemoryStore {
|
|
|
291
291
|
id: e.id,
|
|
292
292
|
text: e.text,
|
|
293
293
|
description: e.description ?? firstSentence(e.text),
|
|
294
|
-
|
|
294
|
+
...parseNoteTimestamp(e.ts),
|
|
295
295
|
...(e.name ? { name: e.name } : {}),
|
|
296
296
|
...(e.type ? { type: e.type } : {}),
|
|
297
297
|
...(e.consolidationGenerated ? { consolidationGenerated: true } : {}),
|
|
@@ -347,9 +347,11 @@ export function firstSentence(text) {
|
|
|
347
347
|
const cut = dot >= 0 && dot < 160 ? dot + 1 : Math.min(t.length, 120);
|
|
348
348
|
return t.slice(0, cut).trim();
|
|
349
349
|
}
|
|
350
|
-
function
|
|
350
|
+
export function parseNoteTimestamp(ts) {
|
|
351
|
+
if (typeof ts !== "string" || ts.trim() === "")
|
|
352
|
+
return { mtimeMs: 0, timestampMissing: true };
|
|
351
353
|
const ms = Date.parse(`${ts.replace(" ", "T")}:00Z`);
|
|
352
|
-
return Number.isFinite(ms) ? ms : 0;
|
|
354
|
+
return Number.isFinite(ms) ? { mtimeMs: ms } : { mtimeMs: 0, timestampMissing: true };
|
|
353
355
|
}
|
|
354
356
|
export function normalizeMemorySpec(input) {
|
|
355
357
|
if (!input)
|
|
@@ -9,6 +9,7 @@ import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
|
9
9
|
import type { OnAsk, ToolPolicy } from "../tool-policy.js";
|
|
10
10
|
import { type ActiveSkillFrame } from "./active-skill-scope.js";
|
|
11
11
|
import type { SessionPermissionRules } from "../session-policy-store.js";
|
|
12
|
+
import { type RecoveredOrphan } from "../session-reconcile.js";
|
|
12
13
|
import { CacheBreakDetector, type ToolFingerprintInput } from "../cache-break-detector.js";
|
|
13
14
|
import { type BrainCallGuardrailRef } from "../../brain/timeout.js";
|
|
14
15
|
import { type OutputRef, type BlockedRef, type SkillListingEntry } from "./synthetic-tools.js";
|
|
@@ -18,7 +19,7 @@ import type { TaskNotificationPayload } from "../task-notification.js";
|
|
|
18
19
|
import { type CwdRef } from "../../tools/fs/index.js";
|
|
19
20
|
import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
20
21
|
import type { Runner } from "./runtask.js";
|
|
21
|
-
import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type PlatformLimitReason, type ResourceLimitReason } from "../checkpoint-store.js";
|
|
22
|
+
import { type CheckpointGate, type CheckpointState, type CheckpointStore, type CheckpointToken, type ResourceLedger, type PlatformLimitReason, type ResourceLimitReason } from "../checkpoint-store.js";
|
|
22
23
|
import type { ActiveWorktreeSession, AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
|
|
23
24
|
import type { NestedUsageAccum, RunnerDeps, TaskEvent, TaskLimits, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
|
|
24
25
|
import type { RepairBundle } from "../../agents/repair-loop.js";
|
|
@@ -42,6 +43,11 @@ export declare function checkpointScopeOf(spec: {
|
|
|
42
43
|
};
|
|
43
44
|
principal?: string;
|
|
44
45
|
}): string;
|
|
46
|
+
export declare function resolveCheckpointStore(spec: {
|
|
47
|
+
checkpointStore?: CheckpointStore | null;
|
|
48
|
+
}, deps: {
|
|
49
|
+
checkpointStore?: CheckpointStore;
|
|
50
|
+
}): CheckpointStore | undefined;
|
|
45
51
|
export interface Prepared {
|
|
46
52
|
harness: AgentHarness;
|
|
47
53
|
session: StoredSession;
|
|
@@ -187,6 +193,7 @@ export interface Prepared {
|
|
|
187
193
|
now: () => number;
|
|
188
194
|
tools: AgentTool[];
|
|
189
195
|
toolEffects: Map<string, ToolEffect>;
|
|
196
|
+
wakeRecovered: RecoveredOrphan[];
|
|
190
197
|
promptOverheadTokens: number;
|
|
191
198
|
readTaskFile?: (path: string) => Promise<string | null>;
|
|
192
199
|
recentlyReadFiles?: () => string[];
|
|
@@ -57,7 +57,7 @@ import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.j
|
|
|
57
57
|
import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from "../task-registry.js";
|
|
58
58
|
import { createMonitorTool } from "../../tools/monitor.js";
|
|
59
59
|
import { createWorktreeTools } from "../../tools/worktree.js";
|
|
60
|
-
import { applyCompactionToReadFileState, bashReversibilityProbe, createHandsToolkit, isReadDedupStubResult, seedReadFileStateFromContext, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
|
|
60
|
+
import { applyCompactionToReadFileState, bashReversibilityProbe, createHandsToolkit, isReadDedupStubResult, seedReadFileStateFromContext, seedReadFileStateFromTranscript, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
|
|
61
61
|
import { decodeTextBytes } from "../../tools/fs/encoding.js";
|
|
62
62
|
import { ASK_USER_QUESTION_TOOL_NAME, createAskUserQuestionTool } from "../ask-question.js";
|
|
63
63
|
import { createSchedulerTools } from "../../tools/scheduler-tools.js";
|
|
@@ -67,6 +67,7 @@ import { createRunWorkflowTool, RUN_WORKFLOW_TOOL_NAME } from "../../orchestrati
|
|
|
67
67
|
import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
68
68
|
import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
|
|
69
69
|
import { resolveKey } from "../../tools/fs/safety.js";
|
|
70
|
+
import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
|
|
70
71
|
import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, } from "../checkpoint-store.js";
|
|
71
72
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
72
73
|
import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
|
|
@@ -171,6 +172,11 @@ export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
|
|
|
171
172
|
export function checkpointScopeOf(spec) {
|
|
172
173
|
return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
|
|
173
174
|
}
|
|
175
|
+
export function resolveCheckpointStore(spec, deps) {
|
|
176
|
+
if (spec.checkpointStore === null)
|
|
177
|
+
return undefined;
|
|
178
|
+
return spec.checkpointStore ?? deps.checkpointStore;
|
|
179
|
+
}
|
|
174
180
|
export function isFableFamilyModelId(id) {
|
|
175
181
|
const tail = id.toLowerCase().split("/").pop() ?? "";
|
|
176
182
|
return /^claude-fable-\d/.test(tail) || /^claude-mythos-5(?!\d)/.test(tail);
|
|
@@ -319,6 +325,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
319
325
|
throw e;
|
|
320
326
|
}
|
|
321
327
|
resolveTaskLimits(spec.limits);
|
|
328
|
+
if (spec.resourceSuspend !== undefined) {
|
|
329
|
+
const rsus = spec.resourceSuspend;
|
|
330
|
+
if (typeof rsus.scope !== "string" || rsus.scope === "") {
|
|
331
|
+
throw limitConfigError("config.limit_invalid", `TaskSpec.resourceSuspend.scope must be a non-empty string (got ${String(rsus.scope)}) — it is the multi-tenant isolation key every resource checkpoint is filed under.`);
|
|
332
|
+
}
|
|
333
|
+
for (const key of ["totalBudgetUsd", "totalTokens", "maxSlices", "ttlMs"]) {
|
|
334
|
+
const value = rsus[key];
|
|
335
|
+
if (value === undefined)
|
|
336
|
+
continue;
|
|
337
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
338
|
+
throw limitConfigError("config.limit_invalid", `TaskSpec.resourceSuspend.${key} must be a finite, non-negative number (got ${String(value)}) — an unevaluable allocation is not an allocation, and a NaN here blinds even the validated per-slice window.`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
322
342
|
const usageWindows = resolveUsageWindows(deps.usageWindows);
|
|
323
343
|
const brainCallGuardrailRef = {};
|
|
324
344
|
const brainCallGuardrailMs = resolveBrainCallGuardrailMs(spec.limits?.brainCallGuardrailMs ?? deps.brainCallGuardrailMs);
|
|
@@ -411,6 +431,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
411
431
|
let acquired;
|
|
412
432
|
let session;
|
|
413
433
|
let conflictRef;
|
|
434
|
+
let wakeRecovered = [];
|
|
414
435
|
let resumeAtBeforeParentId = null;
|
|
415
436
|
for (let attempt = 0;; attempt++) {
|
|
416
437
|
try {
|
|
@@ -487,7 +508,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
487
508
|
}
|
|
488
509
|
await session.getStorage().setLeafId(spec.resumeAtMode === "before" ? entry.parentId : spec.resumeAt);
|
|
489
510
|
}
|
|
490
|
-
await reconcileInterruptedSession(session, toolEffects, resume?.suspendedBatch);
|
|
511
|
+
wakeRecovered = (await reconcileInterruptedSession(session, toolEffects, resume?.suspendedBatch)).recovered;
|
|
491
512
|
break;
|
|
492
513
|
}
|
|
493
514
|
catch (err) {
|
|
@@ -910,6 +931,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
910
931
|
const effectiveShellGate = inheritedShellGate !== undefined && shellGateRank[inheritedShellGate] > shellGateRank[specShellGate] ? inheritedShellGate : specShellGate;
|
|
911
932
|
const ownSessionRulesRef = {};
|
|
912
933
|
const frozenOnAsk = spec.onAsk ?? deps.onAsk;
|
|
934
|
+
const frozenOnQuestion = spec.onQuestion ?? deps.onQuestion;
|
|
913
935
|
const inheritedGateForChildren = () => {
|
|
914
936
|
const ancestorRules = [
|
|
915
937
|
...(inheritedAncestorRules ?? []),
|
|
@@ -945,6 +967,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
945
967
|
thinkingLevel: harnessRef.current?.getThinkingLevel(),
|
|
946
968
|
principal: spec.principal,
|
|
947
969
|
...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
|
|
970
|
+
...(frozenOnQuestion !== undefined ? { onQuestion: frozenOnQuestion } : {}),
|
|
971
|
+
...(spec.handsReadOnly === true ? { handsReadOnly: true } : {}),
|
|
972
|
+
...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
|
|
948
973
|
oneShot: spec.oneShot,
|
|
949
974
|
clientContext: spec.clientContext,
|
|
950
975
|
excludeTools: toolFaceSnapshot.exclude,
|
|
@@ -961,6 +986,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
961
986
|
inheritedGateForChildren,
|
|
962
987
|
...(autoModeDecider ? { autoModeReview: { decider: autoModeDecider } } : {}),
|
|
963
988
|
...(spec.durableApproval !== undefined ? { durableApprovalForChildren: { ...spec.durableApproval } } : {}),
|
|
989
|
+
...(spec.checkpointStore === null ? { checkpointStoreDisabledForChildren: true } : {}),
|
|
964
990
|
taskId: hostTaskId,
|
|
965
991
|
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
|
966
992
|
...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
|
|
@@ -1014,7 +1040,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1014
1040
|
tools.push(createReportFindingsTool());
|
|
1015
1041
|
}
|
|
1016
1042
|
if (spec.enablePlanMode === true && spec.interactiveTools !== false) {
|
|
1017
|
-
const planReviewFace = (spec
|
|
1043
|
+
const planReviewFace = resolveCheckpointStore(spec, deps) !== undefined;
|
|
1018
1044
|
if (spec.interactiveTools === true || planReviewFace) {
|
|
1019
1045
|
tools.push(defineTool(createPresentPlanTool(requestReview)));
|
|
1020
1046
|
if (planReviewFace) {
|
|
@@ -1352,6 +1378,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1352
1378
|
}
|
|
1353
1379
|
const readFileState = new Map((resume?.seed.readFileState ?? []).map(([k, v]) => [rebaseRestoredPath(k), v]));
|
|
1354
1380
|
readFileStateForCheckpoint = readFileState;
|
|
1381
|
+
if (resume === undefined && spec.sessionId !== undefined) {
|
|
1382
|
+
const prior = await session.buildContext().catch(() => undefined);
|
|
1383
|
+
for (const rec of wholeFileRecordsFromTranscript(prior?.messages ?? [])) {
|
|
1384
|
+
const rk = await resolveKey(executionEnv, rootCanonical, rec.path, undefined, undefined, additionalRootsCanonical);
|
|
1385
|
+
if (rk.ok)
|
|
1386
|
+
seedReadFileStateFromTranscript(readFileState, rk.key, rec.content, rec.at);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1355
1389
|
seedContextFiles = async (files) => {
|
|
1356
1390
|
for (const f of files) {
|
|
1357
1391
|
const rk = await resolveKey(executionEnv, rootCanonical, f.path, undefined, undefined, additionalRootsCanonical);
|
|
@@ -1533,8 +1567,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1533
1567
|
}
|
|
1534
1568
|
if (offloadStore)
|
|
1535
1569
|
tools.push(createReadToolResultTool(offloadStore));
|
|
1536
|
-
const onQuestion =
|
|
1537
|
-
const durableQuestionFace = (spec
|
|
1570
|
+
const onQuestion = frozenOnQuestion;
|
|
1571
|
+
const durableQuestionFace = resolveCheckpointStore(spec, deps) !== undefined &&
|
|
1538
1572
|
(spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true);
|
|
1539
1573
|
if (spec.interactiveTools === true || (spec.interactiveTools !== false && (onQuestion !== undefined || durableQuestionFace)))
|
|
1540
1574
|
tools.push(createAskUserQuestionTool(onQuestion, { principal: spec.principal, sourceTaskId: sessionId }));
|
|
@@ -2462,7 +2496,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2462
2496
|
if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME)
|
|
2463
2497
|
return first;
|
|
2464
2498
|
if (pc.durableMandate === true) {
|
|
2465
|
-
if ((spec
|
|
2499
|
+
if (resolveCheckpointStore(spec, deps) !== undefined &&
|
|
2466
2500
|
(spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
|
|
2467
2501
|
markInheritedUnavailable(creq.toolCallId)) {
|
|
2468
2502
|
return first;
|
|
@@ -2518,7 +2552,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2518
2552
|
if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME)
|
|
2519
2553
|
return decision;
|
|
2520
2554
|
if (pc.durableMandate === true) {
|
|
2521
|
-
if ((spec
|
|
2555
|
+
if (resolveCheckpointStore(spec, deps) !== undefined &&
|
|
2522
2556
|
(spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
|
|
2523
2557
|
markInheritedUnavailable(creq.toolCallId)) {
|
|
2524
2558
|
return decision;
|
|
@@ -2678,13 +2712,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2678
2712
|
const blockedTracked = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure || hooks?.postToolBatch);
|
|
2679
2713
|
const restoreSurfaceGap = ownedEnv !== undefined && isRemoteExecutionEnv(ownedEnv) && ownedEnv.capabilities.suspendable ? missingRestoreSurface(ownedEnv) : [];
|
|
2680
2714
|
const incompleteSuspendAdapter = restoreSurfaceGap.length > 0 ? restoreSurfaceGap : undefined;
|
|
2681
|
-
const durableSuspendInfraReady = (spec
|
|
2715
|
+
const durableSuspendInfraReady = resolveCheckpointStore(spec, deps) !== undefined &&
|
|
2682
2716
|
!(offloadStore !== undefined && isVolatileOffloadStore(offloadStore)) &&
|
|
2683
2717
|
(ownedEnv === undefined || isRemoteExecutionEnv(ownedEnv)) &&
|
|
2684
2718
|
incompleteSuspendAdapter === undefined;
|
|
2685
2719
|
const resourceSuspendEligible = spec.resourceSuspend !== undefined && durableSuspendInfraReady;
|
|
2686
2720
|
if (spec.resourceSuspend !== undefined && !resourceSuspendEligible) {
|
|
2687
|
-
const why = (spec
|
|
2721
|
+
const why = resolveCheckpointStore(spec, deps) === undefined
|
|
2688
2722
|
? "no CheckpointStore is wired"
|
|
2689
2723
|
: offloadStore !== undefined && isVolatileOffloadStore(offloadStore)
|
|
2690
2724
|
? "tool-result offload uses the in-memory store (a resume needs durable results)"
|
|
@@ -2842,7 +2876,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2842
2876
|
catch {
|
|
2843
2877
|
}
|
|
2844
2878
|
};
|
|
2845
|
-
const checkpointStore = spec
|
|
2879
|
+
const checkpointStore = resolveCheckpointStore(spec, deps);
|
|
2846
2880
|
const durableApproval = spec.durableApproval ??
|
|
2847
2881
|
(runtimeCaps?.forceDurableGate ? { scope: spec.principal || DEFAULT_IRREVERSIBLE_SCOPE } : undefined);
|
|
2848
2882
|
const inFlightSpendMicroUsd = () => {
|
|
@@ -3602,7 +3636,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3602
3636
|
: undefined;
|
|
3603
3637
|
overheadState.promptChars = systemPrompt.length;
|
|
3604
3638
|
const preparedHolder = {};
|
|
3605
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3639
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3606
3640
|
const prepared = buildPrepared();
|
|
3607
3641
|
preparedHolder.current = prepared;
|
|
3608
3642
|
return prepared;
|
|
@@ -4,6 +4,7 @@ import type { RunInternals } from "./prepare-task.js";
|
|
|
4
4
|
import { type TaskOutcome } from "../task-outcome.js";
|
|
5
5
|
import { type SideQuerySpec, type SideQueryResult } from "../side-query.js";
|
|
6
6
|
import type { SessionStore } from "../session.js";
|
|
7
|
+
import { type RecoveredOrphan } from "../session-reconcile.js";
|
|
7
8
|
import type { AgentDefinition, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
|
|
8
9
|
export type ResumeTaskConfig = Omit<TaskSpec, "objective" | "sessionId">;
|
|
9
10
|
interface ResumeRun {
|
|
@@ -19,6 +20,17 @@ interface ResumeRun {
|
|
|
19
20
|
onEnvRestoreFailed?: (reason: ReopenReason) => Promise<void>;
|
|
20
21
|
decisionDelivered?: boolean;
|
|
21
22
|
}
|
|
23
|
+
declare function toolEndBodyFrom(result: unknown, isError: boolean): {
|
|
24
|
+
output?: unknown;
|
|
25
|
+
truncated?: boolean;
|
|
26
|
+
totalChars?: number;
|
|
27
|
+
structured?: unknown;
|
|
28
|
+
errorCode?: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function reconciledToolEndBody(orphan: Pick<RecoveredOrphan, "text" | "errorKind">): ReturnType<typeof toolEndBodyFrom>;
|
|
31
|
+
export declare const GOVERNANCE_READ_STALLED: unique symbol;
|
|
32
|
+
export declare function awaitChargeWithSlowDisclosure<T>(charge: Promise<T>, onSlow: () => void, discloseAfterMs?: number): Promise<T>;
|
|
33
|
+
export declare function raceUntilDeadline<T>(p: Promise<T>, deadline: number): Promise<T | typeof GOVERNANCE_READ_STALLED>;
|
|
22
34
|
export declare class Runner {
|
|
23
35
|
private deps;
|
|
24
36
|
readonly sessions: SessionStore;
|
|
@@ -24,7 +24,7 @@ import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
|
|
|
24
24
|
import { assembleResult, errorCodeOf } from "./assemble-result.js";
|
|
25
25
|
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, 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";
|
|
26
26
|
import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated } from "./compaction-call-options.js";
|
|
27
|
-
import { prepareTask } from "./prepare-task.js";
|
|
27
|
+
import { prepareTask, resolveCheckpointStore } from "./prepare-task.js";
|
|
28
28
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
29
29
|
import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
|
|
30
30
|
import { hasVerifiableStructureSignal } from "./grounding-signal.js";
|
|
@@ -123,6 +123,9 @@ function toolEndBodyFrom(result, isError) {
|
|
|
123
123
|
...(typeof code === "string" ? { errorCode: code } : {}),
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
|
+
export function reconciledToolEndBody(orphan) {
|
|
127
|
+
return toolEndBodyFrom({ content: orphan.text, details: { code: orphan.errorKind } }, true);
|
|
128
|
+
}
|
|
126
129
|
function deepJsonEqual(a, b) {
|
|
127
130
|
if (a === b)
|
|
128
131
|
return true;
|
|
@@ -240,6 +243,42 @@ function resumeContinuation(resume) {
|
|
|
240
243
|
`conversation above. Do NOT restart the task or re-run any tool you already ran; continue from this ` +
|
|
241
244
|
`exact point, building on the existing results, and finish the remaining work.`);
|
|
242
245
|
}
|
|
246
|
+
const ENV_DUE_GOVERNANCE_READ_BUDGET_MS = 5_000;
|
|
247
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
248
|
+
const DEADLINE_TICK = Symbol("deadline-tick");
|
|
249
|
+
export const GOVERNANCE_READ_STALLED = Symbol("governance-read-stalled");
|
|
250
|
+
const CHARGE_SETTLE_DISCLOSE_MS = 10_000;
|
|
251
|
+
export async function awaitChargeWithSlowDisclosure(charge, onSlow, discloseAfterMs = CHARGE_SETTLE_DISCLOSE_MS) {
|
|
252
|
+
let timer = setTimeout(() => {
|
|
253
|
+
timer = undefined;
|
|
254
|
+
try {
|
|
255
|
+
onSlow();
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
}
|
|
259
|
+
}, discloseAfterMs);
|
|
260
|
+
try {
|
|
261
|
+
return await charge;
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
if (timer !== undefined)
|
|
265
|
+
clearTimeout(timer);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export async function raceUntilDeadline(p, deadline) {
|
|
269
|
+
for (let firstPass = true;; firstPass = false) {
|
|
270
|
+
const remaining = deadline - Date.now();
|
|
271
|
+
if (remaining <= 0 && !firstPass)
|
|
272
|
+
return GOVERNANCE_READ_STALLED;
|
|
273
|
+
let timer;
|
|
274
|
+
const tick = new Promise((res) => {
|
|
275
|
+
timer = setTimeout(() => res(DEADLINE_TICK), Math.max(0, Math.min(remaining, MAX_TIMER_DELAY_MS)));
|
|
276
|
+
});
|
|
277
|
+
const out = await Promise.race([p, tick]).finally(() => clearTimeout(timer));
|
|
278
|
+
if (out !== DEADLINE_TICK)
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
243
282
|
function platformLimitTerminal(reason, retryAfterMs, moment = "turn_boundary") {
|
|
244
283
|
const message = moment === "entry"
|
|
245
284
|
? `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.`
|
|
@@ -267,17 +306,39 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
267
306
|
let platformRetryAfterMs;
|
|
268
307
|
const preemptWillSuspend = preemptHit && prepared.suspendForResource !== undefined;
|
|
269
308
|
if (!preemptWillSuspend && prepared.suspendRef.token === undefined && !prepared.abortController.signal.aborted) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
else if (prepared.usageGovernance !== undefined) {
|
|
309
|
+
const ledgerReadDeadline = prepared.envLifetimeSuspendAt !== undefined ? prepared.envLifetimeSuspendAt + ENV_DUE_GOVERNANCE_READ_BUDGET_MS : undefined;
|
|
310
|
+
if (prepared.usageGovernance !== undefined) {
|
|
274
311
|
const governance = prepared.usageGovernance;
|
|
275
312
|
try {
|
|
276
313
|
const at = Date.now();
|
|
277
|
-
await governance.commit(stats.tokens, at);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
314
|
+
await awaitChargeWithSlowDisclosure(governance.commit(stats.tokens, at), () => runnerHooks.onError?.(new Error("the usage ledger CHARGE has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on the end-of-task flush). A wedged ledger store wedges this boundary, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
|
|
315
|
+
const windowRead = governance.check(at);
|
|
316
|
+
let answer;
|
|
317
|
+
if (ledgerReadDeadline !== undefined) {
|
|
318
|
+
answer = await raceUntilDeadline(windowRead, ledgerReadDeadline);
|
|
319
|
+
if (answer === GOVERNANCE_READ_STALLED) {
|
|
320
|
+
void windowRead.catch((lateErr) => {
|
|
321
|
+
try {
|
|
322
|
+
runnerHooks.onError?.(lateErr instanceof Error ? lateErr : new Error(String(lateErr)), { phase: "config", sessionId: prepared.sessionId });
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
try {
|
|
328
|
+
runnerHooks.onError?.(new Error("the usage ledger did not answer before the execution environment's suspend deadline — stopping for the environment, the cause this run can still prove. The window state for this boundary is UNKNOWN and was not enforced."), { phase: "config", sessionId: prepared.sessionId });
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
answer = await windowRead;
|
|
336
|
+
}
|
|
337
|
+
if (answer !== GOVERNANCE_READ_STALLED) {
|
|
338
|
+
platformRetryAfterMs = answer;
|
|
339
|
+
if (platformRetryAfterMs !== undefined)
|
|
340
|
+
platformCause = "usage_window";
|
|
341
|
+
}
|
|
281
342
|
}
|
|
282
343
|
catch (govErr) {
|
|
283
344
|
rs.limits.platformTerminal = govErr instanceof Error ? govErr : new Error(String(govErr));
|
|
@@ -286,6 +347,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
286
347
|
return undefined;
|
|
287
348
|
}
|
|
288
349
|
}
|
|
350
|
+
if (platformCause === undefined && prepared.envLifetimeSuspendAt !== undefined && Date.now() >= prepared.envLifetimeSuspendAt) {
|
|
351
|
+
platformCause = "env_lifetime";
|
|
352
|
+
}
|
|
289
353
|
}
|
|
290
354
|
if (platformCause !== undefined) {
|
|
291
355
|
const platformSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
|
|
@@ -923,7 +987,8 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
923
987
|
: {}),
|
|
924
988
|
ts: callEndAt,
|
|
925
989
|
}));
|
|
926
|
-
|
|
990
|
+
const cacheRowUnknown = m.usageMissing === true || m.stopReason === "aborted";
|
|
991
|
+
if (prepared.cacheBreakDetector && prepared.cacheFingerprint && !cacheRowUnknown) {
|
|
927
992
|
const finding = prepared.cacheBreakDetector.observe({
|
|
928
993
|
turn: stats.turns + 1,
|
|
929
994
|
systemPrompt: prepared.cacheFingerprint.systemPrompt,
|
|
@@ -1313,6 +1378,10 @@ export class Runner {
|
|
|
1313
1378
|
finally {
|
|
1314
1379
|
releaseLock?.();
|
|
1315
1380
|
publishReady(handle);
|
|
1381
|
+
manualCompactRef.closed = true;
|
|
1382
|
+
if (manualCompactRef.waiters.length > 0)
|
|
1383
|
+
manualCompactRef.emitMooted?.("task_ending");
|
|
1384
|
+
manualCompactRef.emitMooted = undefined;
|
|
1316
1385
|
drainManualCompactWaiters("mooted");
|
|
1317
1386
|
}
|
|
1318
1387
|
})();
|
|
@@ -1501,6 +1570,8 @@ export class Runner {
|
|
|
1501
1570
|
throw steeringError("the task is not running");
|
|
1502
1571
|
if (opts?.signal?.aborted)
|
|
1503
1572
|
return "mooted";
|
|
1573
|
+
if (manualCompactRef.closed)
|
|
1574
|
+
return "mooted";
|
|
1504
1575
|
return new Promise((resolve) => {
|
|
1505
1576
|
const signal = opts?.signal;
|
|
1506
1577
|
const entry = {
|
|
@@ -1516,6 +1587,7 @@ export class Runner {
|
|
|
1516
1587
|
if (manualCompactRef.waiters.length === 0) {
|
|
1517
1588
|
manualCompactRef.requested = false;
|
|
1518
1589
|
}
|
|
1590
|
+
manualCompactRef.emitMooted?.("cancelled");
|
|
1519
1591
|
entry.resolve("mooted");
|
|
1520
1592
|
}
|
|
1521
1593
|
};
|
|
@@ -1649,6 +1721,13 @@ export class Runner {
|
|
|
1649
1721
|
}, this);
|
|
1650
1722
|
notificationHarness = prepared.harness;
|
|
1651
1723
|
notificationSessionId = prepared.sessionId;
|
|
1724
|
+
const runSourceTaskId = spec.taskId ?? prepared.sessionId;
|
|
1725
|
+
const parentToolCallId = internals?.parentToolCallId;
|
|
1726
|
+
const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: runSourceTaskId } : { eventId: uuidv7() };
|
|
1727
|
+
notificationIdent = ident;
|
|
1728
|
+
manualCompactRef.emitMooted = (reason) => {
|
|
1729
|
+
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason, ...ident() });
|
|
1730
|
+
};
|
|
1652
1731
|
prepared.harness.onUndrainedEngineNotes = (payloads) => {
|
|
1653
1732
|
if (notificationSessionId === undefined)
|
|
1654
1733
|
return;
|
|
@@ -1667,7 +1746,7 @@ export class Runner {
|
|
|
1667
1746
|
if (pendingIdle !== undefined) {
|
|
1668
1747
|
for (const payload of discloseDroppedPending(pendingIdle)) {
|
|
1669
1748
|
deliveredAtTurnOpen.add(taskNotificationDedupKey(payload));
|
|
1670
|
-
queue.push({ type: "task_notification", notification: payload });
|
|
1749
|
+
queue.push({ type: "task_notification", notification: payload, ...ident() });
|
|
1671
1750
|
void prepared.harness.nextTurn(renderTaskNotificationXml(payload), { provenance: "engine-note", enginePayload: payload }).catch(() => {
|
|
1672
1751
|
this.pendingSessionNotifications.pend(prepared.sessionId, payload);
|
|
1673
1752
|
});
|
|
@@ -1716,7 +1795,7 @@ export class Runner {
|
|
|
1716
1795
|
rs.limits.outputRetryCap = resolveOutputRetries(spec.outputRetries);
|
|
1717
1796
|
rs.limits.effectiveMaxTurns = resolveMaxTurns(spec.limits);
|
|
1718
1797
|
rs.telemetry.tracer = spec.tracer ?? this.deps.tracer;
|
|
1719
|
-
rs.telemetry.taskId =
|
|
1798
|
+
rs.telemetry.taskId = runSourceTaskId;
|
|
1720
1799
|
if (taskIdRef) {
|
|
1721
1800
|
taskIdRef.current = rs.telemetry.taskId;
|
|
1722
1801
|
taskIdRef.sessionId = prepared.sessionId;
|
|
@@ -2064,9 +2143,6 @@ export class Runner {
|
|
|
2064
2143
|
const walltimeMonotonicDeadline = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStartMonotonic + effectiveTimeoutMs : undefined;
|
|
2065
2144
|
rs.counters.walltimeSyncBackstopFired = false;
|
|
2066
2145
|
const timeout = startTimeout(prepared.harness, prepared.abortController, walltimeMonotonicDeadline !== undefined ? walltimeMonotonicDeadline - performance.now() : undefined, prepared.suspendForResource !== undefined);
|
|
2067
|
-
const parentToolCallId = internals?.parentToolCallId;
|
|
2068
|
-
const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: rs.telemetry.taskId } : { eventId: uuidv7() };
|
|
2069
|
-
notificationIdent = ident;
|
|
2070
2146
|
const pushContent = (e) => {
|
|
2071
2147
|
queue.push(e);
|
|
2072
2148
|
if (parentToolCallId !== undefined && internals?.onForwardEvent) {
|
|
@@ -2114,6 +2190,10 @@ export class Runner {
|
|
|
2114
2190
|
};
|
|
2115
2191
|
const withBrainSinks = (fn) => runWithStatusSink(statusEmit, () => runWithBrainTelemetry(telemetryEmit, fn));
|
|
2116
2192
|
const toolLabels = new Map(prepared.tools.flatMap((t) => (t.label !== undefined && t.label !== t.name ? [[t.name, t.label]] : [])));
|
|
2193
|
+
for (const orphan of prepared.wakeRecovered) {
|
|
2194
|
+
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...reconciledToolEndBody(orphan), ...ident() });
|
|
2195
|
+
emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
|
|
2196
|
+
}
|
|
2117
2197
|
const postToolBatchHook = (spec.hooks ?? this.deps.hooks)?.postToolBatch;
|
|
2118
2198
|
const batchArgs = postToolBatchHook ? new Map() : undefined;
|
|
2119
2199
|
rs.turn.toolBatch = [];
|
|
@@ -2660,7 +2740,7 @@ export class Runner {
|
|
|
2660
2740
|
}
|
|
2661
2741
|
if (prepared.usageGovernance !== undefined) {
|
|
2662
2742
|
try {
|
|
2663
|
-
await prepared.usageGovernance.commit(stats.tokens, Date.now());
|
|
2743
|
+
await awaitChargeWithSlowDisclosure(prepared.usageGovernance.commit(stats.tokens, Date.now()), () => this.deps.onError?.(new Error("the usage ledger's FINAL charge has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on a later flush). A wedged ledger store wedges this teardown, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
|
|
2664
2744
|
}
|
|
2665
2745
|
catch (flushErr) {
|
|
2666
2746
|
this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
|
|
@@ -2675,7 +2755,7 @@ export class Runner {
|
|
|
2675
2755
|
try {
|
|
2676
2756
|
const report = await reconcileInterruptedSession(prepared.session, prepared.toolEffects, undefined, startedToolCallIds);
|
|
2677
2757
|
for (const orphan of report.recovered) {
|
|
2678
|
-
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...ident() });
|
|
2758
|
+
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...reconciledToolEndBody(orphan), ...ident() });
|
|
2679
2759
|
emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
|
|
2680
2760
|
}
|
|
2681
2761
|
}
|
|
@@ -2873,7 +2953,7 @@ export class Runner {
|
|
|
2873
2953
|
const committedToken = prepared.suspendRef.token ?? prepared.reviewRef.token;
|
|
2874
2954
|
const committedScope = prepared.suspendRef.scope ?? prepared.reviewRef.scope;
|
|
2875
2955
|
if (committedToken !== undefined) {
|
|
2876
|
-
const store = spec
|
|
2956
|
+
const store = resolveCheckpointStore(spec, this.deps);
|
|
2877
2957
|
if (store && committedScope !== undefined) {
|
|
2878
2958
|
onSuspend({
|
|
2879
2959
|
env: prepared.ownedEnv,
|
|
@@ -2929,6 +3009,11 @@ export class Runner {
|
|
|
2929
3009
|
catch {
|
|
2930
3010
|
}
|
|
2931
3011
|
}
|
|
3012
|
+
if (manualCompactRef.waiters.length > 0) {
|
|
3013
|
+
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
|
|
3014
|
+
drainManualCompact("mooted");
|
|
3015
|
+
}
|
|
3016
|
+
manualCompactRef.emitMooted = undefined;
|
|
2932
3017
|
queue.push({ type: "done", result });
|
|
2933
3018
|
queue.close();
|
|
2934
3019
|
if (spec.rewindFiles && result.status === "completed") {
|
|
@@ -3117,7 +3202,7 @@ export class Runner {
|
|
|
3117
3202
|
return stream.result();
|
|
3118
3203
|
}
|
|
3119
3204
|
async resumeStream(token, outcome, taskConfig, internals) {
|
|
3120
|
-
const store = taskConfig
|
|
3205
|
+
const store = resolveCheckpointStore(taskConfig, this.deps);
|
|
3121
3206
|
if (!store) {
|
|
3122
3207
|
throw new CheckpointError("checkpoint.not_found", "no CheckpointStore wired — cannot resume (set RunnerDeps.checkpointStore or taskConfig.checkpointStore)");
|
|
3123
3208
|
}
|
|
@@ -3352,13 +3437,16 @@ export class Runner {
|
|
|
3352
3437
|
const completed = new Set(pendingAction.completedCallIds);
|
|
3353
3438
|
const deferredIds = pendingAction.batchToolCallIds.filter((id) => id !== pendingAction.toolCallId && !completed.has(id));
|
|
3354
3439
|
const names = new Map();
|
|
3440
|
+
const deferredArgs = new Map();
|
|
3355
3441
|
if (deferredIds.length > 0) {
|
|
3356
3442
|
const { messages } = await prepared.session.buildContext();
|
|
3357
3443
|
for (const m of messages) {
|
|
3358
3444
|
if (m.role === "assistant") {
|
|
3359
3445
|
for (const c of m.content) {
|
|
3360
|
-
if (c.type === "toolCall")
|
|
3446
|
+
if (c.type === "toolCall") {
|
|
3361
3447
|
names.set(c.id, c.name);
|
|
3448
|
+
deferredArgs.set(c.id, c.arguments);
|
|
3449
|
+
}
|
|
3362
3450
|
}
|
|
3363
3451
|
}
|
|
3364
3452
|
}
|
|
@@ -3369,7 +3457,12 @@ export class Runner {
|
|
|
3369
3457
|
}
|
|
3370
3458
|
else if (!completed.has(id)) {
|
|
3371
3459
|
const name = names.get(id) ?? "unknown";
|
|
3372
|
-
|
|
3460
|
+
const displayLabel = (() => {
|
|
3461
|
+
const l = prepared.tools.find((t) => t.name === name)?.label;
|
|
3462
|
+
return l !== undefined && l !== name ? { label: l } : {};
|
|
3463
|
+
})();
|
|
3464
|
+
emit({ type: "tool_start", toolCallId: id, toolName: name, ...displayLabel, args: deferredArgs.get(id) ?? {} });
|
|
3465
|
+
emit({ type: "tool_end", toolCallId: id, toolName: name, ...displayLabel, isError: true, ...toolEndBodyFrom({ content: formatHookFeedback(DEFERRED_REISSUE) }, true) });
|
|
3373
3466
|
const eid = await prepared.session.appendMessage(toolResultMsg(id, name, formatHookFeedback(DEFERRED_REISSUE), true));
|
|
3374
3467
|
emitCommitted(eid, "toolResult", id);
|
|
3375
3468
|
}
|
|
@@ -3425,7 +3518,19 @@ export class Runner {
|
|
|
3425
3518
|
}
|
|
3426
3519
|
catch (err) {
|
|
3427
3520
|
const execError = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
3428
|
-
|
|
3521
|
+
const marks = (() => {
|
|
3522
|
+
if (err === null || typeof err !== "object")
|
|
3523
|
+
return undefined;
|
|
3524
|
+
const src = err;
|
|
3525
|
+
let d;
|
|
3526
|
+
if (src.details !== null && typeof src.details === "object" && !Array.isArray(src.details)) {
|
|
3527
|
+
d = { ...src.details };
|
|
3528
|
+
}
|
|
3529
|
+
if (typeof src.errorKind === "string")
|
|
3530
|
+
d = { ...(d ?? {}), errorKind: src.errorKind };
|
|
3531
|
+
return d;
|
|
3532
|
+
})();
|
|
3533
|
+
emitEnd(true, { content: execError, ...(marks !== undefined ? { details: marks } : {}) });
|
|
3429
3534
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, execError, true));
|
|
3430
3535
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
3431
3536
|
return;
|