@sema-agent/core 5.9.0 → 5.11.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 (62) hide show
  1. package/CHANGELOG.md +63 -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 +31 -1
  5. package/dist/agents/subagent.js +67 -21
  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/background-agent-store.d.ts +2 -1
  14. package/dist/core/background-agent-store.js +1 -0
  15. package/dist/core/checkpoint-store.d.ts +2 -0
  16. package/dist/core/checkpoint-store.js +1 -0
  17. package/dist/core/memory-recall.js +8 -3
  18. package/dist/core/memory.d.ts +5 -0
  19. package/dist/core/memory.js +6 -4
  20. package/dist/core/runner/assemble-result.js +9 -0
  21. package/dist/core/runner/prepare-task.d.ts +12 -1
  22. package/dist/core/runner/prepare-task.js +110 -38
  23. package/dist/core/runner/runtask.d.ts +12 -0
  24. package/dist/core/runner/runtask.js +169 -37
  25. package/dist/core/runner/session-file-state-replay.d.ts +7 -0
  26. package/dist/core/runner/session-file-state-replay.js +56 -0
  27. package/dist/core/runner/synthetic-tools.js +1 -1
  28. package/dist/core/runner/tool-disclosure.d.ts +1 -0
  29. package/dist/core/runner/tool-disclosure.js +24 -9
  30. package/dist/core/runner/tool-output-projection.js +5 -4
  31. package/dist/core/runner/turn-attachments.d.ts +2 -0
  32. package/dist/core/runner/turn-attachments.js +14 -5
  33. package/dist/core/session-reconcile.d.ts +7 -3
  34. package/dist/core/session-reconcile.js +3 -2
  35. package/dist/core/strategy-store.d.ts +1 -1
  36. package/dist/core/strategy-store.js +27 -4
  37. package/dist/core/task-registry-agent.d.ts +3 -0
  38. package/dist/core/task-registry-agent.js +9 -2
  39. package/dist/core/task-registry-shared.d.ts +1 -0
  40. package/dist/core/task-registry.d.ts +2 -0
  41. package/dist/core/tools.js +9 -1
  42. package/dist/core/trace.d.ts +1 -0
  43. package/dist/core/types.d.ts +8 -1
  44. package/dist/engine/loop/agent-loop.js +168 -22
  45. package/dist/orchestration/run-workflow-tool.js +1 -1
  46. package/dist/orchestration/workflow-governance.js +19 -0
  47. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  48. package/dist/orchestration/workflow-primitives.js +4 -1
  49. package/dist/orchestration/workflow.js +1 -1
  50. package/dist/prompts/coordinator.d.ts +1 -1
  51. package/dist/prompts/coordinator.js +1 -1
  52. package/dist/prompts/default.d.ts +1 -0
  53. package/dist/prompts/default.js +3 -0
  54. package/dist/stores/file/memory-store.js +3 -7
  55. package/dist/tools/fs/fs-bash.js +7 -4
  56. package/dist/tools/fs/fs-shared.d.ts +1 -0
  57. package/dist/tools/fs/fs-shared.js +4 -0
  58. package/dist/tools/fs/index.d.ts +1 -0
  59. package/dist/tools/fs/index.js +7 -4
  60. package/dist/tools/web.d.ts +21 -1
  61. package/dist/tools/web.js +126 -11
  62. package/package.json +2 -2
@@ -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);
@@ -313,12 +319,38 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
313
319
  throw e;
314
320
  }
315
321
  }
322
+ if (spec.toolMaterializeStrategy !== undefined && spec.toolMaterializeStrategy !== "swap" && spec.toolMaterializeStrategy !== "static") {
323
+ const e = new Error(`toolMaterializeStrategy must be "swap" or "static" (got ${JSON.stringify(spec.toolMaterializeStrategy)}).`);
324
+ e.code = "config.tool_materialize_invalid";
325
+ throw e;
326
+ }
327
+ if (spec.toolMaterializeStrategy === "static" && spec.deferSelfResolve === false) {
328
+ const e = new Error(`toolMaterializeStrategy "static" cannot be combined with deferSelfResolve: false — with the direct-call ` +
329
+ `lane disabled a placeholder is never swapped and never self-resolves, so no deferred tool could ever be ` +
330
+ `called. Use "swap", or leave deferSelfResolve on.`);
331
+ e.code = "config.tool_materialize_unreachable";
332
+ throw e;
333
+ }
316
334
  if (resume === undefined && spec.objective.trim().length === 0) {
317
335
  const e = new Error("TaskSpec.objective is empty — a task needs an instruction (an empty user message is rejected by strict model endpoints and would fail every later request of the session).");
318
336
  e.code = "config.empty_objective";
319
337
  throw e;
320
338
  }
