@sema-agent/core 7.3.0 → 7.4.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 (72) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/agents/peer-admission.d.ts +18 -3
  3. package/dist/agents/peer-admission.js +79 -4
  4. package/dist/agents/peer-held-queue.d.ts +101 -0
  5. package/dist/agents/peer-held-queue.js +229 -0
  6. package/dist/agents/peer-idle.d.ts +109 -0
  7. package/dist/agents/peer-idle.js +240 -0
  8. package/dist/agents/peer-notice-route.d.ts +33 -0
  9. package/dist/agents/peer-notice-route.js +46 -0
  10. package/dist/agents/peer-notices.d.ts +103 -0
  11. package/dist/agents/peer-notices.js +206 -0
  12. package/dist/agents/peer-session-drain.d.ts +39 -4
  13. package/dist/agents/peer-session-drain.js +248 -42
  14. package/dist/agents/send-message-tool.d.ts +8 -1
  15. package/dist/agents/send-message-tool.js +96 -30
  16. package/dist/agents/subagent.js +1 -0
  17. package/dist/brain/status-sink.d.ts +10 -0
  18. package/dist/brain/status-sink.js +13 -4
  19. package/dist/brain/stream-engine.d.ts +11 -0
  20. package/dist/brain/stream-engine.js +39 -3
  21. package/dist/core/arg-summary.d.ts +13 -3
  22. package/dist/core/arg-summary.js +138 -7
  23. package/dist/core/auto-mode-defaults.d.ts +11 -0
  24. package/dist/core/auto-mode-defaults.js +2 -0
  25. package/dist/core/auto-mode.d.ts +59 -0
  26. package/dist/core/auto-mode.js +57 -1
  27. package/dist/core/checkpoint-store.js +2 -2
  28. package/dist/core/governance-codes.d.ts +1 -1
  29. package/dist/core/governance-codes.js +8 -0
  30. package/dist/core/hooks.d.ts +30 -0
  31. package/dist/core/hooks.js +43 -8
  32. package/dist/core/mailbox-store.d.ts +33 -1
  33. package/dist/core/mailbox-store.js +42 -2
  34. package/dist/core/runner/assemble-result.d.ts +5 -0
  35. package/dist/core/runner/assemble-result.js +1 -1
  36. package/dist/core/runner/denial-limit-arms.d.ts +149 -0
  37. package/dist/core/runner/denial-limit-arms.js +91 -0
  38. package/dist/core/runner/edited-files-ledger.d.ts +33 -0
  39. package/dist/core/runner/edited-files-ledger.js +14 -0
  40. package/dist/core/runner/prepare-hands-readface.d.ts +5 -0
  41. package/dist/core/runner/prepare-hands-readface.js +1 -0
  42. package/dist/core/runner/prepare-task.d.ts +62 -1
  43. package/dist/core/runner/prepare-task.js +135 -89
  44. package/dist/core/runner/runtask.js +12 -0
  45. package/dist/core/sensitive-path-policy.d.ts +27 -6
  46. package/dist/core/sensitive-path-policy.js +57 -2
  47. package/dist/core/task-notification.d.ts +24 -2
  48. package/dist/core/task-notification.js +6 -1
  49. package/dist/core/tool-policy.d.ts +55 -4
  50. package/dist/core/tool-policy.js +28 -5
  51. package/dist/core/tools.js +1 -0
  52. package/dist/core/types.d.ts +251 -15
  53. package/dist/core/wiring-manifest.d.ts +41 -5
  54. package/dist/core/wiring-manifest.js +8 -0
  55. package/dist/engine/harness/agent-harness.d.ts +1 -0
  56. package/dist/engine/harness/agent-harness.js +3 -0
  57. package/dist/engine/harness/types.d.ts +3 -0
  58. package/dist/engine/loop/agent-loop.d.ts +7 -0
  59. package/dist/engine/loop/agent-loop.js +79 -0
  60. package/dist/engine/loop/types.d.ts +42 -0
  61. package/dist/index.d.ts +12 -6
  62. package/dist/index.js +10 -4
  63. package/dist/internal/harness-types.d.ts +1 -1
  64. package/dist/orchestration/workflow.js +7 -3
  65. package/dist/tools/fs/fs-write.d.ts +4 -4
  66. package/dist/tools/fs/fs-write.js +99 -14
  67. package/dist/tools/fs/index.d.ts +7 -1
  68. package/dist/tools/fs/index.js +1 -1
  69. package/dist/tools/fs/safety.d.ts +29 -8
  70. package/dist/tools/fs/safety.js +11 -1
  71. package/package.json +1 -1
  72. package/test/export-surface.snapshot.json +181 -1
