@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.
Files changed (43) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/agents/roster-store.d.ts +1 -0
  3. package/dist/agents/send-message-tool.js +6 -0
  4. package/dist/agents/subagent.d.ts +30 -0
  5. package/dist/agents/subagent.js +61 -19
  6. package/dist/agents/teacher.js +15 -3
  7. package/dist/agents/team.js +10 -0
  8. package/dist/agents/verify.js +7 -0
  9. package/dist/brain/anthropic.js +27 -10
  10. package/dist/brain/open-responses.js +19 -4
  11. package/dist/brain/openai.js +32 -5
  12. package/dist/core/a2a.js +1 -1
  13. package/dist/core/memory-recall.js +8 -3
  14. package/dist/core/memory.d.ts +5 -0
  15. package/dist/core/memory.js +6 -4
  16. package/dist/core/runner/prepare-task.d.ts +8 -1
  17. package/dist/core/runner/prepare-task.js +45 -11
  18. package/dist/core/runner/runtask.d.ts +12 -0
  19. package/dist/core/runner/runtask.js +127 -22
  20. package/dist/core/runner/session-file-state-replay.d.ts +7 -0
  21. package/dist/core/runner/session-file-state-replay.js +56 -0
  22. package/dist/core/runner/synthetic-tools.js +1 -1
  23. package/dist/core/runner/tool-output-projection.js +5 -4
  24. package/dist/core/session-reconcile.d.ts +7 -3
  25. package/dist/core/session-reconcile.js +3 -2
  26. package/dist/core/strategy-store.d.ts +1 -1
  27. package/dist/core/strategy-store.js +27 -4
  28. package/dist/core/tools.js +9 -1
  29. package/dist/core/types.d.ts +5 -1
  30. package/dist/engine/loop/agent-loop.js +168 -22
  31. package/dist/orchestration/run-workflow-tool.js +1 -1
  32. package/dist/orchestration/workflow-governance.js +19 -0
  33. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  34. package/dist/orchestration/workflow-primitives.js +4 -1
  35. package/dist/orchestration/workflow.js +1 -1
  36. package/dist/prompts/coordinator.d.ts +1 -1
  37. package/dist/prompts/coordinator.js +1 -1
  38. package/dist/stores/file/memory-store.js +3 -7
  39. package/dist/tools/fs/fs-bash.js +3 -3
  40. package/dist/tools/fs/fs-shared.d.ts +1 -0
  41. package/dist/tools/fs/fs-shared.js +4 -0
  42. package/dist/tools/web.js +0 -1
  43. 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.mtimeMs > prev.mtimeMs)
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
  };
@@ -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;
@@ -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
- mtimeMs: mtimeMsOf(e.ts),
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
- mtimeMs: mtimeMsOf(e.ts),
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 mtimeMsOf(ts) {
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.checkpointStore ?? deps.checkpointStore) !== undefined;
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 = spec.onQuestion ?? deps.onQuestion;
1537
- const durableQuestionFace = (spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
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.checkpointStore ?? deps.checkpointStore) !== undefined &&
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.checkpointStore ?? deps.checkpointStore) !== undefined &&
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.checkpointStore ?? deps.checkpointStore) !== undefined &&
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.checkpointStore ?? deps.checkpointStore) === undefined
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.checkpointStore ?? deps.checkpointStore;
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
- if (prepared.envLifetimeSuspendAt !== undefined && Date.now() >= prepared.envLifetimeSuspendAt) {
271
- platformCause = "env_lifetime";
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
- platformRetryAfterMs = await governance.check(at);
279
- if (platformRetryAfterMs !== undefined)
280
- platformCause = "usage_window";
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
- if (prepared.cacheBreakDetector && prepared.cacheFingerprint) {
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 = spec.taskId ?? prepared.sessionId;
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.checkpointStore ?? this.deps.checkpointStore;
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.checkpointStore ?? this.deps.checkpointStore;
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
- emit({ type: "tool_end", toolCallId: id, toolName: name, ...((() => { const l = prepared.tools.find((t) => t.name === name)?.label; return l !== undefined && l !== name ? { label: l } : {}; })()), isError: true, ...toolEndBodyFrom({ content: formatHookFeedback(DEFERRED_REISSUE) }, true) });
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
- emitEnd(true, { content: execError });
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;