@sema-agent/core 5.64.0 → 6.0.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 (165) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/agents/subagent.d.ts +2 -2
  3. package/dist/agents/subagent.js +11 -0
  4. package/dist/agents/verify.d.ts +1 -1
  5. package/dist/brain/anthropic.js +1 -1
  6. package/dist/brain/errors.d.ts +29 -0
  7. package/dist/brain/errors.js +20 -0
  8. package/dist/brain/open-responses.js +2 -2
  9. package/dist/brain/route-adjudicator.d.ts +8 -1
  10. package/dist/brain/route-adjudicator.js +1 -0
  11. package/dist/brain/status-sink.js +12 -1
  12. package/dist/brain/stream-engine.js +17 -6
  13. package/dist/core/auto-compaction.d.ts +26 -0
  14. package/dist/core/auto-compaction.js +7 -2
  15. package/dist/core/auto-mode-arming.d.ts +138 -0
  16. package/dist/core/auto-mode-arming.js +181 -0
  17. package/dist/core/auto-mode-defaults.d.ts +13 -0
  18. package/dist/core/auto-mode-defaults.js +5 -0
  19. package/dist/core/auto-mode-prompt.d.ts +14 -3
  20. package/dist/core/auto-mode-prompt.js +10 -7
  21. package/dist/core/auto-mode-rebuild.d.ts +75 -0
  22. package/dist/core/auto-mode-rebuild.js +41 -0
  23. package/dist/core/auto-mode.d.ts +15 -0
  24. package/dist/core/auto-mode.js +4 -2
  25. package/dist/core/checkpoint-store.d.ts +113 -4
  26. package/dist/core/context-edit.d.ts +47 -5
  27. package/dist/core/context-guard.d.ts +1 -1
  28. package/dist/core/file-history-retention.d.ts +106 -0
  29. package/dist/core/file-history-retention.js +36 -0
  30. package/dist/core/file-history-store.d.ts +768 -0
  31. package/dist/core/file-history-store.js +880 -0
  32. package/dist/core/governance-codes.d.ts +2 -1
  33. package/dist/core/governance-codes.js +14 -0
  34. package/dist/core/hooks.d.ts +48 -8
  35. package/dist/core/hooks.js +39 -22
  36. package/dist/core/lsp.d.ts +2 -2
  37. package/dist/core/mcp.d.ts +29 -7
  38. package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
  39. package/dist/core/memory-engine/consolidation-driver.js +71 -4
  40. package/dist/core/memory-engine/consolidation.d.ts +25 -2
  41. package/dist/core/memory-engine/consolidation.js +4 -1
  42. package/dist/core/memory-engine/distiller.d.ts +84 -1
  43. package/dist/core/memory-engine/distiller.js +68 -0
  44. package/dist/core/memory-engine/dual-root.js +6 -0
  45. package/dist/core/memory-engine/engine.d.ts +329 -15
  46. package/dist/core/memory-engine/engine.js +364 -34
  47. package/dist/core/memory-engine/file-backend.d.ts +30 -0
  48. package/dist/core/memory-engine/file-backend.js +14 -13
  49. package/dist/core/memory-engine/frontmatter.d.ts +22 -1
  50. package/dist/core/memory-engine/frontmatter.js +3 -0
  51. package/dist/core/memory-engine/header-hints.d.ts +5 -0
  52. package/dist/core/memory-engine/index.d.ts +5 -4
  53. package/dist/core/memory-engine/index.js +5 -4
  54. package/dist/core/memory-engine/layout.d.ts +88 -2
  55. package/dist/core/memory-engine/layout.js +112 -3
  56. package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
  57. package/dist/core/memory-engine/provenance-wording.js +3 -0
  58. package/dist/core/memory-engine/tools.d.ts +89 -8
  59. package/dist/core/memory-engine/tools.js +263 -22
  60. package/dist/core/memory-engine/types.d.ts +80 -1
  61. package/dist/core/memory-recall.d.ts +6 -0
  62. package/dist/core/memory.d.ts +27 -1
  63. package/dist/core/memory.js +16 -2
  64. package/dist/core/permission-rule-consent.d.ts +20 -0
  65. package/dist/core/permission-rule-consent.js +12 -3
  66. package/dist/core/permission-rule-model.d.ts +67 -7
  67. package/dist/core/permission-rule-model.js +53 -7
  68. package/dist/core/permission-rule-store.js +15 -10
  69. package/dist/core/permission-rule-sync.js +15 -11
  70. package/dist/core/remote-env.d.ts +3 -3
  71. package/dist/core/retention-policy.d.ts +9 -0
  72. package/dist/core/retention-policy.js +5 -2
  73. package/dist/core/retention.d.ts +13 -2
  74. package/dist/core/runner/assemble-result.d.ts +19 -1
  75. package/dist/core/runner/assemble-result.js +17 -2
  76. package/dist/core/runner/compaction-call-options.d.ts +93 -0
  77. package/dist/core/runner/compaction-call-options.js +3 -0
  78. package/dist/core/runner/memory-capture-optout.d.ts +80 -0
  79. package/dist/core/runner/memory-capture-optout.js +53 -0
  80. package/dist/core/runner/prepare-config-doors.d.ts +5 -0
  81. package/dist/core/runner/prepare-config-doors.js +16 -0
  82. package/dist/core/runner/prepare-hands-readface.d.ts +119 -5
  83. package/dist/core/runner/prepare-hands-readface.js +103 -8
  84. package/dist/core/runner/prepare-memory.d.ts +88 -0
  85. package/dist/core/runner/prepare-memory.js +306 -25
  86. package/dist/core/runner/prepare-task.d.ts +156 -5
  87. package/dist/core/runner/prepare-task.js +488 -98
  88. package/dist/core/runner/runtask.d.ts +27 -20
  89. package/dist/core/runner/runtask.js +283 -99
  90. package/dist/core/runner/session-file-state-replay.d.ts +18 -10
  91. package/dist/core/runner/session-file-state-replay.js +52 -1
  92. package/dist/core/runner/tool-disclosure.js +2 -1
  93. package/dist/core/runner/turn-attachments.d.ts +22 -12
  94. package/dist/core/session-store.d.ts +1 -1
  95. package/dist/core/session-store.js +6 -1
  96. package/dist/core/session.d.ts +34 -1
  97. package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
  98. package/dist/core/store-contracts/file-history-store-contract.js +720 -0
  99. package/dist/core/stub-env.d.ts +4 -0
  100. package/dist/core/stub-env.js +1 -0
  101. package/dist/core/task-registry-shared.js +30 -2
  102. package/dist/core/tool-errors.js +1 -0
  103. package/dist/core/tool-policy.d.ts +172 -1
  104. package/dist/core/tool-policy.js +32 -1
  105. package/dist/core/tool-result-store.js +2 -1
  106. package/dist/core/trace.d.ts +24 -0
  107. package/dist/core/types.d.ts +875 -97
  108. package/dist/core/types.js +4 -3
  109. package/dist/core/untrusted-text.d.ts +1 -1
  110. package/dist/core/untrusted-text.js +8 -0
  111. package/dist/core/workflow-run-store-contract.js +17 -0
  112. package/dist/core/workflow-run-store.d.ts +20 -0
  113. package/dist/core/workflow-run-store.js +1 -0
  114. package/dist/engine/compaction/compaction.d.ts +88 -10
  115. package/dist/engine/compaction/compaction.js +109 -30
  116. package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
  117. package/dist/engine/execution-env/node-execution-env.js +28 -0
  118. package/dist/engine/harness/agent-harness.d.ts +52 -1
  119. package/dist/engine/harness/agent-harness.js +36 -1
  120. package/dist/engine/harness/types.d.ts +44 -1
  121. package/dist/engine/llm/types.d.ts +50 -4
  122. package/dist/engine/loop/agent-loop.d.ts +5 -1
  123. package/dist/engine/loop/agent-loop.js +25 -0
  124. package/dist/engine/loop/types.d.ts +19 -0
  125. package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
  126. package/dist/engine/session/session.js +1 -1
  127. package/dist/index.d.ts +18 -8
  128. package/dist/index.js +14 -6
  129. package/dist/orchestration/run-workflow-tool.d.ts +20 -2
  130. package/dist/orchestration/run-workflow-tool.js +22 -3
  131. package/dist/orchestration/workflow-governance.d.ts +59 -1
  132. package/dist/orchestration/workflow-governance.js +61 -8
  133. package/dist/orchestration/workflow-meta.d.ts +4 -2
  134. package/dist/orchestration/workflow-primitives.js +56 -13
  135. package/dist/orchestration/workflow-types.d.ts +112 -1
  136. package/dist/orchestration/workflow-types.js +2 -2
  137. package/dist/orchestration/workflow.d.ts +20 -0
  138. package/dist/orchestration/workflow.js +182 -14
  139. package/dist/prompt-assembly/event-registry.js +1 -1
  140. package/dist/prompts/default.d.ts +15 -7
  141. package/dist/prompts/default.js +3 -0
  142. package/dist/stores/file/file-history-store.d.ts +368 -0
  143. package/dist/stores/file/file-history-store.js +1248 -0
  144. package/dist/stores/file/index.d.ts +22 -13
  145. package/dist/stores/file/index.js +4 -4
  146. package/dist/stores/file/permission-rule-store.js +1 -0
  147. package/dist/stores/file/strategy-store.d.ts +3 -3
  148. package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
  149. package/dist/tools/fs/bash-readonly-classifier.js +106 -4
  150. package/dist/tools/fs/fs-bash.js +9 -5
  151. package/dist/tools/fs/fs-shared.d.ts +52 -1
  152. package/dist/tools/fs/fs-shared.js +14 -0
  153. package/dist/tools/fs/fs-write.d.ts +5 -5
  154. package/dist/tools/fs/fs-write.js +71 -14
  155. package/dist/tools/fs/index.d.ts +6 -1
  156. package/dist/tools/fs/index.js +1 -1
  157. package/dist/tools/web.js +2 -1
  158. package/package.json +5 -1
  159. package/test/export-surface.snapshot.json +159 -23
  160. package/dist/core/file-snapshot-store.d.ts +0 -165
  161. package/dist/core/file-snapshot-store.js +0 -259
  162. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
  163. package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
  164. package/dist/stores/file/file-snapshot-store.d.ts +0 -58
  165. package/dist/stores/file/file-snapshot-store.js +0 -353