@@ -5,7 +5,8 @@ import { AgentHarness, DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT
5
5
  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
- import { createAutoModeDecider } from "../auto-mode.js";
8
+ import { createAutoModeDecider, createAutoModeDenialTracker } from "../auto-mode.js";
9
+ import { attachRebuiltDenialTrackers, createDenialLimitStop, headlessDenyAtFold, headlessDenyAtRecheck, judgeInheritedClassifier, settleDenialLimitFallback } from "./denial-limit-arms.js";
9
10
  import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
10
11
  import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
11
12
  import { resolveTaskModel } from "../roles.js";
@@ -63,6 +64,7 @@ import { limitConfigError, prepareConfigDoors } from "./prepare-config-doors.js"
63
64
  import { NAMESPACED_NAME_SHAPES, prepareSafetyScan } from "./prepare-safety-scan.js";
64
65
  import { prepareAcquireReconcile } from "./prepare-acquire-reconcile.js";
65
66
  import { prepareHandsMount, resolveHandsLessReadFace } from "./prepare-hands-readface.js";
67
+ import { createEditedFilesLedger } from "./edited-files-ledger.js";
66
68
  import { prepareWorkspaceRestore, remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./prepare-workspace-restore.js";
67
69
  import { defaultPromptProvider, buildEnvironmentContext, formatLocalDate, isValidTimeZone, PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js";
68
70
  import { applyGitFrameGuard, probeGitStatusLane } from "./git-status-frame.js";
@@ -85,6 +87,7 @@ import { createMonitorTool } from "../../tools/monitor.js";
85
87
  import { createWorktreeTools } from "../../tools/worktree.js";
86
88
  import { announcePeerLaneMount, bindPeerLaneDrain, listAgentsMountable, mountListAgents, peerLaneSendMessageSeats } from "../../agents/peer-session-drain.js";
87
89
  import { CROSS_SESSION_CLASSIFIER_RULE } from "../../agents/cross-session-envelope.js";
90
+ import { NodeExecutionEnv } from "../../engine/execution-env/node-execution-env.js";
88
91
  import { applyCompactionToReadFileState, isReadDedupStubResult, FULL_SHELL_CONTRACT_ID, HAND_TOOL_EFFECTS } from "../../tools/fs/index.js";
89
92
  import { decodeTextBytes } from "../../tools/fs/encoding.js";
90
93
  import { ASK_USER_QUESTION_TOOL_NAME, createAskUserQuestionTool, classifyQuestionOutcome, isLiveQuestionFace, validateAskQuestions, } from "../ask-question.js";
@@ -635,6 +638,45 @@ function createFileHistoryBoundarySeat(opts) {
635
638
  },
636
639
  };
637
640
  }
641
+ async function resolveFileHistoryCoordinates(enabled, env, taskRoot, lineage, sessionId) {
642
+ if (!enabled)
643
+ return { historyRoot: taskRoot, historyScope: sessionId, historyFs: "" };
644
+ const canonRoot = await env.canonicalPath(taskRoot);
645
+ const historyRoot = canonRoot.ok ? canonRoot.value : taskRoot;
646
+ const historyFs = fileHistoryFilesystemIdentity(env);
647
+ return { historyRoot, historyScope: resolveFileHistoryScope(lineage, historyRoot, historyFs, sessionId), historyFs };
648
+ }
649
+ export function resolveFileHistoryScope(lineage, historyRoot, historyFs, sessionId) {
650
+ if (lineage === undefined || lineage.scope === "" || lineage.root !== historyRoot || lineage.fs !== historyFs)
651
+ return sessionId;
652
+ return lineage.scope;
653
+ }
654
+ const envFilesystemTokens = new WeakMap();
655
+ let envFilesystemSeq = 0;
656
+ export function fileHistoryFilesystemIdentity(env) {
657
+ if (isRemoteExecutionEnv(env)) {
658
+ try {
659
+ const h = env.workspaceHandle();
660
+ return JSON.stringify(["remote", h.provider, h.sandboxId, h.deviceId !== undefined && h.deviceId !== "" ? h.deviceId : null]);
661
+ }
662
+ catch {
663
+ }
664
+ }
665
+ else if (env.hostLocalPaths === true || (env.hostLocalPaths === undefined && env instanceof NodeExecutionEnv)) {
666
+ return "host";
667
+ }
668
+ let token = envFilesystemTokens.get(env);
669
+ if (token === undefined) {
670
+ token = `instance:${++envFilesystemSeq}`;
671
+ envFilesystemTokens.set(env, token);
672
+ }
673
+ return token;
674
+ }
675
+ function childScopeRewindRefusal(rootScope, target) {
676
+ const e = new Error(`rewind-files: this run is a delegated child recording its edits into the ROOT session "${rootScope}"'s file history, so restoring files to entry "${target}" is the root session's action (rewindFilesTo / resumeAt+restoreFiles on that session), not the child's — the files were NOT rewound`);
677
+ e.code = "rewind.child_scope_unsupported";
678
+ return e;
679
+ }
638
680
  export function gatedCallIdOf(p) {
639
681
  if (p.suspendRef.token !== undefined)
640
682
  return p.suspendRef.gatedCallId;
@@ -1210,6 +1252,18 @@ function autoModeLatchHealthy(d) {
1210
1252
  return false;
1211
1253
  }
1212
1254
  }
1255
+ function autoModeArmReasonOf(intent, facePresent, capsAutoMode, capsFaulted) {
1256
+ if (!intent)
1257
+ return "no_intent";
1258
+ if (!facePresent)
1259
+ return "no_face";
1260
+ if (capsAutoMode === false)
1261
+ return capsFaulted ? "resolver_fault" : "denied";
1262
+ return "armed";
1263
+ }
1264
+ function validateInputFaultNotice(onError, sessionId) {
1265
+ return ({ toolName, toolCallId, error }) => onError?.(new Error(`tool input pre-validation (validateInput) threw for ${toolName} (call ${toolCallId}) — read as no verdict; the call proceeded to the gate: ${error instanceof Error ? error.message : String(error)}`), { phase: "hook", sessionId });
1266
+ }
1213
1267
  function refuseInvalidAutoModeSeat(spec) {
1214
1268
  if (spec.autoModeRequested !== undefined && spec.autoModeRequested !== true) {
1215
1269
  const e = new Error(`autoModeRequested must be the literal true when present (got ${JSON.stringify(spec.autoModeRequested)}) — omit it for a non-auto task; a non-boolean or false value is refused rather than read as either polarity.`);
@@ -1474,9 +1528,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1474
1528
  throw e;
1475
1529
  }
1476
1530
  const fileHistoryStore = resolveWiredFileHistoryStore(deps.fileHistoryStore);
1477
- if (fileHistoryStore !== undefined) {
1478
- await adoptForkedFileHistory(fileHistoryStore, session, sessionId, deps.onError);
1479
- }
1480
1531
  const legacyRewindFiles = spec.rewindFiles;
1481
1532
  if (legacyRewindFiles === true) {
1482
1533
  if (spec.resumeAt !== undefined) {
@@ -1509,12 +1560,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1509
1560
  });
1510
1561
  }
1511
1562
  const fileHistoryEnabled = fileHistoryStore !== undefined && handsEnabled;
1512
- let historyRoot = taskRootFinal;
1513
- if (fileHistoryEnabled) {
1514
- const canonRoot = await executionEnv.canonicalPath(taskRootFinal);
1515
- if (canonRoot.ok)
1516
- historyRoot = canonRoot.value;
1517
- }
1563
+ const { historyRoot, historyScope, historyFs } = await resolveFileHistoryCoordinates(fileHistoryEnabled, executionEnv, taskRootFinal, internals?.fileHistoryLineage, sessionId);
1564
+ if (fileHistoryStore !== undefined && internals?.fileHistoryLineage === undefined)
1565
+ await adoptForkedFileHistory(fileHistoryStore, session, sessionId, deps.onError);
1566
+ if (rewindTarget !== undefined && historyScope !== sessionId)
1567
+ throw childScopeRewindRefusal(historyScope, rewindTarget);
1518
1568
  if (fileHistoryStore === undefined) {
1519
1569
  if (rewindTarget !== undefined) {
1520
1570
  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`);
@@ -1591,14 +1641,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1591
1641
  }
1592
1642
  const trackFileEdit = fileHistoryEnabled
1593
1643
  ? async (req) => {
1594
- const r = await fileHistoryStore.trackEdit(sessionId, req.key, executionEnv, historyRoot, req.signal);
1644
+ const r = await fileHistoryStore.trackEdit(historyScope, req.key, executionEnv, historyRoot, req.signal);
1595
1645
  if (r.ok) {
1596
1646
  if (!r.minted)
1597
1647
  return { ok: true };
1598
1648
  return {
1599
1649
  ok: true,
1600
1650
  annul: async (proof) => {
1601
- const a = await fileHistoryStore.annulTrack(sessionId, req.key, historyRoot, proof === "verify" ? { env: executionEnv, ...(req.signal !== undefined ? { signal: req.signal } : {}) } : undefined);
1651
+ const a = await fileHistoryStore.annulTrack(historyScope, req.key, historyRoot, proof === "verify" ? { env: executionEnv, ...(req.signal !== undefined ? { signal: req.signal } : {}) } : undefined);
1602
1652
  if (a.ok)
1603
1653
  return;
1604
1654
  try {
@@ -1623,7 +1673,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1623
1673
  };
1624
1674
  }
1625
1675
  : undefined;
1626
- const fileHistoryBoundary = fileHistoryEnabled
1676
+ const { note: noteFileEdited, snapshot: editedFilesSnapshot } = createEditedFilesLedger();
1677
+ const fileHistoryBoundary = fileHistoryEnabled && historyScope === sessionId
1627
1678
  ? createFileHistoryBoundarySeat({ store: fileHistoryStore, sessionId, env: executionEnv, root: historyRoot, onError: deps.onError })
1628
1679
  : undefined;
1629
1680
  const harnessRef = {};
@@ -1680,7 +1731,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1680
1731
  : shellGateRank[liveShellGate] >= shellGateRank[seedShellGate]
1681
1732
  ? liveShellGate
1682
1733
  : seedShellGate;
1683
- const inheritedParentConstraints = liveInheritedGate?.parentConstraints;
1734
+ const inheritedParentConstraints = attachRebuiltDenialTrackers(liveInheritedGate?.parentConstraints, deps.autoMode?.denialLimit);
1684
1735
  const autoModeIntent = autoModeSeat || liveInheritedGate?.autoModeRequested === true || seedInheritedGate?.autoModeRequested === true;
1685
1736
  const liveAdmittedOrg = liveInheritedGate?.admittedOrgScopes;
1686
1737
  const seedAdmittedOrg = seedInheritedGate?.admittedOrgScopes;
@@ -1769,7 +1820,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1769
1820
  ...(durableMandate ? { durableMandate: true } : {}),
1770
1821
  ...(contentMandate ? { contentMandate: true } : {}),
1771
1822
  ...(autoModeDecider !== undefined
1772
- ? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1823
+ ? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}), ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1773
1824
  : {}),
1774
1825
  },
1775
1826
  ]
@@ -1785,7 +1836,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1785
1836
  ...(durableMandate ? { durableMandate: true } : {}),
1786
1837
  ...(contentMandate ? { contentMandate: true } : {}),
1787
1838
  ...(autoModeDecider !== undefined
1788
- ? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1839
+ ? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}), ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1789
1840
  : {}),
1790
1841
  },
1791
1842
  ]
@@ -1811,6 +1862,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1811
1862
  spec.handsReadOnly !== true &&
1812
1863
  !(toolFaceSnapshot.exclude?.includes("Bash") ?? false);
1813
1864
  let autoModeDecider;
1865
+ let autoModeDenialTracking;
1866
+ const { gateStopRef, stopForDenialLimit } = createDenialLimitStop({ sessionId, runId, onNotice: deps.onNotice, abort: () => { abortController.abort(); void harness.abort(); } });
1814
1867
  let autoModeArming;
1815
1868
  const delegationEntryCapsResolved = resolveDelegationEntryCaps(deps.delegationEntryCaps);
1816
1869
  const enrichSpecToolCtx = (ctx) => ({
@@ -1852,6 +1905,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1852
1905
  },
1853
1906
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
1854
1907
  parentCwd: taskRootFinal,
1908
+ ...(fileHistoryEnabled ? { fileHistoryLineage: { scope: historyScope, root: historyRoot, fs: historyFs } } : {}),
1855
1909
  reminderMark,
1856
1910
  reminderDisclosureCounts,
1857
1911
  ...(centerAdoption !== undefined ? { centerArtifactDigest: centerAdoption.artifact.artifactDigest } : {}),
@@ -1977,6 +2031,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1977
2031
  }
1978
2032
  agentForkDenial = forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork);
1979
2033
  observersActive = runtimeCaps?.allowObservers === true;
2034
+ const autoModeArmReason = autoModeArmReasonOf(autoModeIntent, deps.autoMode !== undefined, runtimeCaps?.autoMode, runtimeCapsFaulted);
1980
2035
  if (autoModeIntent && deps.autoMode !== undefined && runtimeCaps?.autoMode !== false) {
1981
2036
  const am = deps.autoMode;
1982
2037
  let classifierModel;
@@ -1992,6 +2047,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1992
2047
  const classifierLaneRule = peerLaneActive && peerSendMessageBuiltIn;
1993
2048
  const classifierSystemPrompt = buildAutoModePrompt(classifierLaneRule ? { ...am, crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : am);
1994
2049
  const classifierRuntime = brainToRuntime(deps.brain);
2050
+ autoModeDenialTracking = createAutoModeDenialTracker(am.denialLimit);
1995
2051
  autoModeDecider = createAutoModeDecider({
1996
2052
  ...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
1997
2053
  ...(am.failureThreshold !== undefined ? { failureThreshold: am.failureThreshold } : {}),
@@ -2301,7 +2357,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2301
2357
  foldProtocolAxes(a2a.toolAxes, "A2A");
2302
2358
  const callIssuedAtRef = {};
2303
2359
  const memoryWriteGateRef = {};
2304
- 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 } : {}) };
2360
+ 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 } : {}), onFileEdited: noteFileEdited, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
2305
2361
  const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
2306
2362
  const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
2307
2363
  resolvedReadFace = handsReadFace.resolvedReadFace;
@@ -2340,6 +2396,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2340
2396
  enrichCtx: enrichSpecToolCtx,
2341
2397
  ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}),
2342
2398
  ...(deps.peerAdmission !== undefined ? { admission: deps.peerAdmission } : {}),
2399
+ ...(deps.onNotice !== undefined ? { onNotice: deps.onNotice } : {}),
2343
2400
  ...(internals?.peerSelfRef !== undefined ? { peerSelf: internals.peerSelfRef } : {}),
2344
2401
  ...(internals?.peerInboundChainRef !== undefined ? { peerInbound: internals.peerInboundChainRef } : {}),
2345
2402
  ...(deps.onBackgroundChildEvent ? { onBackgroundChildEvent: deps.onBackgroundChildEvent } : {}),
@@ -3613,7 +3670,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3613
3670
  stream: withBrainCallGuardrail((m, c, o) => deps.brain.stream(m, c, o), brainCallGuardrailMs, brainCallGuardrailRef),
3614
3671
  };
3615
3672
  const harness = new AgentHarness({
3616
- abortResultDetails: () => parkContaminationMarker({ suspendRef, reviewRef }),
3673
+ abortResultDetails: () => (gateStopRef.terminal !== undefined ? { code: gateStopRef.terminal.code } : parkContaminationMarker({ suspendRef, reviewRef })),
3674
+ onToolInputValidationFault: validateInputFaultNotice(deps.onError, sessionId),
3617
3675
  ...(spec.limits?.maxOutputTokens !== undefined && spec.limits.maxOutputTokens > 0
3618
3676
  ? { maxOutputTokens: spec.limits.maxOutputTokens }
3619
3677
  : {}),
@@ -3969,8 +4027,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3969
4027
  cwdRef: handsCwdRef,
3970
4028
  deniesDirectoryRead: readDenyMatcher !== undefined ? (d) => readDenyMatcher.matchPath(d) !== null : undefined,
3971
4029
  });
3972
- const frozenClassifierExcluded = (d) => d.decisionReason === "hook" || d.matchedAskRule !== undefined;
3973
- const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider) => {
4030
+ const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider, ancestorTracker) => {
3974
4031
  let editArgs = edit;
3975
4032
  for (let round = 0;; round++) {
3976
4033
  if (round >= 3) {
@@ -3996,22 +4053,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3996
4053
  }
3997
4054
  if (re.updatedInput !== undefined)
3998
4055
  editArgs = re.updatedInput;
3999
- if (ancestorDecider !== undefined && !frozenClassifierExcluded(re)) {
4000
- const verdict = await ancestorDecider
4001
- .decide({ req: { ...creq, args: editArgs }, ...(re.message !== undefined ? { askMessage: re.message } : {}) }, csignal ?? abortController.signal)
4002
- .catch(() => ({ kind: "unavailable", cause: "error" }));
4003
- if (verdict.kind === "allow")
4004
- return { action: "allow", updatedInput: editArgs };
4005
- if (verdict.kind === "block") {
4006
- const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
4007
- const category = verdict.category ? inlineUntrusted(verdict.category) : "";
4008
- return {
4009
- action: "deny",
4010
- message: `auto-mode classifier blocked the approved edit at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
4011
- decisionReason: "classifier",
4012
- };
4013
- }
4014
- }
4056
+ const judged = await judgeInheritedClassifier({ autoMode: ancestorDecider !== undefined ? { decider: ancestorDecider, denialTracking: ancestorTracker } : undefined, ask: re, req: { ...creq, args: editArgs }, signal: csignal ?? abortController.signal, subject: "the approved edit" });
4057
+ if (judged.kind === "allow")
4058
+ return { action: "allow", updatedInput: editArgs };
4059
+ if (judged.kind === "deny")
4060
+ return judged.result;
4061
+ const { ask: editAsk, fallback: editFallback, mintedHere: editMintedHere } = judged;
4062
+ re = editAsk;
4015
4063
  const rr = await resolveAsk({
4016
4064
  toolName: creq.toolName,
4017
4065
  toolCallId: creq.toolCallId,
@@ -4022,8 +4070,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4022
4070
  ...riskAxesOf(creq.toolName),
4023
4071
  ...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4024
4072
  ...(re.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: re.persistedRuleShadowed } : {}),
4073
+ ...(editFallback !== undefined ? { denialLimitFallback: editFallback } : {}),
4025
4074
  ruleEvidence: inheritedAskEvidence,
4026
- }, onAskOf, csignal ?? abortController.signal);
4075
+ }, onAskOf, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
4076
+ settleDenialLimitFallback({ fallback: editFallback, mintedHere: editMintedHere, tracker: ancestorTracker, resolved: rr, headless: headlessDenyAtRecheck, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
4027
4077
  if (rr.action !== "allow")
4028
4078
  return rr;
4029
4079
  if (rr.updatedInput === undefined)
@@ -4076,23 +4126,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4076
4126
  `ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
4077
4127
  };
4078
4128
  }
4079
- if (pc.autoMode !== undefined && !frozenClassifierExcluded(first)) {
4080
- const verdict = await pc.autoMode.decider
4081
- .decide({ req: creq, ...(first.message !== undefined ? { askMessage: first.message } : {}) }, csignal ?? abortController.signal)
4082
- .catch(() => ({ kind: "unavailable", cause: "error" }));
4083
- if (verdict.kind === "allow")
4084
- return { action: "allow" };
4085
- if (verdict.kind === "block") {
4086
- const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
4087
- const category = verdict.category ? inlineUntrusted(verdict.category) : "";
4088
- return {
4089
- action: "deny",
4090
- message: `auto-mode classifier blocked this call at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
4091
- decisionReason: "classifier",
4092
- };
4093
- }
4094
- }
4095
- if (sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
4129
+ const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: first, req: creq, signal: csignal ?? abortController.signal, subject: "this call" });
4130
+ if (judged.kind === "allow")
4131
+ return { action: "allow" };
4132
+ if (judged.kind === "deny")
4133
+ return judged.result;
4134
+ const { ask: inheritedAsk, fallback, mintedHere } = judged;
4135
+ if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
4096
4136
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
4097
4137
  return { action: "allow" };