321
339
  resolveTaskLimits(spec.limits);
340
+ if (spec.resourceSuspend !== undefined) {
341
+ const rsus = spec.resourceSuspend;
342
+ if (typeof rsus.scope !== "string" || rsus.scope === "") {
343
+ 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.`);
344
+ }
345
+ for (const key of ["totalBudgetUsd", "totalTokens", "maxSlices", "ttlMs"]) {
346
+ const value = rsus[key];
347
+ if (value === undefined)
348
+ continue;
349
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
350
+ 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.`);
351
+ }
352
+ }
353
+ }
322
354
  const usageWindows = resolveUsageWindows(deps.usageWindows);
323
355
  const brainCallGuardrailRef = {};
324
356
  const brainCallGuardrailMs = resolveBrainCallGuardrailMs(spec.limits?.brainCallGuardrailMs ?? deps.brainCallGuardrailMs);
@@ -411,6 +443,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
411
443
  let acquired;
412
444
  let session;
413
445
  let conflictRef;
446
+ let wakeRecovered = [];
414
447
  let resumeAtBeforeParentId = null;
415
448
  for (let attempt = 0;; attempt++) {
416
449
  try {
@@ -487,7 +520,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
487
520
  }
488
521
  await session.getStorage().setLeafId(spec.resumeAtMode === "before" ? entry.parentId : spec.resumeAt);
489
522
  }
490
- await reconcileInterruptedSession(session, toolEffects, resume?.suspendedBatch);
523
+ wakeRecovered = (await reconcileInterruptedSession(session, toolEffects, resume?.suspendedBatch)).recovered;
491
524
  break;
492
525
  }