@@ -6,6 +6,7 @@ const PROMPT_HASH_SALT = randomBytes(16);
6
6
  import { sanitizeCompactionSettings } from "../auto-compaction.js";
7
7
  import { projectStaleToolResults, resolveStaleToolResultOffload } from "./compaction-call-options.js";
8
8
  import { createAutoModeDecider } from "../auto-mode.js";
9
+ import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
9
10
  import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
10
11
  import { resolveTaskModel } from "../roles.js";
11
12
  import { primaryActivityArg } from "../arg-summary.js";
@@ -20,7 +21,7 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial, resolveDelegationEn
20
21
  import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
21
22
  import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
22
23
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
23
- import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
24
+ import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOfLayer, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
24
25
  const PERSISTED_RULE_TOOL = "Bash";
25
26
  import { findAdmittingRule, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
26
27
  import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
@@ -49,7 +50,6 @@ import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDisco
49
50
  import { createSharedMemoryTools } from "../shared-memory/tools.js";
50
51
  import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
51
52
  import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
52
- import { memoryRecallDisciplineSegment } from "../memory-engine/engine.js";
53
53
  import { classifyToolContentOrigin, contentOriginPollutes, delegationCallIsExternal } from "../memory-engine/content-origin.js";
54
54
  import { narrowContentSafety, readCardAttestation } from "../memory-engine/delegation-provenance.js";
55
55
  import { recordSyncUnattestable, sessionSettlements } from "../memory-engine/delegation-settlement.js";
@@ -97,6 +97,7 @@ import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDec
97
97
  import { durableParkGapFor } from "../park-selfcheck.js";
98
98
  import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
99
99
  import { deliverEngineNotice } from "../types.js";
100
+ import { resolveTrackKey } from "../file-history-store.js";
100
101
  let announcedMaterializeEnvBySink = new WeakMap();
101
102
  const announcedMaterializeEnvConsole = new Set();
102
103
  function materializeEnvLedger(onNotice) {
@@ -218,6 +219,7 @@ function announceDeclaredMcpContentClasses(args) {
218
219
  toolCount,
219
220
  ...(declaredClass === "execution" ? { execIsExternalContent: args.execIsExternalContent } : {}),
220
221
  sessionId: args.sessionId,
222
+ runId: args.runId,
221
223
  },
222
224
  });
223
225
  }