4098
4138
  }
@@ -4102,27 +4142,29 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4102
4142
  toolName: creq.toolName,
4103
4143
  toolCallId: creq.toolCallId,
4104
4144
  args: presentedArgs,
4105
- ...ruleOffersOf(creq.toolName, presentedArgs, { ...(first.action === "ask" ? first : {}), ancestorResolved: true }),
4106
- message: first.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
4145
+ ...ruleOffersOf(creq.toolName, presentedArgs, { ...(inheritedAsk.action === "ask" ? inheritedAsk : {}), ancestorResolved: true }),
4146
+ message: inheritedAsk.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
4107
4147
  ...askSourceIdentity(),
4108
4148
  ...riskAxesOf(creq.toolName),
4109
- ...(first.action === "ask" && first.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4110
- ...(first.action === "ask" && first.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: first.persistedRuleShadowed } : {}),
4149
+ ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4150
+ ...(inheritedAsk.action === "ask" && inheritedAsk.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: inheritedAsk.persistedRuleShadowed } : {}),
4151
+ ...(fallback !== undefined ? { denialLimitFallback: fallback } : {}),
4111
4152
  ruleEvidence: inheritedAskEvidence,
4112
- }, pc.onAsk, csignal ?? abortController.signal);
4153
+ }, pc.onAsk, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
4113
4154
  const askWaitMs = Math.max(0, now() - askT0);