493
526
  catch (err) {
@@ -910,6 +943,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
910
943
  const effectiveShellGate = inheritedShellGate !== undefined && shellGateRank[inheritedShellGate] > shellGateRank[specShellGate] ? inheritedShellGate : specShellGate;
911
944
  const ownSessionRulesRef = {};
912
945
  const frozenOnAsk = spec.onAsk ?? deps.onAsk;
946
+ const frozenOnQuestion = spec.onQuestion ?? deps.onQuestion;
913
947
  const inheritedGateForChildren = () => {
914
948
  const ancestorRules = [
915
949
  ...(inheritedAncestorRules ?? []),
@@ -945,6 +979,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
945
979
  thinkingLevel: harnessRef.current?.getThinkingLevel(),
946
980
  principal: spec.principal,
947
981
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
982
+ ...(frozenOnQuestion !== undefined ? { onQuestion: frozenOnQuestion } : {}),
983
+ ...(spec.handsReadOnly === true ? { handsReadOnly: true } : {}),
984
+ ...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
948
985
  oneShot: spec.oneShot,
949
986
  clientContext: spec.clientContext,
950
987
  excludeTools: toolFaceSnapshot.exclude,
@@ -952,6 +989,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
952
989
  alwaysLoadTools: toolFaceSnapshot.alwaysLoad,
953
990
  promptProfile,
954
991
  ...(spec.additionalDirectories !== undefined ? { additionalDirectories: Object.freeze([...spec.additionalDirectories]) } : {}),
992
+ ...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
955
993
  ...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
956
994
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
957
995
  parentCwd: taskRootPath,
@@ -961,6 +999,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
961
999
  inheritedGateForChildren,
962
1000
  ...(autoModeDecider ? { autoModeReview: { decider: autoModeDecider } } : {}),
963
1001
  ...(spec.durableApproval !== undefined ? { durableApprovalForChildren: { ...spec.durableApproval } } : {}),
1002
+ ...(spec.checkpointStore === null ? { checkpointStoreDisabledForChildren: true } : {}),
964
1003
  taskId: hostTaskId,
965
1004
  ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
966
1005
  ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
@@ -1014,7 +1053,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1014
1053
  tools.push(createReportFindingsTool());
1015
1054
  }
1016
1055
  if (spec.enablePlanMode === true && spec.interactiveTools !== false) {
1017
- const planReviewFace = (spec.checkpointStore ?? deps.checkpointStore) !== undefined;
1056
+ const planReviewFace = resolveCheckpointStore(spec, deps) !== undefined;
1018
1057
  if (spec.interactiveTools === true || planReviewFace) {
1019
1058
  tools.push(defineTool(createPresentPlanTool(requestReview)));
1020
1059
  if (planReviewFace) {
@@ -1125,6 +1164,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1125
1164
  gates: resume?.priorHumanReview?.gates ? [...resume.priorHumanReview.gates] : [],
1126
1165
  };
1127
1166
  const priorSuspendCount = resume?.priorSuspendCount ?? 0;
1167
+ const suspendProgressRef = { executedApproved: false };
1168
+ const suspendChainBase = () => (suspendProgressRef.executedApproved ? 0 : priorSuspendCount);
1128
1169
  const maxSuspends = spec.maxSuspends ?? deps.maxSuspends ?? DEFAULT_MAX_SUSPENDS;
1129
1170
  const maxSlices = spec.resourceSuspend?.maxSlices;
1130
1171
  const priorLedger = resume?.priorLedger;
@@ -1308,34 +1349,40 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1308
1349
  const callIssuedAtRef = {};
1309
1350
  const memoryWriteGateRef = {};
1310
1351
  const additionalRootsCanonical = [];
1352
+ const additionalReadRootsCanonical = [];
1311
1353
  let attachmentRootCanonical;
1312
1354
  if (handsEnabled) {
1313
1355
  const rootRaw = taskRootPath;
1314
1356
  const canon = await executionEnv.canonicalPath(rootRaw);
1315
1357
  const rootCanonical = canon.ok ? canon.value : rootRaw;
1316
1358
  attachmentRootCanonical = rootCanonical;
1317
- for (const dir of spec.additionalDirectories ?? []) {
1318
- if (!dir || !dir.trim())
1319
- continue;
1320
- const c = await executionEnv.canonicalPath(dir);
1321
- if (c.ok) {
1322
- additionalRootsCanonical.push(c.value);
1323
- }
1324
- else {
1325
- deps.onError?.(new Error(`additionalDirectories entry skipped (cannot canonicalize): ${dir}`), {
1326
- phase: "config",
1327
- sessionId,
1328
- });
1329
- emitTrace(deps.tracer, () => ({
1330
- kind: "config.additional_directory_skipped",
1331
- version: 1,
1332
- taskId: hostTaskId,
1333
- entry: dir,
1334
- reason: `${c.error.code}: ${c.error.message}`,
1335
- ts: Date.now(),
1336
- }));
1359
+ const canonicalizeExtraDirs = async (dirs, field, sink) => {
1360
+ for (const dir of dirs ?? []) {
1361
+ if (!dir || !dir.trim())
1362
+ continue;
1363
+ const c = await executionEnv.canonicalPath(dir);
1364
+ if (c.ok) {
1365
+ sink.push(c.value);
1366
+ }
1367
+ else {
1368
+ deps.onError?.(new Error(`${field} entry skipped (cannot canonicalize): ${dir}`), {
1369
+ phase: "config",
1370
+ sessionId,
1371
+ });
1372
+ emitTrace(deps.tracer, () => ({
1373
+ kind: "config.additional_directory_skipped",
1374
+ version: 1,
1375
+ taskId: hostTaskId,
1376
+ entry: dir,
1377
+ ...(field !== "additionalDirectories" ? { field } : {}),
1378
+ reason: `${c.error.code}: ${c.error.message}`,
1379
+ ts: Date.now(),
1380
+ }));
1381
+ }
1337
1382
  }
1338
- }
1383
+ };
1384
+ await canonicalizeExtraDirs(spec.additionalDirectories, "additionalDirectories", additionalRootsCanonical);
1385
+ await canonicalizeExtraDirs(spec.additionalReadDirectories, "additionalReadDirectories", additionalReadRootsCanonical);
1339
1386
  if (spec.envFacts?.scratchpadDir) {
1340
1387
  let c = await executionEnv.canonicalPath(spec.envFacts.scratchpadDir);
1341
1388
  if (!c.ok && c.error.code === "not_found") {
@@ -1352,6 +1399,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1352
1399
  }
1353
1400
  const readFileState = new Map((resume?.seed.readFileState ?? []).map(([k, v]) => [rebaseRestoredPath(k), v]));
1354
1401
  readFileStateForCheckpoint = readFileState;
1402
+ if (resume === undefined && spec.sessionId !== undefined) {
1403
+ const prior = await session.buildContext().catch(() => undefined);
1404
+ for (const rec of wholeFileRecordsFromTranscript(prior?.messages ?? [])) {
1405
+ const rk = await resolveKey(executionEnv, rootCanonical, rec.path, undefined, undefined, additionalRootsCanonical);
1406
+ if (rk.ok)
1407
+ seedReadFileStateFromTranscript(readFileState, rk.key, rec.content, rec.at);
1408
+ }
1409
+ }
1355
1410
  seedContextFiles = async (files) => {
1356
1411
  for (const f of files) {
1357
1412
  const rk = await resolveKey(executionEnv, rootCanonical, f.path, undefined, undefined, additionalRootsCanonical);
@@ -1378,6 +1433,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1378
1433
  }
1379
1434
  const band = createHandsToolkit(executionEnv, readFileState, rootCanonical, {
1380
1435
  ...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
1436
+ ...(additionalReadRootsCanonical.length > 0 ? { additionalReadRoots: additionalReadRootsCanonical } : {}),
1381
1437
  includeShell: handsIncludeShell,
1382
1438
  readOnly: handsReadOnly,
1383
1439
  ...(handsCwdRef ? { cwdRef: handsCwdRef } : {}),
@@ -1419,7 +1475,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1419
1475
  irreversibilityTier.set("Bash", shellGate === "always" ? "always" : "maybe");
1420
1476
  irreversibleTools.add("Bash");
1421
1477
  const shellReadBoundary = () => ({
1422
- roots: [rootCanonical, ...additionalRootsCanonical],
1478
+ roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
1423
1479
  ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}),
1424
1480
  });
1425
1481
  if (shellGate === "classify")
@@ -1533,8 +1589,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1533
1589
  }
1534
1590
  if (offloadStore)
1535
1591
  tools.push(createReadToolResultTool(offloadStore));
1536
- const onQuestion = spec.onQuestion ?? deps.onQuestion;
1537
- const durableQuestionFace = (spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
1592
+ const onQuestion = frozenOnQuestion;
1593
+ const durableQuestionFace = resolveCheckpointStore(spec, deps) !== undefined &&
1538
1594
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true);
1539
1595
  if (spec.interactiveTools === true || (spec.interactiveTools !== false && (onQuestion !== undefined || durableQuestionFace)))
1540
1596
  tools.push(createAskUserQuestionTool(onQuestion, { principal: spec.principal, sourceTaskId: sessionId }));
@@ -1743,6 +1799,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1743
1799
  if (additionalRootsCanonical.length > 0) {
1744
1800
  envFacts.additionalDirectories = [...additionalRootsCanonical];
1745
1801
  }
1802
+ if (additionalReadRootsCanonical.length > 0) {
1803
+ envFacts.additionalReadDirectories = [...additionalReadRootsCanonical];
1804
+ }
1746
1805
  try {
1747
1806
  const probe = await executionEnv.exec('uname -s; uname -r; (git rev-parse --is-inside-work-tree 2>/dev/null || echo false); (git symbolic-ref --short -q HEAD 2>/dev/null || echo "HEAD (detached)"); (git rev-parse --show-toplevel 2>/dev/null || echo); (test -n "$(git status --porcelain 2>/dev/null | head -1)" && echo dirty || echo clean); (s=$(ps -p $$ -o comm= 2>/dev/null); s=${s##*/}; echo "${s#-}"); (test "$(git rev-parse --git-dir 2>/dev/null)" != "$(git rev-parse --git-common-dir 2>/dev/null)" && echo linked || echo main); (pwd -P 2>/dev/null || pwd)', { cwd: envFacts.cwd, timeout: 10 });
1748
1807
  if (probe.ok && probe.value.exitCode === 0) {
@@ -1987,6 +2046,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1987
2046
  .filter((s) => s.status === "failed")
1988
2047
  .map((s) => ({ name: inlineUntrusted(s.name, 160), ...(s.error !== undefined ? { error: inlineUntrusted(s.error, 240) } : {}) }));
1989
2048
  let toolsDeltaRef;
2049
+ let toolMaterializeStatic = false;
1990
2050
  if (deferred.size > 0 || failedMcpServers.length > 0) {
1991
2051
  toolsDeltaRef = { pending: [], pendingRemoved: [], pendingReadded: [], pendingFailed: failedMcpServers };
1992
2052
  }
@@ -2063,8 +2123,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2063
2123
  };
2064
2124
  offloadReachableToolsRef.current = callableToolNames;
2065
2125
  let toolSearch;
2126
+ const envStrategy = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
2127
+ if (envStrategy !== undefined && envStrategy !== "swap" && envStrategy !== "static") {
2128
+ const e = new Error(`SEMA_TOOL_MATERIALIZE_STRATEGY must be "swap" or "static" (got ${JSON.stringify(envStrategy)}).`);
2129
+ e.code = "config.tool_materialize_invalid";
2130
+ throw e;
2131
+ }
2132
+ const materializeStatic = (spec.toolMaterializeStrategy ?? envStrategy ?? "static") === "static" && spec.deferSelfResolve !== false;
2133
+ toolMaterializeStatic = materializeStatic;
2066
2134
  const buildToolList = (active) => {
2067
- const list = tools.map((t) => (deferred.has(t.name) && !active.has(t.name) ? placeholders.get(t.name) : t));
2135
+ const list = tools.map((t) => (deferred.has(t.name) && (materializeStatic || !active.has(t.name)) ? placeholders.get(t.name) : t));
2068
2136
  list.push(toolSearch);
2069
2137
  return list;
2070
2138
  };
@@ -2105,6 +2173,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2105
2173
  listingRide: (newly) => listingRideRef.current?.(newly),
2106
2174
  mountedNames: callableToolNames,
2107
2175
  directCallEnabled: spec.deferSelfResolve !== false,
2176
+ ...(materializeStatic
2177
+ ? { staticSchemaFor: (name) => tools.find((t) => t.name === name)?.parameters }
2178
+ : {}),
2108
2179
  serializeActivation,
2109
2180
  });
2110
2181
  harnessTools = buildToolList(activeTools);
@@ -2462,7 +2533,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2462
2533
  if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME)
2463
2534
  return first;
2464
2535
  if (pc.durableMandate === true) {
2465
- if ((spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
2536
+ if (resolveCheckpointStore(spec, deps) !== undefined &&
2466
2537
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
2467
2538
  markInheritedUnavailable(creq.toolCallId)) {
2468
2539
  return first;
@@ -2518,7 +2589,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2518
2589
  if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME)
2519
2590
  return decision;
2520
2591
  if (pc.durableMandate === true) {
2521
- if ((spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
2592
+ if (resolveCheckpointStore(spec, deps) !== undefined &&
2522
2593
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
2523
2594
  markInheritedUnavailable(creq.toolCallId)) {
2524
2595
  return decision;
@@ -2678,13 +2749,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2678
2749
  const blockedTracked = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure || hooks?.postToolBatch);
2679
2750
  const restoreSurfaceGap = ownedEnv !== undefined && isRemoteExecutionEnv(ownedEnv) && ownedEnv.capabilities.suspendable ? missingRestoreSurface(ownedEnv) : [];
2680
2751
  const incompleteSuspendAdapter = restoreSurfaceGap.length > 0 ? restoreSurfaceGap : undefined;
2681
- const durableSuspendInfraReady = (spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
2752
+ const durableSuspendInfraReady = resolveCheckpointStore(spec, deps) !== undefined &&
2682
2753
  !(offloadStore !== undefined && isVolatileOffloadStore(offloadStore)) &&
2683
2754
  (ownedEnv === undefined || isRemoteExecutionEnv(ownedEnv)) &&
2684
2755
  incompleteSuspendAdapter === undefined;
2685
2756
  const resourceSuspendEligible = spec.resourceSuspend !== undefined && durableSuspendInfraReady;
2686
2757
  if (spec.resourceSuspend !== undefined && !resourceSuspendEligible) {
2687
- const why = (spec.checkpointStore ?? deps.checkpointStore) === undefined
2758
+ const why = resolveCheckpointStore(spec, deps) === undefined
2688
2759
  ? "no CheckpointStore is wired"
2689
2760
  : offloadStore !== undefined && isVolatileOffloadStore(offloadStore)
2690
2761
  ? "tool-result offload uses the in-memory store (a resume needs durable results)"
@@ -2842,7 +2913,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2842
2913
  catch {
2843
2914
  }
2844
2915
  };
2845
- const checkpointStore = spec.checkpointStore ?? deps.checkpointStore;
2916
+ const checkpointStore = resolveCheckpointStore(spec, deps);
2846
2917
  const durableApproval = spec.durableApproval ??
2847
2918
  (runtimeCaps?.forceDurableGate ? { scope: spec.principal || DEFAULT_IRREVERSIBLE_SCOPE } : undefined);
2848
2919
  const inFlightSpendMicroUsd = () => {
@@ -3049,7 +3120,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3049
3120
  }
3050
3121
  return false;
3051
3122
  }
3052
- if (suspendLoopCapHit(priorSuspendCount, maxSuspends, " for a plan_review (likely a resume/restart loop)."))
3123
+ if (suspendLoopCapHit(suspendChainBase(), maxSuspends, " for a plan_review (likely a resume/restart loop)."))
3053
3124
  return false;
3054
3125
  const leafId = await session.getLeafId();
3055
3126
  if (!leafId) {
@@ -3097,7 +3168,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3097
3168
  createdAt: mintedAt,
3098
3169
  suspendedAt: now(),
3099
3170
  deadline: mintedAt + (sanitizedTtlMs(spec.durableApproval?.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS),
3100
- suspendCount: priorSuspendCount + 1,
3171
+ suspendCount: suspendChainBase() + 1,
3101
3172
  humanReview: humanReviewRef.count > 0
3102
3173
  ? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
3103
3174
  : undefined,
@@ -3158,7 +3229,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3158
3229
  if (abortController.signal.aborted) {
3159
3230
  return undefined;
3160
3231
  }
3161
- if (suspendLoopCapHit(priorSuspendCount, maxSuspends, ` for tool "${req.toolName}" — likely a resume/restart loop.`))
3232
+ if (suspendLoopCapHit(suspendChainBase(), maxSuspends, ` for tool "${req.toolName}" — likely a resume/restart loop.`))
3162
3233
  return undefined;
3163
3234
  if (remoteEnv !== undefined) {
3164
3235
  if (hasBackgroundShell(remoteEnv))
@@ -3181,6 +3252,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3181
3252
  args: postHookArgs,
3182
3253
  safety,
3183
3254
  shellGated: (req.toolName === "Bash" && shellGatedBash) || (req.toolName === "Monitor" && shellGatedMonitor),
3255
+ ...(effectiveShellGate !== "off" ? { shellGateDoctrine: effectiveShellGate } : {}),
3184
3256
  });
3185
3257
  gate =
3186
3258
  safety !== undefined
@@ -3241,7 +3313,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3241
3313
  humanReview: humanReviewRef.count > 0
3242
3314
  ? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
3243
3315
  : undefined,
3244
- suspendCount: priorSuspendCount + 1,
3316
+ suspendCount: suspendChainBase() + 1,
3245
3317
  resourceLedger: approvalLedger,
3246
3318
  sourceTaskId: sessionId,
3247
3319
  ...(spec.principal ? { principal: spec.principal } : {}),
@@ -3602,7 +3674,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3602
3674
  : undefined;
3603
3675
  overheadState.promptChars = systemPrompt.length;
3604
3676
  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 } : {}) });
3677
+ 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 } : {}), toolMaterializeStatic, ownedEnv, suspendRef, suspendProgressRef, 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
3678
  const prepared = buildPrepared();
3607
3679
  preparedHolder.current = prepared;
3608
3680
  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;