@@ -324,7 +326,7 @@ class ParkRefusal extends Error {
324
326
  export { resolveCheckpointStore } from "../checkpoint-store.js";
325
327
  export { isFableFamilyModelId, resolveAttachmentsConfig, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
326
328
  export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
327
- function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
329
+ function buildMicroCompactState(deps, model, sessionId, runId, offloadStore, knob) {
328
330
  const triggerWindow = resolveTriggerWindow(model);
329
331
  if (triggerWindow.clamped) {
330
332
  deliverEngineNotice(deps.onNotice, {
@@ -337,6 +339,7 @@ function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
337
339
  declaredAutoCompactTokens: triggerWindow.declaredAutoCompactTokens,
338
340
  physicalWindow: triggerWindow.physicalWindow,
339
341
  sessionId,
342
+ runId,
340
343
  },
341
344
  });
342
345
  }
@@ -433,6 +436,187 @@ export async function runGuardChain(args) {
433
436
  const t = applyGuardTrim(working, anchoredWorking);
434
437
  return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
435
438
  }
439
+ function restoredAbsPathsOf(restored, root) {
440
+ const out = [];
441
+ for (const key of [...restored.filesChanged, ...restored.failed.map((f) => f.path)]) {
442
+ const abs = resolveTrackKey(root, key);
443
+ if (abs.ok)
444
+ out.push(abs.abs);
445
+ }
446
+ return out;
447
+ }
448
+ function resolveWiredFileHistoryStore(candidate) {
449
+ if (candidate === undefined)
450
+ return undefined;
451
+ const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
452
+ const verbOf = (v) => {
453
+ try {
454
+ return candidate[v];
455
+ }
456
+ catch {
457
+ return undefined;
458
+ }
459
+ };
460
+ const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
461
+ const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
462
+ if (missing.length > 0) {
463
+ const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
464
+ const e = new Error(`RunnerDeps.fileHistoryStore was wired with ${shape} — missing contract verbs: ${missing.join(", ")}. Refusing prepare rather than half-wiring the rewind seat (a present-but-unusable store makes every Edit/Write/NotebookEdit fail its first-touch backup while restore requests report "no store wired"). Omit the key to run without file history.`);
465
+ e.code = "config.invalid_file_history_store";
466
+ throw e;
467
+ }
468
+ return candidate;
469
+ }
470
+ const LEGACY_PROBE_TIMEOUT_MS = 2_000;
471
+ const LEGACY_PROBE_TIMED_OUT = Symbol("legacy-rewind-probe-timeout");
472
+ async function legacyRewindEpochTail(deps, sessionId, entryId, form) {
473
+ const probe = deps.legacyRewindBoundaryProbe;
474
+ let verdict;
475
+ if (probe !== undefined) {
476
+ try {
477
+ let timer;
478
+ const timeout = new Promise((resolve) => {
479
+ timer = setTimeout(() => resolve(LEGACY_PROBE_TIMED_OUT), LEGACY_PROBE_TIMEOUT_MS);
480
+ });
481
+ try {
482
+ verdict = await Promise.race([Promise.resolve(probe({ sessionId, entryId })), timeout]);
483
+ }
484
+ finally {
485
+ clearTimeout(timer);
486
+ }
487
+ if (verdict === LEGACY_PROBE_TIMED_OUT) {
488
+ verdict = undefined;
489
+ try {
490
+ deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe did not answer within ${LEGACY_PROBE_TIMEOUT_MS}ms while explaining a rewind refusal — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
491
+ }
492
+ catch {
493
+ }
494
+ }
495
+ }
496
+ catch (err) {
497
+ verdict = undefined;
498
+ try {
499
+ deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe threw while explaining a rewind refusal (${err.message}) — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
500
+ }
501
+ catch {
502
+ }
503
+ }
504
+ if (verdict !== undefined && typeof verdict !== "boolean") {
505
+ try {
506
+ deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe answered ${JSON.stringify(String(verdict))} for entry "${entryId}" — only true/false/undefined are meaningful, so the refusal names both possibilities instead of trusting it`), { phase: "rewind", sessionId });
507
+ }
508
+ catch {
509
+ }
510
+ verdict = undefined;
511
+ }
512
+ }
513
+ if (verdict === true) {
514
+ return `This deployment's legacy-epoch probe reports that entry "${entryId}" DOES carry a boundary from the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore — the two epochs are not interchangeable (a whole-tree manifest carries no tracked-set provenance, so replaying it here would converge files this session never edited). Restore it with the whole-tree-era tooling this deployment kept, or re-run the turns under the current engine.`;
515
+ }
516
+ if (verdict === false) {
517
+ return `This deployment's legacy-epoch probe reports no whole-tree-era boundary for entry "${entryId}" either, so the boundary simply does not exist: its turns ran without a RunnerDeps.fileHistoryStore wired, or it was reaped.`;
518
+ }
519
+ return form === "keyed"
520
+ ? `Boundaries are keyed by a turn's INITIAL entry and exist only for turns that ran with a RunnerDeps.fileHistoryStore wired; a boundary from the retired whole-tree snapshot era is not restorable by this seat, and reaped boundaries have none.`
521
+ : `Either the boundary does not exist (its turns ran without a RunnerDeps.fileHistoryStore, or it was reaped), or it was produced in the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore.`;
522
+ }
523
+ const FILE_HISTORY_BOUNDARY_TIMEOUT_MS = 30_000;
524
+ const FILE_HISTORY_CARRY_MEMO = new WeakMap();
525
+ async function adoptForkedFileHistory(store, session, sessionId, onError) {
526
+ const memo = FILE_HISTORY_CARRY_MEMO.get(store) ?? { settled: new Set(), disclosed: new Set(), inFlight: new Map() };
527
+ FILE_HISTORY_CARRY_MEMO.set(store, memo);
528
+ const discloseOnce = (err) => {
529
+ if (memo.disclosed.has(sessionId))
530
+ return;
531
+ memo.disclosed.add(sessionId);
532
+ onError?.(err, { phase: "rewind", sessionId });
533
+ };
534
+ try {
535
+ if (memo.settled.has(sessionId))
536
+ return;
537
+ const forkedFrom = (await session.getMetadata()).forkedFrom;
538
+ if (forkedFrom === undefined || forkedFrom === sessionId)
539
+ return;
540
+ const shared = memo.inFlight.get(sessionId);
541
+ if (shared !== undefined)
542
+ return await shared;
543
+ if (memo.settled.has(sessionId))
544
+ return;
545
+ const attempt = store.adoptScope(forkedFrom, sessionId).then((carried) => {
546
+ if (carried.ok || carried.error.code === "conflict") {
547
+ memo.settled.add(sessionId);
548
+ return;
549
+ }
550
+ discloseOnce(new Error(`file-history fork carry from session "${forkedFrom}" failed (${carried.error.code}): ${carried.error.message} — this forked session starts with an EMPTY rewind history, so boundaries recorded before the fork are not restorable from it (the run itself proceeds; the carry is retried on this session's next turn while its scope is still empty)`));
551
+ }, (err) => discloseOnce(err));
552
+ memo.inFlight.set(sessionId, attempt);
553
+ try {
554
+ await attempt;
555
+ }
556
+ finally {
557
+ memo.inFlight.delete(sessionId);
558
+ }
559
+ }
560
+ catch (err) {
561
+ try {
562
+ discloseOnce(err);
563
+ }
564
+ catch {
565
+ }
566
+ }
567
+ }
568
+ function createFileHistoryBoundarySeat(opts) {
569
+ let inFlight;
570
+ let begun = false;
571
+ return {
572
+ begin(entryId) {
573
+ if (begun)
574
+ return;
575
+ begun = true;
576
+ const ac = new AbortController();
577
+ let timer;
578
+ const timeout = new Promise((resolve) => {
579
+ timer = setTimeout(() => {
580
+ ac.abort();
581
+ resolve({ ok: false });
582
+ }, FILE_HISTORY_BOUNDARY_TIMEOUT_MS);
583
+ });
584
+ let work;
585
+ try {
586
+ work = opts.store.snapshot(opts.sessionId, entryId, opts.env, opts.root, ac.signal);
587
+ }
588
+ catch (err) {
589
+ work = Promise.resolve({ ok: false, error: { code: "snapshot_failed", message: `history store threw synchronously: ${err.message}` } });
590
+ }
591
+ work.catch(() => { });
592
+ inFlight = Promise.race([
593
+ work.then((r) => (r.ok ? undefined : `boundary capture failed (${r.error.code}): ${r.error.message}`)),
594
+ timeout.then(() => `boundary capture timed out after ${FILE_HISTORY_BOUNDARY_TIMEOUT_MS / 1000}s (fenced — the attempt can never publish)`),
595
+ ])
596
+ .then((failure) => {
597
+ if (failure !== undefined) {
598
+ try {
599
+ opts.onError?.(new Error(`file-history ${failure} — this turn has no boundary and cannot be rewound to (the turn itself proceeds)`), { phase: "rewind", sessionId: opts.sessionId });
600
+ }
601
+ catch {
602
+ }
603
+ }
604
+ })
605
+ .catch((err) => {
606
+ try {
607
+ opts.onError?.(err, { phase: "rewind", sessionId: opts.sessionId });
608
+ }
609
+ catch {
610
+ }
611
+ })
612
+ .finally(() => clearTimeout(timer));
613
+ },
614
+ async settle() {
615
+ if (inFlight !== undefined)
616
+ await inFlight;
617
+ },
618
+ };
619
+ }
436
620
  export function gatedCallIdOf(p) {
437
621
  if (p.suspendRef.token !== undefined)
438
622
  return p.suspendRef.gatedCallId;
@@ -508,7 +692,7 @@ function createDenyObserverNotifier(hooks, hookTimeoutMs, signal, report) {
508
692
  }
509
693
  };
510
694
  }
511
- function mcpRevocationWiring(deps) {
695
+ function mcpRevocationWiring(deps, runId) {
512
696
  if (deps.mcpRevocations === undefined)
513
697
  return undefined;
514
698
  const ledger = deps.mcpRevocations;
@@ -517,7 +701,7 @@ function mcpRevocationWiring(deps) {
517
701
  onProbeFailure: (e) => deliverEngineNotice(deps.onNotice, {
518
702
  code: "mcp.revocation_probe_failed",
519
703
  message: `the mcpRevocations.isRevoked probe threw — MCP dispatch fails OPEN (no server treated as revoked) until the probe recovers: ${e instanceof Error ? e.message : String(e)}`,
520
- detail: { message: e instanceof Error ? e.message : String(e) },
704
+ detail: { message: e instanceof Error ? e.message : String(e), runId },
521
705
  }),
522
706
  };
523
707
  }
@@ -674,43 +858,69 @@ async function derivedRouteFallsBack(args) {
674
858
  const verdict = await adjudicateDerivedRoute({ brain: args.brain, model: args.derived, getApiKeyAndHeaders: args.getApiKeyAndHeaders });
675
859
  if (verdict === undefined || verdict.ok)
676
860
  return false;
677
- deliverEngineNotice(args.onNotice, fallbackToPrimaryNotice({ seat: args.seat, from: args.derived.id, to: args.primary.id, verdict, ...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}) }));
861
+ deliverEngineNotice(args.onNotice, fallbackToPrimaryNotice({ seat: args.seat, from: args.derived.id, to: args.primary.id, verdict, ...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}), ...(args.runId !== undefined ? { runId: args.runId } : {}) }));
678
862
  return true;
679
863
  }
680
864
  catch {
681
865
  return false;
682
866
  }
683
867
  }