4114
4155
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
4115
4156
  if (markInheritedUnavailable(creq.toolCallId))
4116
- return first;
4157
+ return inheritedAsk;
4117
4158
  return {
4118
4159
  action: "deny",
4119
4160
  message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
4120
4161
  };
4121
4162
  }
4163
+ settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
4122
4164
  if (resolved.action !== "allow")
4123
4165
  return resolved;
4124
4166
  if (resolved.updatedInput !== undefined) {
4125
- return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal, pc.autoMode?.decider);
4167
+ return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal, pc.autoMode?.decider, pc.autoMode?.denialTracking);
4126
4168
  }
4127
4169
  recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
4128
4170
  return { action: "allow" };
@@ -4164,24 +4206,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4164
4206
  };
4165
4207
  }
4166
4208
  const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
4167
- if (pc.autoMode !== undefined && !frozenClassifierExcluded(decision)) {
4168
- const verdict = await pc.autoMode.decider
4169
- .decide({ req: { ...creq, args: presentedArgs }, ...(decision.message !== undefined ? { askMessage: decision.message } : {}) }, csignal ?? abortController.signal)
4170
- .catch(() => ({ kind: "unavailable", cause: "error" }));
4171
- if (verdict.kind === "allow") {
4172
- return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
4173
- }
4174
- if (verdict.kind === "block") {
4175
- const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
4176
- const category = verdict.category ? inlineUntrusted(verdict.category) : "";
4177
- return {
4178
- action: "deny",
4179
- message: `auto-mode classifier blocked this call at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
4180
- decisionReason: "classifier",
4181
- };
4182
- }
4183
- }
4184
- if (sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
4209
+ const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: decision, req: { ...creq, args: presentedArgs }, signal: csignal ?? abortController.signal, subject: "this call" });
4210
+ if (judged.kind === "allow")
4211
+ return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
4212
+ if (judged.kind === "deny")
4213
+ return judged.result;
4214
+ const { ask: inheritedAsk, fallback, mintedHere } = judged;
4215
+ if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
4185
4216
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
4186
4217
  return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
4187
4218
  }
@@ -4190,27 +4221,29 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4190
4221
  toolName: creq.toolName,
4191
4222
  toolCallId: creq.toolCallId,
4192
4223
  args: presentedArgs,
4193
- ...ruleOffersOf(creq.toolName, presentedArgs, { ...(decision.action === "ask" ? decision : {}), ancestorResolved: true }),
4194
- message: decision.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
4224
+ ...ruleOffersOf(creq.toolName, presentedArgs, { ...(inheritedAsk.action === "ask" ? inheritedAsk : {}), ancestorResolved: true }),
4225
+ message: inheritedAsk.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
4195
4226
  ...askSourceIdentity(),
4196
4227
  ...riskAxesOf(creq.toolName),
4197
- ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4198
- ...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
4228
+ ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4229
+ ...(inheritedAsk.action === "ask" && inheritedAsk.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: inheritedAsk.persistedRuleShadowed } : {}),
4230
+ ...(fallback !== undefined ? { denialLimitFallback: fallback } : {}),
4199
4231
  ruleEvidence: inheritedAskEvidence,
4200
- }, pc.onAsk, csignal ?? abortController.signal);
4232
+ }, pc.onAsk, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
4201
4233
  const askWaitMs = Math.max(0, now() - askT0);
4202
4234
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
4203
4235
  if (markInheritedUnavailable(creq.toolCallId))
4204
- return decision;
4236
+ return inheritedAsk;
4205
4237
  return {
4206
4238
  action: "deny",
4207
4239
  message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
4208
4240
  };
4209
4241
  }
4242
+ settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
4210
4243
  if (resolved.action !== "allow")
4211
4244
  return resolved;
4212
4245
  if (resolved.updatedInput !== undefined) {
4213
- return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal, pc.autoMode?.decider);
4246
+ return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal, pc.autoMode?.decider, pc.autoMode?.denialTracking);
4214
4247
  }
4215
4248
  if (pc.preToolUse === undefined) {
4216
4249
  recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
@@ -4400,10 +4433,21 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4400
4433
  memoryAdmissionWired: deps.memoryScopeAdmission !== undefined,
4401
4434
  retentionPolicyWired: deps.retentionPolicy !== undefined,
4402
4435
  ...(modelGateManifest !== undefined ? { modelGate: modelGateManifest } : {}),
4436
+ autoMode: { armed: autoModeArmReason === "armed", reason: autoModeArmReason },
4403
4437
  });
4404
4438
  peerLaneRefs.askEffective = wiringManifest.ask.effective;
4405
4439
  if (peerLaneActive && internals?.onTaskNotification !== undefined) {
4406
- bindPeerLaneDrain(harness, { deps, sessionId, runId, scope: taskScope, inject: internals.onTaskNotification, ownTokens: () => internals?.peerSelfRef?.current.ownTokens ?? [], refs: peerLaneRefs });
4440
+ bindPeerLaneDrain(harness, {
4441
+ deps,
4442
+ sessionId,
4443
+ runId,
4444
+ scope: taskScope,
4445
+ inject: internals.onTaskNotification,
4446
+ ownTokens: () => internals?.peerSelfRef?.current.ownTokens ?? [],
4447
+ refs: peerLaneRefs,
4448
+ selfName: () => internals?.explicitAgentName,
4449
+ parked: () => suspendRef.token !== undefined || reviewRef.token !== undefined,
4450
+ });
4407
4451
  }
4408
4452
  const parkLaneArmed = wiringManifest.parkLane.effective === true;
4409
4453
  announceDurableGateUnavailable({ onNotice: deps.onNotice, forceDurableGate: runtimeCaps?.forceDurableGate === true, storeWired: checkpointStore !== undefined, taskStoreNull: spec.checkpointStore === null, liveApprover: isLiveApproverSeat(frozenOnAsk), liveQuestionFace: liveQuestionFace !== undefined, parentConstraints: liveInheritedGate?.parentConstraints, sessionId, runId, principal: spec.principal });
@@ -4465,6 +4509,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4465
4509
  ...riskAxesOf(req.toolName),
4466
4510
  ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4467
4511
  ...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
4512
+ ...(decision.action === "ask" && decision.denialLimitFallback !== undefined ? { denialLimitFallback: decision.denialLimitFallback } : {}),
4468
4513
  ...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
4469
4514
  ...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
4470
4515
  ...(decision.action === "ask" && decision.ruleEvidence !== undefined ? { ruleEvidence: decision.ruleEvidence } : {}),
@@ -5276,7 +5321,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5276
5321
  ...(notifyPermissionDenied ? { permissionDenied: notifyPermissionDenied } : {}),
5277
5322
  onHookError: notifyHookError,
5278
5323
  shellGated: (e.toolName === "Bash" && shellGatedBash) || (e.toolName === "Monitor" && shellGatedMonitor),
5279
- ...(autoModeDecider ? { autoMode: { decider: autoModeDecider } } : {}),
5324
+ ...(autoModeDecider ? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}) } } : {}),
5325
+ onHeadlessDenialLimit: stopForDenialLimit,
5280
5326
  ...(permissionRuleLane
5281
5327
  ? {
5282
5328
  persistedRules: {
@@ -5714,7 +5760,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5714
5760
  const effectiveReadFaceObserved = carrierReadFace();
5715
5761
  const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
5716
5762
  const preparedHolder = {};
5717
- 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 } : {}) });
5763
+ const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, 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, gateStopRef, 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 } : {}) });
5718
5764
  const prepared = buildPrepared();
5719
5765
  preparedHolder.current = prepared;
5720
5766
  return prepared;
@@ -1779,6 +1779,10 @@ export class Runner {
1779
1779
  ...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
1780
1780
  ...(taskIdRef.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: taskIdRef.effectiveMemoryScopes } : {}),
1781
1781
  ...(taskIdRef.effectiveReasoning !== undefined ? { effectiveReasoning: taskIdRef.effectiveReasoning } : {}),
1782
+ ...(() => {
1783
+ const observed = taskIdRef.editedFiles?.();
1784
+ return observed !== undefined && observed.length > 0 ? { editedFiles: observed } : {};
1785
+ })(),
1782
1786
  ...(() => {
1783
1787
  const hinted = err.retryAfterMs;
1784
1788
  return code === "memory.admission_required" && typeof hinted === "number" && Number.isFinite(hinted) && hinted > 0
@@ -2349,6 +2353,8 @@ export class Runner {
2349
2353
  notificationSessionId = prepared.sessionId;
2350
2354
  if (taskIdRef)
2351
2355
  taskIdRef.effectiveMemoryScopes = prepared.effectiveMemoryScopes;
2356
+ if (taskIdRef)
2357
+ taskIdRef.editedFiles = prepared.editedFilesSnapshot;
2352
2358
  const runSourceTaskId = spec.taskId ?? prepared.sessionId;
2353
2359
  const parentToolCallId = internals?.parentToolCallId;
2354
2360
  const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: runSourceTaskId } : { eventId: uuidv7() };
@@ -2872,6 +2878,8 @@ export class Runner {
2872
2878
  ...(s.retryInSec !== undefined ? { retryInSec: s.retryInSec } : {}),
2873
2879
  ...(s.retryInMs !== undefined ? { retryInMs: s.retryInMs } : {}),
2874
2880
  ...(s.retryAtMs !== undefined ? { retryAtMs: s.retryAtMs } : {}),
2881
+ ...(s.elapsedMs !== undefined ? { elapsedMs: s.elapsedMs } : {}),
2882
+ ...(s.timeoutMs !== undefined ? { timeoutMs: s.timeoutMs } : {}),
2875
2883
  ...(s.attempt !== undefined ? { attempt: s.attempt } : {}),
2876
2884
  ...(s.maxRetries !== undefined ? { maxRetries: s.maxRetries } : {}),
2877
2885
  ...(s.errClass !== undefined ? { errClass: s.errClass } : {}),
@@ -3886,6 +3894,9 @@ export class Runner {
3886
3894
  if (threw === undefined && prepared.brainCallGuardrailRef.timedOut !== undefined) {
3887
3895
  threw = prepared.brainCallGuardrailRef.timedOut;
3888
3896
  }
3897
+ if (threw === undefined && prepared.gateStopRef.terminal !== undefined) {
3898
+ threw = prepared.gateStopRef.terminal;
3899
+ }
3889
3900
  if (rs.limits.platformTerminal !== undefined) {
3890
3901
  threw = rs.limits.platformTerminal;
3891
3902
  }
@@ -4069,6 +4080,7 @@ export class Runner {
4069
4080
  model: prepared.model.id,
4070
4081
  unpricedSpend: rs.telemetry.unpricedSpend,
4071
4082
  rewindNotes: prepared.rewindNotes,
4083
+ editedFiles: prepared.editedFilesSnapshot(),
4072
4084
  haltedOnUserRejection: prepared.batchHaltRef.current !== undefined,
4073
4085
  userHalted: loopLatch.userHalted,
4074
4086
  strandedHumanAnswers,
@@ -15,12 +15,22 @@ import type { ToolPolicy } from "./tool-policy.js";
15
15
  * segments is a wiring ERROR (it would guard nothing) and throws rather than being dropped.
16
16
  * Remember this policy guards WRITES only; reads are out of scope by design.
17
17
  *
18
- * NOT listed, deliberately (HRD-PRM-10): sema's own data root (`$AGENT_DATA_DIR ?? ~/.ai-agent`). Its
19
- * `sessions/` subtree is already owned by `createTranscriptIntegrityPolicy`, which is wired ALWAYS-ON by
20
- * the runner and answers `ask` the design's deliberate verdict, since «reading transcripts is routine»
21
- * and a legitimate operator write must remain clearable by judgment. A deny here would outrank that ask
22
- * under the deny > ask > allow fold and silently convert it into a hard refusal for every deployment
23
- * that wires this list.
18
+ * HRD-PRM-10 sema's own data root (`$AGENT_DATA_DIR ?? ~/.ai-agent`) is excluded BY CONSTRUCTION,
19
+ * not by the absence of an entry. The exclusion used to be "just don't add a pattern that names it",
20
+ * which held only as long as no entry happened to match one of its ANCESTOR segments. That assumption
21
+ * died the moment the shell's own config root joined the list: the shell sets
22
+ * `AGENT_DATA_DIR = <configRoot>/engine-data` with a configRoot of `~/.sema`, so every path under the
23
+ * data root carries a `.sema` segment, and a pattern guarding the config root swallowed the whole data
24
+ * root with it — including the memory library the model is INSTRUCTED to write with the Write tool
25
+ * (`MEMORY_INSTRUCTION_TEMPLATE`), i.e. every memory write refused, on a default server wiring.
26
+ * So the exclusion is now mechanical: a target INSIDE the data root is matched on its path RELATIVE to
27
+ * that root (see {@link createSensitivePathPolicy}'s `dataRoot`), so the root's own ancestry is not the
28
+ * target's business, while a genuinely sensitive path INSIDE it (`<dataRoot>/x/.ssh/id_rsa`) still
29
+ * matches. The `sessions/` subtree keeps its own owner: `createTranscriptIntegrityPolicy` is wired
30
+ * ALWAYS-ON by the runner and answers `ask` — the design's deliberate verdict, since «reading
31
+ * transcripts is routine» and a legitimate operator write must remain clearable by judgment — and a
32
+ * deny here would outrank that ask under the deny > ask > allow fold and silently convert it into a
33
+ * hard refusal for every deployment that wires this list.
24
34
  */
25
35
  export declare const RECOMMENDED_SENSITIVE_PATTERNS: readonly string[];
26
36
  /**
@@ -46,6 +56,17 @@ export declare function createSensitivePathPolicy(opts: {
46
56
  /** `readonly` accepted so {@link RECOMMENDED_SENSITIVE_PATTERNS} can be passed as-is (widening, non-breaking). */
47
57
  patterns: readonly string[];
48
58
  rootPath?: string;
59
+ /**
60
+ * HRD-PRM-10 — the engine data root this deployment runs on. Default
61
+ * `$AGENT_DATA_DIR ?? ~/.ai-agent`, the same resolution
62
+ * {@link import("./tool-policy.js").createTranscriptIntegrityPolicy} performs for its own default.
63
+ * A target inside it is judged on its path RELATIVE to it, so the root's own ancestor segments
64
+ * (`.sema`, when a deployment sites its data root under its config root) cannot make the whole
65
+ * subtree — the memory library the model is instructed to WRITE included — read as a guarded path.
66
+ * Pass it explicitly whenever the deployment passes `memoryEngineDir`/a custom data root, so this
67
+ * gate and the transcript gate keep speaking about the same directory.
68
+ */
69
+ dataRoot?: string;
49
70
  /** Override the guarded tool set (default: `Write`, `Edit`, `MultiEdit`-legacy → Edit, `NotebookEdit`). */
50
71
  tools?: string[];
51
72
  }): ToolPolicy;