684
- function mintPlacementRootSessionId(internals, sessionId) {
685
- const placementRootSessionId = internals?.placementRoot ?? internals?.rootSessionId ?? sessionId;
686
- if (placementRootSessionId === "") {
868
+ function mintPlacementRootSessionId(placementRootResolved) {
869
+ if (placementRootResolved === "") {
687
870
  const e = new Error("placement root resolved EMPTY (internals.placementRoot / internals.rootSessionId carries an empty string) — an empty fixed point cannot key a placement lookup; fix the spawning lane instead of defaulting around it.");
688
871
  e.code = "config.placement_root_invalid";
689
872
  throw e;
690
873
  }
691
- return placementRootSessionId;
874
+ return placementRootResolved;
875
+ }
876
+ function restorePlacementRoot(internals, seedPlacementRoot) {
877
+ if (internals?.placementRoot !== undefined)
878
+ return internals;
879
+ const seed = placementValueOrAbsent(seedPlacementRoot);
880
+ if (seed === undefined)
881
+ return internals;
882
+ return { ...(internals ?? {}), placementRoot: seed };
883
+ }
884
+ export function placementValueOrAbsent(value) {
885
+ return value || undefined;
886
+ }
887
+ function stampPlacementRootSessionId(placementRootResolved) {
888
+ return placementValueOrAbsent(placementRootResolved);
889
+ }
890
+ function explicitlyDeferredMemoryTrio(mounted, roster, deferNames) {
891
+ return mounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => roster.some((t) => t.name === n) && (deferNames ?? []).includes(n)) : [];
892
+ }
893
+ function memoryGroupRetractionSet(builtinDeferPairNames, engineTrioInPlay) {
894
+ return new Set([...builtinDeferPairNames, ...(engineTrioInPlay ? MEMORY_ENGINE_TOOL_NAMES : [])]);
895
+ }
896
+ function refuseRequireExistingWithoutSession(spec) {
897
+ if (spec.requireExistingSession && !spec.sessionId) {
898
+ const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
899
+ e.code = "resume.session_not_found";
900
+ throw e;
901
+ }
692
902
  }
693
- export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
903
+ export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf, runIdSink) {
904
+ const runId = uuidv7();
905
+ if (runIdSink !== undefined)
906
+ runIdSink.runId = runId;
694
907
  const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
695
908
  spec = doors.spec;
696
- const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
909
+ const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, memoryCapturePolicy, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
697
910
  announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
698
911
  const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
699
912
  let shellGatedBash = false;
700
913
  let shellGatedMonitor = false;
701
- if (spec.requireExistingSession && !spec.sessionId) {
702
- const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
703
- e.code = "resume.session_not_found";
704
- throw e;
705
- }
914
+ refuseRequireExistingWithoutSession(spec);
706
915
  const { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, ...(internals?.sessionPlacement !== undefined ? { placement: internals.sessionPlacement } : {}), ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
707
916
  const sessionId = acquired.sessionId;
708
917
  const hostTaskId = spec.taskId ?? sessionId;
709
918
  const delegation = effectiveDelegationFacts(internals, resume?.seed.isDelegatedChild);
919
+ const placementRootResolved = (internals = restorePlacementRoot(internals, resume?.seed.placementRootSessionId))?.placementRoot ?? internals?.rootSessionId ?? sessionId;
710
920
  let effectiveCompModel = compModel;
711
921
  if (spec.compactionModel === undefined &&
712
922
  compModel !== undefined &&
713
- (await derivedRouteFallsBack({ seat: "compaction-summary", derived: compModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId }))) {
923
+ (await derivedRouteFallsBack({ seat: "compaction-summary", derived: compModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId }))) {
714
924
  effectiveCompModel = undefined;
715
925
  }
716
926
  warnCompactionWindowHazard(deps.tracer, spec, model, effectiveCompModel, hostTaskId);
@@ -798,7 +1008,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
798
1008
  try {
799
1009
  ownedEnv = deps.executionEnvFactory
800
1010
  ? await deps.executionEnvFactory({
801
- sessionId, placementRootSessionId: mintPlacementRootSessionId(internals, sessionId),
1011
+ sessionId, placementRootSessionId: mintPlacementRootSessionId(placementRootResolved),
802
1012
  taskId: spec.taskId,
803
1013
  ...(internals?.isolation ? { isolation: internals.isolation } : {}),
804
1014
  ...(internals?.parentCwd ? { parentCwd: internals.parentCwd } : {}),
@@ -812,6 +1022,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
812
1022
  throw factoryErr;
813
1023
  }
814
1024
  const executionEnv = ownedEnv ?? deps.executionEnv ?? new StubExecutionEnv();
1025
+ const externalContentTargetActive = executionEnv.externalContentTarget === true || resume?.seed.externalContentTarget === true;
815
1026
  if (internals?.requestedCwd !== undefined) {
816
1027
  const canon = async (p) => {
817
1028
  const r = await executionEnv.canonicalPath(p);
@@ -889,79 +1100,165 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
889
1100
  }
890
1101
  const { taskRootFinal, rebaseRestoredPath } = await prepareWorkspaceRestore({ ownedEnv, deps, spec, internals, resume, taskRootInitial, abortController, sessionId });
891
1102
  const rewindNotes = [];
892
- const rewindCaptureRequested = spec.rewindFiles === true;
1103
+ const onTrackFailure = deps.onTrackFailure === undefined ? "refuse" : deps.onTrackFailure;
1104
+ if (onTrackFailure !== "refuse" && onTrackFailure !== "proceed-unprotected") {
1105
+ const e = new Error(`RunnerDeps.onTrackFailure must be "refuse" or "proceed-unprotected"; got ${JSON.stringify(deps.onTrackFailure)} — refusing prepare rather than silently defaulting a first-touch-failure policy.`);
1106
+ e.code = "config.invalid_on_track_failure";
1107
+ throw e;
1108
+ }
1109
+ const fileHistoryStore = resolveWiredFileHistoryStore(deps.fileHistoryStore);
1110
+ if (fileHistoryStore !== undefined) {
1111
+ await adoptForkedFileHistory(fileHistoryStore, session, sessionId, deps.onError);
1112
+ }
1113
+ const legacyRewindFiles = spec.rewindFiles;
1114
+ if (legacyRewindFiles === true) {
1115
+ if (spec.resumeAt !== undefined) {
1116
+ const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is always on when a RunnerDeps.fileHistoryStore is wired.)`);
1117
+ e.code = "rewind.rewind_files_retired";
1118
+ throw e;
1119
+ }
1120
+ try {
1121
+ deps.onError?.(new Error(`TaskSpec.rewindFiles (capture sense) is retired and was ignored: per-edited-file first-touch tracking is always on when RunnerDeps.fileHistoryStore is wired, so a per-turn capture request has nothing left to ask for. Drop the field; the restore sense moved to TaskSpec.restoreFiles.`), { phase: "config", sessionId });
1122
+ }
1123
+ catch {
1124
+ }
1125
+ }
1126
+ if (spec.restoreFiles === true && spec.resumeAt === undefined) {
1127
+ const e = new Error(`rewind-files: restoreFiles: true needs a resumeAt anchor — restoring files means converging them to a specific prior entry's boundary, and this spec names none. Use resumeAt + restoreFiles to move conversation and files together, or rewindFilesTo alone for a code-only restore.`);
1128
+ e.code = "rewind.invalid_spec";
1129
+ throw e;
1130
+ }
893
1131
  if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
894
- const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when rewindFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
1132
+ const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when restoreFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
895
1133
  e.code = "rewind.conflicting_targets";
896
1134
  throw e;
897
1135
  }
898
- let rewindTarget = spec.resumeAt !== undefined ? (rewindCaptureRequested ? spec.resumeAt : undefined) : spec.rewindFilesTo;
1136
+ let rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
899
1137
  const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
900
- if (spec.resumeAt !== undefined && !rewindCaptureRequested) {
1138
+ if (spec.resumeAt !== undefined && spec.restoreFiles !== true) {
901
1139
  rewindNotes.push({
902
1140
  code: "conversation_only",
903
- message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without rewindFiles, so files remain at their current state`,
1141
+ message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without restoreFiles, so files remain at their current state`,
904
1142
  });
905
1143
  }
906
- if (!deps.fileSnapshotStore) {
1144
+ const fileHistoryEnabled = fileHistoryStore !== undefined && handsEnabled;
1145
+ let historyRoot = taskRootFinal;
1146
+ if (fileHistoryEnabled) {
1147
+ const canonRoot = await executionEnv.canonicalPath(taskRootFinal);
1148
+ if (canonRoot.ok)
1149
+ historyRoot = canonRoot.value;
1150
+ }
1151
+ if (fileHistoryStore === undefined) {
907
1152
  if (rewindTarget !== undefined) {
908
- const e = new Error(`rewind-files: restoring the working tree to entry "${rewindTarget}" requires a snapshot backend, but this deployment wired no RunnerDeps.fileSnapshotStore — no snapshot was ever captured, so the files were NOT rewound`);
1153
+ const e = new Error(`rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
909
1154
  e.code = "rewind.store_unconfigured";
910
1155
  throw e;
911
1156
  }
912
- if (rewindCaptureRequested) {
913
- rewindNotes.push({
914
- code: "snapshot_store_unconfigured",
915
- message: "rewindFiles was requested but no RunnerDeps.fileSnapshotStore is wired — no working-tree snapshot was captured for this turn, so it cannot be rewound to later",
916
- });
917
- }
918
1157
  }
919
1158
  else if (!handsEnabled) {
920
- if (rewindTarget !== undefined || rewindCaptureRequested) {
1159
+ if (rewindTarget !== undefined) {
921
1160
  rewindNotes.push({
922
1161
  code: "files_env_unsupported",
923
- message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no working-tree snapshot was captured or restored",
1162
+ message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no file history was recorded or restored",
924
1163
  });
925
1164
  }
926
1165
  }
927
- if (rewindTarget !== undefined && deps.fileSnapshotStore && handsEnabled) {
1166
+ const legacyEpochTail = (entryId, form = "at-or-above") => legacyRewindEpochTail(deps, sessionId, entryId, form);
1167
+ let restoredFilePaths = [];
1168
+ if (rewindTarget !== undefined && fileHistoryStore !== undefined && handsEnabled) {
1169
+ const historyStore = fileHistoryStore;
928
1170
  if (rewindBefore) {
929
1171
  let anchor;
930
- const walked = new Set();
931
- let cursor = resumeAtBeforeParentId;
932
- while (cursor !== null && !walked.has(cursor)) {
933
- walked.add(cursor);
934
- if (await deps.fileSnapshotStore.has(sessionId, cursor)) {
935
- anchor = cursor;
936
- break;
1172
+ if (await historyStore.canRestore(sessionId, rewindTarget)) {
1173
+ anchor = rewindTarget;
1174
+ }
1175
+ else {
1176
+ const walked = new Set();
1177
+ let cursor = resumeAtBeforeParentId;
1178
+ while (cursor !== null && !walked.has(cursor)) {
1179
+ walked.add(cursor);
1180
+ if (await historyStore.canRestore(sessionId, cursor)) {
1181
+ anchor = cursor;
1182
+ break;
1183
+ }
1184
+ cursor = (await session.getEntry(cursor))?.parentId ?? null;
937
1185
  }
938
- cursor = (await session.getEntry(cursor))?.parentId ?? null;
939
1186
  }
940
1187
  if (anchor === undefined) {
941
- const e = new Error(`rewind-files: no file snapshot exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context (earlier turns ran without rewindFiles, or their snapshots were reaped)`);
1188
+ const e = new Error(`rewind-files: no file-history boundary exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context. ${await legacyEpochTail(rewindTarget)}`);
942
1189
  e.code = "rewind_snapshot.unresolvable";
943
1190
  throw e;
944
1191
  }
945
1192
  rewindTarget = anchor;
946
1193
  }
947
- const restoreRoot = taskRootFinal;
1194
+ const restoreRoot = historyRoot;
948
1195
  const restoreSignal = spec.signal ? AbortSignal.any([abortController.signal, spec.signal]) : abortController.signal;
949
- const restored = await deps.fileSnapshotStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
950
- if (!restored.ok) {
951
- if (restored.error.code === "not_found") {
1196
+ const restored = await historyStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
1197
+ const ledgerLine = (r) => `applied ${r.filesChanged.length}, identical ${r.identicalSkipped.length}, refused ${r.refused.length}${r.refused.length > 0 ? ` (${r.refused.map((f) => `${f.path}: ${f.reason}`).join("; ")})` : ""}, failed ${r.failed.length}${r.failed.length > 0 ? ` (${r.failed.map((f) => `${f.path}: ${f.reason}`).join("; ")})` : ""}`;
1198
+ if (restored.outcome === "failed") {
1199
+ if (restored.error?.code === "not_found") {
952
1200
  const e = new Error(rewindBefore
953
- ? `rewind-files: the resolved "before" snapshot anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
954
- : `rewind-files: no file snapshot exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound (only a COMPLETED turn that ran with rewindFiles is snapshotted, and snapshots are keyed by that turn's END leaf; earlier turns that ran without rewindFiles, mid-turn entries, and reaped snapshots have none)`);
1201
+ ? `rewind-files: the resolved "before" history anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
1202
+ : `rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${await legacyEpochTail(rewindTarget, "keyed")}`);
955
1203
  e.code = "rewind_snapshot.unresolvable";
956
1204
  throw e;
957
1205
  }
1206
+ const e = new Error(`rewind-files restore failed (${restored.error?.code ?? "restore_failed"}): ${restored.error?.message ?? "unknown"} — ledger: ${ledgerLine(restored)}`);
1207
+ e.code = "rewind.restore_failed";
1208
+ throw e;
1209
+ }
1210
+ if (restored.outcome === "partial") {
1211
+ if (spec.acceptPartialRestore === true) {
1212
+ rewindNotes.push({
1213
+ code: "restore_partial",
1214
+ message: `file restore to entry "${rewindTarget}" was PARTIAL and was tolerated by acceptPartialRestore — ledger: ${ledgerLine(restored)}. Re-running the same restore converges (per-file idempotent).`,
1215
+ });
1216
+ }
958
1217
  else {
959
- const e = new Error(`rewind-files restore failed (${restored.error.code}): ${restored.error.message}`);
1218
+ const e = new Error(`rewind-files: restore to entry "${rewindTarget}" was PARTIAL — ledger: ${ledgerLine(restored)}. The task is refused rather than run on a mixed-state tree (set acceptPartialRestore: true to tolerate and disclose instead); re-running the same restore converges (per-file idempotent).`);
960
1219
  e.code = "rewind.restore_failed";
961
1220
  throw e;
962
1221
  }
963
1222
  }
1223
+ restoredFilePaths = restoredAbsPathsOf(restored, restoreRoot);
964
1224
  }
1225
+ const trackFileEdit = fileHistoryEnabled
1226
+ ? async (req) => {
1227
+ const r = await fileHistoryStore.trackEdit(sessionId, req.key, executionEnv, historyRoot, req.signal);
1228
+ if (r.ok) {
1229
+ if (!r.minted)
1230
+ return { ok: true };
1231
+ return {
1232
+ ok: true,
1233
+ annul: async (proof) => {
1234
+ const a = await fileHistoryStore.annulTrack(sessionId, req.key, historyRoot, proof === "verify" ? { env: executionEnv, ...(req.signal !== undefined ? { signal: req.signal } : {}) } : undefined);
1235
+ if (a.ok)
1236
+ return;
1237
+ try {
1238
+ deps.onError?.(new Error(`file-history could not retract the first-touch record for "${req.key}" after its edit failed to land (${a.error.code}: ${a.error.message}) — the path stays tracked, so a rewind to a boundary below it may converge bytes this engine never wrote`), { phase: "rewind", sessionId });
1239
+ }
1240
+ catch {
1241
+ }
1242
+ },
1243
+ };
1244
+ }
1245
+ if (onTrackFailure === "proceed-unprotected") {
1246
+ try {
1247
+ deps.onError?.(new Error(`file-history first-touch backup for "${req.key}" failed (${r.error.code}: ${r.error.message}) — proceeding UNPROTECTED per RunnerDeps.onTrackFailure: the pristine promise for this path is void, and only in this process's memory (no durable trace exists, so after a process restart the next edit can still mint already-modified bytes as pristine v1)`), { phase: "rewind", sessionId });
1248
+ }
1249
+ catch {
1250
+ }
1251
+ return { ok: true };
1252
+ }
1253
+ return {
1254
+ ok: false,
1255
+ refusal: `Error (${req.tool}): the edit was refused because its first-touch file-history backup could not be persisted (${r.error.code}: ${r.error.message}). Proceeding would leave "${req.path}" with no durable pristine record — after the history store recovers, a retried edit would mint already-modified bytes as the pristine baseline. Retry when the file-history store is healthy.`,
1256
+ };
1257
+ }
1258
+ : undefined;
1259
+ const fileHistoryBoundary = fileHistoryEnabled
1260
+ ? createFileHistoryBoundarySeat({ store: fileHistoryStore, sessionId, env: executionEnv, root: historyRoot, onError: deps.onError })
1261
+ : undefined;
965
1262
  const harnessRef = {};
966
1263
  const skillScope = new ActiveSkillScope();
967
1264
  const forwardSink = internals?.onForwardEvent;
@@ -1103,7 +1400,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1103
1400
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1104
1401
  ...(durableMandate ? { durableMandate: true } : {}),
1105
1402
  ...(contentMandate ? { contentMandate: true } : {}),
1106
- ...(autoModeDecider !== undefined ? { autoMode: { decider: autoModeDecider } } : {}),
1403
+ ...(autoModeDecider !== undefined
1404
+ ? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1405
+ : {}),
1107
1406
  },
1108
1407
  ]
1109
1408
  : [];
@@ -1117,7 +1416,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1117
1416
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1118
1417
  ...(durableMandate ? { durableMandate: true } : {}),
1119
1418
  ...(contentMandate ? { contentMandate: true } : {}),
1120
- ...(autoModeDecider !== undefined ? { autoMode: { decider: autoModeDecider } } : {}),
1419
+ ...(autoModeDecider !== undefined
1420
+ ? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1421
+ : {}),
1121
1422
  },
1122
1423
  ]
1123
1424
  : []),
@@ -1141,6 +1442,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1141
1442
  spec.handsReadOnly !== true &&
1142
1443
  !(toolFaceSnapshot.exclude?.includes("Bash") ?? false);
1143
1444
  let autoModeDecider;
1445
+ let autoModeArming;
1144
1446
  const delegationEntryCapsResolved = resolveDelegationEntryCaps(deps.delegationEntryCaps);
1145
1447
  const enrichSpecToolCtx = (ctx) => ({
1146
1448
  ...ctx,
@@ -1166,6 +1468,19 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1166
1468
  ...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
1167
1469
  ...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
1168
1470
  ...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
1471
+ get memoryCaptureOptedOut() {
1472
+ return memoryEngineSession?.captureOptOut?.optedOut() === true;
1473
+ },
1474
+ get memoryCaptureIndeterminate() {
1475
+ return memoryEngineSession?.captureOptOut?.indeterminate() === true;
1476
+ },
1477
+ get memoryCaptureControlDir() {
1478
+ return memoryEngineSession?.engine.controlPlaneDir;
1479
+ },
1480
+ get memoryCaptureAncestors() {
1481
+ const ctl = memoryEngineSession?.engine.controlPlaneDir;
1482
+ return [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }];
1483
+ },
1169
1484
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
1170
1485
  parentCwd: taskRootFinal,
1171
1486
  reminderMark,
@@ -1244,6 +1559,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1244
1559
  }
1245
1560
  }
1246
1561
  let runtimeCaps;
1562
+ let runtimeCapsFaulted = false;
1247
1563
  if (deps.runtimeCapsResolver) {
1248
1564
  try {
1249
1565
  runtimeCaps = (await deps.runtimeCapsResolver(spec.principal)) ?? undefined;
@@ -1251,6 +1567,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1251
1567
  catch (err) {
1252
1568
  deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
1253
1569
  runtimeCaps = { allowWorkflows: false, allowFork: false };
1570
+ runtimeCapsFaulted = true;
1254
1571
  }
1255
1572
  }
1256
1573
  let complianceDenies = new Set();
@@ -1297,7 +1614,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1297
1614
  catch {
1298
1615
  classifierModel = model;
1299
1616
  }
1300
- if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId })) {
1617
+ if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId })) {
1301
1618
  classifierModel = model;
1302
1619
  }
1303
1620
  const classifierSystemPrompt = buildAutoModePrompt(am);
@@ -1322,6 +1639,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1322
1639
  .join("");
1323
1640
  },
1324
1641
  });
1642
+ if (am.persistArming === true) {
1643
+ autoModeArming = autoModeArmingRecipeOf(am, {
1644
+ promptDigest: `apv1:${createHash("sha256").update(classifierSystemPrompt).digest("hex")}`,
1645
+ });
1646
+ if (autoModeArming === undefined) {
1647
+ deps.onError?.(new Error("RunnerDeps.autoMode.persistArming is set but the auto-mode face did not canonicalize into an arming recipe " +
1648
+ "(a rule list carrying a non-string, or a non-finite/out-of-range timeoutMs / failureThreshold / window bound) — " +
1649
+ "NO arming recipe is recorded on this run's parked constraint chain, and a cross-process redemption keeps the " +
1650
+ "conservative behavior (the ancestor classifier answers unavailable and the inherited ask flows to a human)"), { phase: "config", sessionId });
1651
+ }
1652
+ }
1325
1653
  }
1326
1654
  const deploymentWorkflowReady = runnerSelf !== undefined && isSelfOrchestrationActive(spec, deps);
1327
1655
  const selfOrchestrationActive = deploymentWorkflowReady && runtimeCaps?.allowWorkflows !== false;
@@ -1369,6 +1697,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1369
1697
  principal: spec.principal,
1370
1698
  oneShot: spec.oneShot,
1371
1699
  ...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
1700
+ parentMemoryCaptureState: () => {
1701
+ const ctl = memoryEngineSession?.engine.controlPlaneDir;
1702
+ return {
1703
+ optedOut: memoryEngineSession?.captureOptOut?.optedOut() === true,
1704
+ indeterminate: memoryEngineSession?.captureOptOut?.indeterminate() === true,
1705
+ ...(ctl !== undefined ? { controlDir: ctl } : {}),
1706
+ ancestors: [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }],
1707
+ };
1708
+ },
1372
1709
  autoModeReview: () => (autoModeDecider !== undefined ? { decider: autoModeDecider } : undefined),
1373
1710
  workflowDepth: internals?.workflowDepth,
1374
1711
  parentCwd: taskRootFinal,
@@ -1447,7 +1784,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1447
1784
  tools.push(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined));
1448
1785
  }
1449
1786
  mcp = lockedPreflight.mcp?.length
1450
- ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps))
1787
+ ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId))
1451
1788
  : { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
1452
1789
  for (const w of mcp.warnings)
1453
1790
  deps.onError?.(w, { phase: "mcp", sessionId });
@@ -1603,9 +1940,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1603
1940
  foldProtocolAxes(a2a.toolAxes, "A2A");
1604
1941
  const callIssuedAtRef = {};
1605
1942
  const memoryWriteGateRef = {};
1606
- const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts };
1943
+ const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
1607
1944
  const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
1608
- const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher } = handsReadFace;
1945
+ const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
1609
1946
  resolvedReadFace = handsReadFace.resolvedReadFace;
1610
1947
  readDenyAdditionsNormalized = handsReadFace.readDenyAdditionsNormalized;
1611
1948
  handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
@@ -1689,6 +2026,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1689
2026
  }
1690
2027
  if (backgroundTaskToolsActive) {
1691
2028
  toolEffects.set("Monitor", "write");
2029
+ envHandToolNames.add("Monitor");
1692
2030
  tools.push(firstPartyOffload(createMonitorTool(executionEnv, {
1693
2031
  registry: defaultTaskRegistry,
1694
2032
  owner: hostTaskId,
@@ -1724,7 +2062,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1724
2062
  };
1725
2063
  if (workspaceStateSettle !== undefined)
1726
2064
  workspaceStateSettle.restoredWorktreeDir = worktreeSessionRef.current?.worktreeDir;
1727
- tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => firstPartyOffload(t)));
2065
+ tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))));
1728
2066
  }
1729
2067
  if (offloadStore)
1730
2068
  tools.push(createReadToolResultTool(offloadStore));
@@ -1809,7 +2147,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1809
2147
  ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
1810
2148
  ...(sessionId !== undefined ? { sessionId } : {}),
1811
2149
  requestStopAfterTurn,
1812
- }));
2150
+ }).map((t) => (envHandToolNames.add(t.name), t)));
1813
2151
  }
1814
2152
  if (internals?.insideFork !== true &&
1815
2153
  runnerSelf &&
@@ -1823,6 +2161,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1823
2161
  if (lspManager) {
1824
2162
  const lspRoot = taskRootFinal;
1825
2163
  tools.push(createLspTool(lspManager, { isPathIgnored: gitCheckIgnoreFilter(executionEnv, lspRoot), env: executionEnv }));
2164
+ envHandToolNames.add("LSP");
1826
2165
  }
1827
2166
  const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && handsEnabled && spec.handsReadOnly !== true
1828
2167
  ? lspManager.diagnostics
@@ -1848,7 +2187,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1848
2187
  const memoryPairExcludedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => (toolFaceSnapshot.exclude ?? []).includes(n));
1849
2188
  const memoryPairOccupiedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => memoryPairNameDomain.has(n));
1850
2189
  const memorySearchToolsPlanned = memoryPairExcludedName === undefined && memoryPairOccupiedName === undefined;
1851
- const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, memoryTools, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
2190
+ const captureLineage = await (async () => {
2191
+ try {
2192
+ const ff = (await session.getMetadata()).forkedFrom;
2193
+ return { ...(ff !== undefined && ff !== sessionId ? { origin: ff } : {}), fault: false };
2194
+ }
2195
+ catch {
2196
+ return { fault: true };
2197
+ }
2198
+ })();
2199
+ const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, memoryTools, recallDisciplineSegment: memoryRecallSegment, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
1852
2200
  spec,
1853
2201
  deps,
1854
2202
  sessionId,
@@ -1859,6 +2207,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1859
2207
  !(toolFaceSnapshot.exclude?.includes("Write") ?? false) &&
1860
2208
  !(handsEnabled && isRemoteExecutionEnv(executionEnv) && spec.memoryPersistenceCapable !== true),
1861
2209
  memoryProvenance,
2210
+ memoryCapturePolicy,
2211
+ memoryCapturePolicyDeclared: deps.memoryCapturePolicy !== undefined,
2212
+ captureEntitlement: {
2213
+ value: typeof runtimeCaps?.allowMemoryOptOut === "boolean" ? runtimeCaps.allowMemoryOptOut : undefined,
2214
+ faulted: runtimeCapsFaulted || (runtimeCaps?.allowMemoryOptOut !== undefined && typeof runtimeCaps.allowMemoryOptOut !== "boolean"),
2215
+ },
2216
+ captureCarrierUnsupported: isRemoteExecutionEnv(executionEnv) && deps.memoryCaptureRecordStore === undefined,
2217
+ ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
2218
+ captureFloor: internals?.memoryCaptureFloor === true,
2219
+ ...(internals?.memoryCaptureFloorIndeterminate === true ? { captureFloorIndeterminate: true } : {}),
2220
+ ...(captureLineage.origin !== undefined ? { captureForkOrigin: captureLineage.origin } : {}),
2221
+ ...(captureLineage.fault ? { captureLineageFault: true } : {}),
2222
+ ...(internals?.memoryCaptureQueryDir !== undefined ? { parentCaptureQueryDir: internals.memoryCaptureQueryDir } : {}),
2223
+ ...(internals?.memoryCaptureAncestors !== undefined ? { captureAncestors: internals.memoryCaptureAncestors } : {}),
1862
2224
  memoryPersistenceDeclared: spec.memoryPersistenceCapable,
1863
2225
  rosterCanPersist: spec.memoryPersistenceCapable ??
1864
2226
  tools.some((t) => {
@@ -1917,6 +2279,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1917
2279
  }
1918
2280
  }
1919
2281
  let instructionSources;
2282
+ let projectInstructionContent;
1920
2283
  if (deps.loadProjectMemory) {
1921
2284
  let projectMemoryPhase = spec.sessionId ? "resume" : "fresh";
1922
2285
  if (spec.sessionId) {
@@ -1968,6 +2331,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1968
2331
  }
1969
2332
  }
1970
2333
  if (projectMem != null && projectMem.trim()) {
2334
+ projectInstructionContent = projectMem;
1971
2335
  const projectBlock = `${PROJECT_CONTEXT_FRAMING}\n\n${composeMemoryBlock(projectMem, "project")}`;
1972
2336
  memoryBlock = memoryBlock ? `${memoryBlock}\n\n${projectBlock}` : projectBlock;
1973
2337
  }
@@ -2032,7 +2396,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2032
2396
  (memoryPairExcludedName !== undefined
2033
2397
  ? `excludeTools removes "${memoryPairExcludedName}"`
2034
2398
  : `this task already declares a tool named "${memoryPairOccupiedName}" (the built-in yields to it)`) +
2035
- " — the pair mounts together or not at all."), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
2399
+ " — these tools mount together or not at all."), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
2036
2400
  }
2037
2401
  const skillsListing = skillSpecs.length > 0
2038
2402
  ? {
@@ -2098,6 +2462,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2098
2462
  envFacts.sandboxEgress = spec.envFacts.egress;
2099
2463
  if (spec.envFacts?.scratchpadDir)
2100
2464
  envFacts.scratchpadDir = spec.envFacts.scratchpadDir;
2465
+ if (externalContentTargetActive)
2466
+ envFacts.externalContentTarget = true;
2101
2467
  if (resume !== undefined && spec.envFacts?.resumeFacts) {
2102
2468
  const rf = spec.envFacts.resumeFacts;
2103
2469
  const copy = {};
@@ -2229,7 +2595,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2229
2595
  return deferredSet;
2230
2596
  };
2231
2597
  const sharedMemoryPair = sharedMemoryPairMounted ? SHARED_MEMORY_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n)) : [];
2232
- const memoryEnginePair = memoryEnginePairMounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n)) : [];
2598
+ const memoryEnginePair = explicitlyDeferredMemoryTrio(memoryEnginePairMounted, tools, toolFaceSnapshot.defer);
2233
2599
  const builtinDeferPairNames = [...sharedMemoryPair, ...memoryEnginePair];
2234
2600
  let deferred;
2235
2601
  if (builtinDeferPairNames.length > 0) {
@@ -2239,8 +2605,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2239
2605
  const soleCause = d0.size === 0 && d1.size > 0;
2240
2606
  const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
2241
2607
  if (soleCause && supportNameTaken) {
2608
+ const retractNames = memoryGroupRetractionSet(builtinDeferPairNames, memoryEnginePair.length > 0);
2242
2609
  for (let i = tools.length - 1; i >= 0; i--) {
2243
- if (builtinDeferPairNames.some((n) => n === tools[i].name))
2610
+ if (retractNames.has(tools[i].name))
2244
2611
  tools.splice(i, 1);
2245
2612
  }
2246
2613
  if (sharedMemoryPair.length > 0) {
@@ -2250,8 +2617,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2250
2617
  }
2251
2618
  if (memoryEnginePair.length > 0) {
2252
2619
  memoryEnginePairMounted = false;
2253
- if (memoryBlock !== undefined) {
2254
- const segment = memoryRecallDisciplineSegment(memoryProvenance);
2620
+ if (memoryBlock !== undefined && memoryRecallSegment !== undefined) {
2621
+ const segment = memoryRecallSegment;
2255
2622
  if (memoryBlock === segment)
2256
2623
  memoryBlock = undefined;
2257
2624
  else if (memoryBlock.includes(`\n\n${segment}`))
@@ -2260,7 +2627,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2260
2627
  memoryBlock = memoryBlock.slice(segment.length + 2);
2261
2628
  }
2262
2629
  deps.onError?.(new Error(`Memory tools ${MEMORY_ENGINE_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
2263
- `the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — the pair mounts together or not at all.`), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
2630
+ `the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — these tools mount together or not at all.`), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
2264
2631
  }
2265
2632
  deferred = d0;
2266
2633
  }
@@ -2278,13 +2645,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2278
2645
  let staticMarkWaiverAnnounced = false;
2279
2646
  const effectiveSafety = narrowContentSafety(delegationProvenanceChannel?.contentSafety, memoryEngineSession?.contentSafety);
2280
2647
  const trustedTools = effectiveSafety.trustedTools;
2281
- const execIsExternalContent = effectiveSafety.execIsExternalContent;
2648
+ const execIsExternalContent = effectiveSafety.execIsExternalContent || externalContentTargetActive;
2282
2649
  provenanceForChildrenRef.current = { trustedTools: [...trustedTools], execIsExternalContent };
2283
2650
  announceDeclaredMcpContentClasses({
2284
2651
  entries: lockedPreflight.mcp,
2285
2652
  statuses: mcp?.statuses,
2286
2653
  execIsExternalContent,
2287
2654
  sessionId,
2655
+ runId,
2288
2656
  onNotice: deps.onNotice,
2289
2657
  });
2290
2658
  if (memoryEngineSession?.settlement !== undefined) {
@@ -2300,12 +2668,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2300
2668
  };
2301
2669
  const callerToolNames = new Set((spec.tools ?? []).map((t) => t.name));
2302
2670
  const contentOriginWrapped = new WeakSet();
2303
- const classOf = (t) => classifyToolContentOrigin({
2304
- declared: t.contentOrigin,
2305
- isProtocolTool: protocolOf(t.name) !== undefined,
2306
- isCallerTool: callerToolNames.has(t.name),
2307
- trusted: trustedTools.has(t.name),
2308
- });
2671
+ const classOf = (t) => externalContentTargetActive && envHandToolNames.has(t.name)
2672
+ ? "external"
2673
+ : classifyToolContentOrigin({
2674
+ declared: t.contentOrigin,
2675
+ isProtocolTool: protocolOf(t.name) !== undefined,
2676
+ isCallerTool: callerToolNames.has(t.name),
2677
+ trusted: trustedTools.has(t.name),
2678
+ });
2309
2679
  const delegationByName = new Map();
2310
2680
  for (const t of spec.tools ?? []) {
2311
2681
  const withFaces = t;
@@ -2446,6 +2816,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2446
2816
  reason: waivedReason,
2447
2817
  ...(typeof requested === "string" ? { subagentType: inlineUntrusted(requested, 120) } : {}),
2448
2818
  sessionId,
2819
+ runId,
2449
2820
  },
2450
2821
  });
2451
2822
  }
@@ -2906,7 +3277,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2906
3277
  emitTrace(deps.tracer, () => ({
2907
3278
  kind: "loop.recovery",
2908
3279
  version: 1,
2909
- taskId: spec.taskId ?? sessionId,
3280
+ taskId: hostTaskId,
2910
3281
  reason: step.reason,
2911
3282
  ts: Date.now(),
2912
3283
  }));
@@ -3137,7 +3508,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3137
3508
  emitTrace(deps.tracer, () => ({
3138
3509
  kind: "permission.sandbox_admitted",
3139
3510
  version: 1,
3140
- taskId: spec.taskId ?? sessionId,
3511
+ taskId: hostTaskId,
3141
3512
  toolCallId: info.toolCallId,
3142
3513
  toolName: info.toolName,
3143
3514
  askClasses: info.askClasses.map((a) => a.cls),
@@ -3223,7 +3594,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3223
3594
  emitTrace(deps.tracer, () => ({
3224
3595
  kind: "permission.rule_store_unreadable",
3225
3596
  version: 1,
3226
- taskId: spec.taskId ?? sessionId,
3597
+ taskId: hostTaskId,
3227
3598
  message: err instanceof Error ? err.message : String(err),
3228
3599
  ts: Date.now(),
3229
3600
  }));
@@ -3268,26 +3639,36 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3268
3639
  return {};
3269
3640
  if ((spec.principal === undefined || spec.principal === "") && deps.localOwnerRules !== true)
3270
3641
  return {};
3271
- if (ask?.requiresRealApproval === true ||
3272
- ask?.persistedRuleShadowed !== undefined ||
3273
- ask?.decisionReason === "hook" ||
3274
- ask?.matchedAskRule !== undefined ||
3275
- ask?.inheritedUnresolved === true ||
3276
- ask?.ancestorResolved === true) {
3277
- return {};
3278
- }
3642
+ const closedDoor = (() => {
3643
+ if (ask?.requiresRealApproval === true)
3644
+ return "mandated";
3645
+ if (ask?.persistedRuleShadowed !== undefined)
3646
+ return "shadowed";
3647
+ if (ask?.decisionReason === "hook")
3648
+ return "mandated";
3649
+ if (ask?.matchedAskRule !== undefined)
3650
+ return "shadowed";
3651
+ if (ask?.inheritedUnresolved === true)
3652
+ return "mandated";
3653
+ if (ask?.ancestorResolved === true)
3654
+ return "mandated";
3655
+ return undefined;
3656
+ })();
3657
+ if (closedDoor !== undefined)
3658
+ return { ruleOffersAbsence: closedDoor };
3279
3659
  if (persistedRuleMandateOf({
3280
3660
  egress: egressTools.has(toolName),
3281
3661
  irreversibility: irreversibilityTier.get(toolName),
3282
3662
  shellGated: shellGatedBash,
3663
+ probeMandated: ask?.probeMandated === true,
3283
3664
  }) !== undefined) {
3284
- return {};
3665
+ return { ruleOffersAbsence: "mandated" };
3285
3666
  }
3286
3667
  const command = args?.command;
3287
3668
  if (typeof command !== "string")
3288
- return {};
3669
+ return { ruleOffersAbsence: "lane_cannot_speak" };
3289
3670
  const offers = suggestRulesForCommand(command, ask?.segmentCoverage !== undefined ? { coverage: ask.segmentCoverage } : undefined);
3290
- return offers.length > 0 ? { ruleOffers: offers } : {};
3671
+ return offers.length > 0 ? { ruleOffers: offers } : { ruleOffersAbsence: "lane_cannot_speak" };
3291
3672
  };
3292
3673
  const frozenClassifierExcluded = (d) => d.decisionReason === "hook" || d.matchedAskRule !== undefined;
3293
3674
  const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider) => {
@@ -3724,7 +4105,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3724
4105
  const parkLaneArmed = wiringManifest.parkLane.effective === true;
3725
4106
  const hookIdentity = mintHookInvocationIdentity({
3726
4107
  sessionId,
3727
- taskId: spec.taskId ?? sessionId,
4108
+ taskId: hostTaskId,
3728
4109
  legKind: wiringManifest.leg.kind,
3729
4110
  isDelegatedChild: delegation.isDelegatedChild,
3730
4111
  ...(internals?.insideFork === true ? { insideFork: true } : {}),
@@ -3733,6 +4114,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3733
4114
  });
3734
4115
  const hookContextConsumerWired = hooks?.preToolUse !== undefined || hooks?.postToolUse !== undefined || hooks?.postToolUseFailure !== undefined;
3735
4116
  const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
4117
+ if (fileHistoryBoundary !== undefined) {
4118
+ harness.on("tool_call", async () => {
4119
+ await fileHistoryBoundary.settle();
4120
+ return undefined;
4121
+ });
4122
+ }
3736
4123
  if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
3737
4124
  const adjudicate = effectivePolicy
3738
4125
  ? (req) => raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}) }, abortController.signal)), abortController.signal, () => ({
@@ -3860,7 +4247,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3860
4247
  const requiresParentConstraint = (inheritedParentConstraints?.length ?? 0) > 0 || seedInheritedGate?.requiresParentConstraint === true;
3861
4248
  const parentConstraintCount = (inheritedParentConstraints?.length ?? 0) > 0 ? inheritedParentConstraints.length : seedInheritedGate?.parentConstraintCount;
3862
4249
  const constraintChain = (inheritedParentConstraints?.length ?? 0) > 0
3863
- ? inheritedParentConstraints.map((pc) => constraintChainEntryOf(pc.policy, { ...(pc.autoMode !== undefined ? { autoModeArmed: true } : {}), ...(pc.durableMandate === true ? { durableMandate: true } : {}), ...(pc.contentMandate === true ? { contentMandate: true } : {}) }))
4250
+ ? inheritedParentConstraints.map((pc) => constraintChainEntryOfLayer(pc))
3864
4251
  : seedInheritedGate?.constraintChain;
3865
4252
  const constraintDigest = (inheritedParentConstraints?.length ?? 0) > 0
3866
4253
  ? constraintChainDigest(constraintChain)
@@ -3897,6 +4284,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3897
4284
  gitAnnouncement: gitStatusRef.announced !== undefined ? { ...gitStatusRef.announced } : undefined,
3898
4285
  delegationProvenance: internals?.delegationProvenance !== undefined ? { ...internals.delegationProvenance.ref.current } : undefined,
3899
4286
  isDelegatedChild: hookIdentity.isDelegatedChild ? true : undefined,
4287
+ placementRootSessionId: stampPlacementRootSessionId(placementRootResolved),
4288
+ externalContentTarget: externalContentTargetActive ? true : undefined,
3900
4289
  });
3901
4290
  const commitSuspendSaga = async (token, cp, remoteEnv, remoteHandle) => {
3902
4291
  if (!checkpointStore)
@@ -4256,7 +4645,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4256
4645
  }
4257
4646
  };
4258
4647
  const suspendAsk = parkLaneArmed && checkpointStore !== undefined
4259
- ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule) => {
4648
+ ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated) => {
4260
4649
  const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
4261
4650
  if (syncFirstEligible &&
4262
4651
  runtimeCaps?.forceDurableGate !== true &&
@@ -4427,6 +4816,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4427
4816
  ...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
4428
4817
  ...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}),
4429
4818
  ...(matchedAskRule !== undefined ? { matchedAskRule } : {}),
4819
+ ...(probeMandated === true ? { probeMandated: true } : {}),
4430
4820
  ...(inheritedUnavailableAsks.has(req.toolCallId) ? { inheritedUnresolved: true } : {}),
4431
4821
  ...(segmentCoverage !== undefined ? { segmentCoverage } : {}),
4432
4822
  }),
@@ -4526,7 +4916,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4526
4916
  let humanBareRejection = false;
4527
4917
  try {
4528
4918
  result = await runToolGate({
4529
- onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: spec.taskId ?? sessionId, site: f.site, message: f.error.message, ts: Date.now() })),
4919
+ onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: hostTaskId, site: f.site, message: f.error.message, ts: Date.now() })),
4530
4920
  event: e,
4531
4921
  identity: hookIdentity,
4532
4922
  reminderMark,
@@ -4555,7 +4945,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4555
4945
  onResolved: (info) => emitTrace(deps.tracer, () => ({
4556
4946
  kind: "permission.persisted_rule_allowed",
4557
4947
  version: 2,
4558
- taskId: spec.taskId ?? sessionId,
4948
+ taskId: hostTaskId,
4559
4949
  toolName: info.toolName,
4560
4950
  toolCallId: info.toolCallId,
4561
4951
  rules: [...info.rules],
@@ -4572,7 +4962,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4572
4962
  onUnavailable: (info) => emitTrace(deps.tracer, () => ({
4573
4963
  kind: "permission.org_snapshot_unavailable",
4574
4964
  version: 1,
4575
- taskId: spec.taskId ?? sessionId,
4965
+ taskId: hostTaskId,
4576
4966
  toolName: info.toolName,
4577
4967
  toolCallId: info.toolCallId,
4578
4968
  message: info.message,
@@ -4694,7 +5084,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4694
5084
  }
4695
5085
  const editAt = editBudget(model);
4696
5086
  const guardAt = guardBudget(model);
4697
- const microCompact = buildMicroCompactState(deps, model, sessionId, offloadStore, microCompactKnob);
5087
+ const microCompact = buildMicroCompactState(deps, model, sessionId, runId, offloadStore, microCompactKnob);
4698
5088
  const charsPerToken = model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
4699
5089
  harness.on("context", async ({ messages, recheck, signal }) => {
4700
5090
  batchHaltRef.current = undefined;
@@ -4709,7 +5099,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4709
5099
  onCapped: (info) => pendingProjectionObservations.push(() => emitTrace(deps.tracer, () => ({
4710
5100
  kind: "tool_result.capped",
4711
5101
  version: 1,
4712
- taskId: spec.taskId ?? sessionId,
5102
+ taskId: hostTaskId,
4713
5103
  ...(info.tool !== undefined ? { tool: info.tool } : {}),
4714
5104
  sizeChars: info.sizeChars,
4715
5105
  storeFallback: info.storeFallback,
@@ -4746,11 +5136,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4746
5136
  : {}),
4747
5137
  });
4748
5138
  if (ccPass !== undefined) {
4749
- recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: spec.taskId ?? sessionId, trigger: "frontier" });
5139
+ recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: hostTaskId, trigger: "frontier" });
4750
5140
  }
4751
5141
  const chain = await runGuardChain({
4752
5142
  edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken, offloadStore,
4753
- sessionId, taskId: spec.taskId ?? sessionId, deps, trimPressureRef, recheck, signal,
5143
+ sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal,
4754
5144
  pendingProjectionObservations,
4755
5145
  });
4756
5146
  if ("earlyReturn" in chain) {
@@ -4985,7 +5375,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4985
5375
  const effectiveReadFaceObserved = carrierReadFace();
4986
5376
  const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
4987
5377
  const preparedHolder = {};
4988
- const buildPrepared = () => ({ harness, session, sessionId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), 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, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(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, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
5378
+ const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), 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, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
4989
5379
  const prepared = buildPrepared();
4990
5380
  preparedHolder.current = prepared;
4991
5381
  return prepared;