@sema-agent/core 7.3.1 → 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 (56) hide show
  1. package/CHANGELOG.md +35 -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/core/auto-mode-defaults.d.ts +11 -0
  18. package/dist/core/auto-mode-defaults.js +2 -0
  19. package/dist/core/auto-mode.d.ts +59 -0
  20. package/dist/core/auto-mode.js +57 -1
  21. package/dist/core/checkpoint-store.js +2 -2
  22. package/dist/core/governance-codes.d.ts +1 -1
  23. package/dist/core/governance-codes.js +8 -0
  24. package/dist/core/hooks.d.ts +30 -0
  25. package/dist/core/hooks.js +43 -8
  26. package/dist/core/mailbox-store.d.ts +33 -1
  27. package/dist/core/mailbox-store.js +42 -2
  28. package/dist/core/runner/assemble-result.d.ts +5 -0
  29. package/dist/core/runner/assemble-result.js +1 -1
  30. package/dist/core/runner/denial-limit-arms.d.ts +149 -0
  31. package/dist/core/runner/denial-limit-arms.js +91 -0
  32. package/dist/core/runner/edited-files-ledger.d.ts +33 -0
  33. package/dist/core/runner/edited-files-ledger.js +14 -0
  34. package/dist/core/runner/prepare-hands-readface.d.ts +5 -0
  35. package/dist/core/runner/prepare-hands-readface.js +1 -0
  36. package/dist/core/runner/prepare-task.d.ts +62 -1
  37. package/dist/core/runner/prepare-task.js +120 -89
  38. package/dist/core/runner/runtask.js +10 -0
  39. package/dist/core/sensitive-path-policy.d.ts +27 -6
  40. package/dist/core/sensitive-path-policy.js +57 -2
  41. package/dist/core/task-notification.d.ts +24 -2
  42. package/dist/core/task-notification.js +6 -1
  43. package/dist/core/tool-policy.d.ts +55 -4
  44. package/dist/core/tool-policy.js +28 -5
  45. package/dist/core/types.d.ts +207 -10
  46. package/dist/index.d.ts +10 -5
  47. package/dist/index.js +8 -3
  48. package/dist/orchestration/workflow.js +7 -3
  49. package/dist/tools/fs/fs-write.d.ts +4 -4
  50. package/dist/tools/fs/fs-write.js +30 -11
  51. package/dist/tools/fs/index.d.ts +7 -1
  52. package/dist/tools/fs/index.js +1 -1
  53. package/dist/tools/fs/safety.d.ts +29 -8
  54. package/dist/tools/fs/safety.js +11 -1
  55. package/package.json +1 -1
  56. package/test/export-surface.snapshot.json +169 -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;
@@ -1486,9 +1528,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1486
1528
  throw e;
1487
1529
  }
1488
1530
  const fileHistoryStore = resolveWiredFileHistoryStore(deps.fileHistoryStore);
1489
- if (fileHistoryStore !== undefined) {
1490
- await adoptForkedFileHistory(fileHistoryStore, session, sessionId, deps.onError);
1491
- }
1492
1531
  const legacyRewindFiles = spec.rewindFiles;
1493
1532
  if (legacyRewindFiles === true) {
1494
1533
  if (spec.resumeAt !== undefined) {
@@ -1521,12 +1560,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1521
1560
  });
1522
1561
  }
1523
1562
  const fileHistoryEnabled = fileHistoryStore !== undefined && handsEnabled;
1524
- let historyRoot = taskRootFinal;
1525
- if (fileHistoryEnabled) {
1526
- const canonRoot = await executionEnv.canonicalPath(taskRootFinal);
1527
- if (canonRoot.ok)
1528
- historyRoot = canonRoot.value;
1529
- }
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);
1530
1568
  if (fileHistoryStore === undefined) {
1531
1569
  if (rewindTarget !== undefined) {
1532
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`);
@@ -1603,14 +1641,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1603
1641
  }
1604
1642
  const trackFileEdit = fileHistoryEnabled
1605
1643
  ? async (req) => {
1606
- 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);
1607
1645
  if (r.ok) {
1608
1646
  if (!r.minted)
1609
1647
  return { ok: true };
1610
1648
  return {
1611
1649
  ok: true,
1612
1650
  annul: async (proof) => {
1613
- 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);
1614
1652
  if (a.ok)
1615
1653
  return;
1616
1654
  try {
@@ -1635,7 +1673,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1635
1673
  };
1636
1674
  }
1637
1675
  : undefined;
1638
- const fileHistoryBoundary = fileHistoryEnabled
1676
+ const { note: noteFileEdited, snapshot: editedFilesSnapshot } = createEditedFilesLedger();
1677
+ const fileHistoryBoundary = fileHistoryEnabled && historyScope === sessionId
1639
1678
  ? createFileHistoryBoundarySeat({ store: fileHistoryStore, sessionId, env: executionEnv, root: historyRoot, onError: deps.onError })
1640
1679
  : undefined;
1641
1680
  const harnessRef = {};
@@ -1692,7 +1731,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1692
1731
  : shellGateRank[liveShellGate] >= shellGateRank[seedShellGate]
1693
1732
  ? liveShellGate
1694
1733
  : seedShellGate;
1695
- const inheritedParentConstraints = liveInheritedGate?.parentConstraints;
1734
+ const inheritedParentConstraints = attachRebuiltDenialTrackers(liveInheritedGate?.parentConstraints, deps.autoMode?.denialLimit);
1696
1735
  const autoModeIntent = autoModeSeat || liveInheritedGate?.autoModeRequested === true || seedInheritedGate?.autoModeRequested === true;
1697
1736
  const liveAdmittedOrg = liveInheritedGate?.admittedOrgScopes;
1698
1737
  const seedAdmittedOrg = seedInheritedGate?.admittedOrgScopes;
@@ -1781,7 +1820,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1781
1820
  ...(durableMandate ? { durableMandate: true } : {}),
1782
1821
  ...(contentMandate ? { contentMandate: true } : {}),
1783
1822
  ...(autoModeDecider !== undefined
1784
- ? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1823
+ ? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}), ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1785
1824
  : {}),
1786
1825
  },
1787
1826
  ]
@@ -1797,7 +1836,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1797
1836
  ...(durableMandate ? { durableMandate: true } : {}),
1798
1837
  ...(contentMandate ? { contentMandate: true } : {}),
1799
1838
  ...(autoModeDecider !== undefined
1800
- ? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1839
+ ? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}), ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1801
1840
  : {}),
1802
1841
  },
1803
1842
  ]
@@ -1823,6 +1862,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1823
1862
  spec.handsReadOnly !== true &&
1824
1863
  !(toolFaceSnapshot.exclude?.includes("Bash") ?? false);
1825
1864
  let autoModeDecider;
1865
+ let autoModeDenialTracking;
1866
+ const { gateStopRef, stopForDenialLimit } = createDenialLimitStop({ sessionId, runId, onNotice: deps.onNotice, abort: () => { abortController.abort(); void harness.abort(); } });
1826
1867
  let autoModeArming;
1827
1868
  const delegationEntryCapsResolved = resolveDelegationEntryCaps(deps.delegationEntryCaps);
1828
1869
  const enrichSpecToolCtx = (ctx) => ({
@@ -1864,6 +1905,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1864
1905
  },
1865
1906
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
1866
1907
  parentCwd: taskRootFinal,
1908
+ ...(fileHistoryEnabled ? { fileHistoryLineage: { scope: historyScope, root: historyRoot, fs: historyFs } } : {}),
1867
1909
  reminderMark,
1868
1910
  reminderDisclosureCounts,
1869
1911
  ...(centerAdoption !== undefined ? { centerArtifactDigest: centerAdoption.artifact.artifactDigest } : {}),
@@ -2005,6 +2047,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2005
2047
  const classifierLaneRule = peerLaneActive && peerSendMessageBuiltIn;
2006
2048
  const classifierSystemPrompt = buildAutoModePrompt(classifierLaneRule ? { ...am, crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : am);
2007
2049
  const classifierRuntime = brainToRuntime(deps.brain);
2050
+ autoModeDenialTracking = createAutoModeDenialTracker(am.denialLimit);
2008
2051
  autoModeDecider = createAutoModeDecider({
2009
2052
  ...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
2010
2053
  ...(am.failureThreshold !== undefined ? { failureThreshold: am.failureThreshold } : {}),
@@ -2314,7 +2357,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2314
2357
  foldProtocolAxes(a2a.toolAxes, "A2A");
2315
2358
  const callIssuedAtRef = {};
2316
2359
  const memoryWriteGateRef = {};
2317
- 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 } : {}) };
2318
2361
  const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
2319
2362
  const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
2320
2363
  resolvedReadFace = handsReadFace.resolvedReadFace;
@@ -2353,6 +2396,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2353
2396
  enrichCtx: enrichSpecToolCtx,
2354
2397
  ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}),
2355
2398
  ...(deps.peerAdmission !== undefined ? { admission: deps.peerAdmission } : {}),
2399
+ ...(deps.onNotice !== undefined ? { onNotice: deps.onNotice } : {}),
2356
2400
  ...(internals?.peerSelfRef !== undefined ? { peerSelf: internals.peerSelfRef } : {}),
2357
2401
  ...(internals?.peerInboundChainRef !== undefined ? { peerInbound: internals.peerInboundChainRef } : {}),
2358
2402
  ...(deps.onBackgroundChildEvent ? { onBackgroundChildEvent: deps.onBackgroundChildEvent } : {}),
@@ -3626,7 +3670,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3626
3670
  stream: withBrainCallGuardrail((m, c, o) => deps.brain.stream(m, c, o), brainCallGuardrailMs, brainCallGuardrailRef),
3627
3671
  };
3628
3672
  const harness = new AgentHarness({
3629
- abortResultDetails: () => parkContaminationMarker({ suspendRef, reviewRef }),
3673
+ abortResultDetails: () => (gateStopRef.terminal !== undefined ? { code: gateStopRef.terminal.code } : parkContaminationMarker({ suspendRef, reviewRef })),
3630
3674
  onToolInputValidationFault: validateInputFaultNotice(deps.onError, sessionId),
3631
3675
  ...(spec.limits?.maxOutputTokens !== undefined && spec.limits.maxOutputTokens > 0
3632
3676
  ? { maxOutputTokens: spec.limits.maxOutputTokens }
@@ -3983,8 +4027,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3983
4027
  cwdRef: handsCwdRef,
3984
4028
  deniesDirectoryRead: readDenyMatcher !== undefined ? (d) => readDenyMatcher.matchPath(d) !== null : undefined,
3985
4029
  });
3986
- const frozenClassifierExcluded = (d) => d.decisionReason === "hook" || d.matchedAskRule !== undefined;
3987
- const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider) => {
4030
+ const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider, ancestorTracker) => {
3988
4031
  let editArgs = edit;
3989
4032
  for (let round = 0;; round++) {
3990
4033
  if (round >= 3) {
@@ -4010,22 +4053,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4010
4053
  }
4011
4054
  if (re.updatedInput !== undefined)
4012
4055
  editArgs = re.updatedInput;
4013
- if (ancestorDecider !== undefined && !frozenClassifierExcluded(re)) {
4014
- const verdict = await ancestorDecider
4015
- .decide({ req: { ...creq, args: editArgs }, ...(re.message !== undefined ? { askMessage: re.message } : {}) }, csignal ?? abortController.signal)
4016
- .catch(() => ({ kind: "unavailable", cause: "error" }));
4017
- if (verdict.kind === "allow")
4018
- return { action: "allow", updatedInput: editArgs };
4019
- if (verdict.kind === "block") {
4020
- const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
4021
- const category = verdict.category ? inlineUntrusted(verdict.category) : "";
4022
- return {
4023
- action: "deny",
4024
- message: `auto-mode classifier blocked the approved edit at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
4025
- decisionReason: "classifier",
4026
- };
4027
- }
4028
- }
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;
4029
4063
  const rr = await resolveAsk({
4030
4064
  toolName: creq.toolName,
4031
4065
  toolCallId: creq.toolCallId,
@@ -4036,8 +4070,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4036
4070
  ...riskAxesOf(creq.toolName),
4037
4071
  ...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4038
4072
  ...(re.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: re.persistedRuleShadowed } : {}),
4073
+ ...(editFallback !== undefined ? { denialLimitFallback: editFallback } : {}),
4039
4074
  ruleEvidence: inheritedAskEvidence,
4040
- }, 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 });
4041
4077
  if (rr.action !== "allow")
4042
4078
  return rr;
4043
4079
  if (rr.updatedInput === undefined)
@@ -4090,23 +4126,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4090
4126
  `ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
4091
4127
  };
4092
4128
  }
4093
- if (pc.autoMode !== undefined && !frozenClassifierExcluded(first)) {
4094
- const verdict = await pc.autoMode.decider
4095
- .decide({ req: creq, ...(first.message !== undefined ? { askMessage: first.message } : {}) }, csignal ?? abortController.signal)
4096
- .catch(() => ({ kind: "unavailable", cause: "error" }));
4097
- if (verdict.kind === "allow")
4098
- return { action: "allow" };
4099
- if (verdict.kind === "block") {
4100
- const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
4101
- const category = verdict.category ? inlineUntrusted(verdict.category) : "";
4102
- return {
4103
- action: "deny",
4104
- message: `auto-mode classifier blocked this call at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
4105
- decisionReason: "classifier",
4106
- };
4107
- }
4108
- }
4109
- 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)) {
4110
4136
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
4111
4137
  return { action: "allow" };
4112
4138
  }
@@ -4116,27 +4142,29 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4116
4142
  toolName: creq.toolName,
4117
4143
  toolCallId: creq.toolCallId,
4118
4144
  args: presentedArgs,
4119
- ...ruleOffersOf(creq.toolName, presentedArgs, { ...(first.action === "ask" ? first : {}), ancestorResolved: true }),
4120
- 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)`,
4121
4147
  ...askSourceIdentity(),
4122
4148
  ...riskAxesOf(creq.toolName),
4123
- ...(first.action === "ask" && first.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4124
- ...(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 } : {}),
4125
4152
  ruleEvidence: inheritedAskEvidence,
4126
- }, 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 }));
4127
4154
  const askWaitMs = Math.max(0, now() - askT0);
4128
4155
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
4129
4156
  if (markInheritedUnavailable(creq.toolCallId))
4130
- return first;
4157
+ return inheritedAsk;
4131
4158
  return {
4132
4159
  action: "deny",
4133
4160
  message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
4134
4161
  };
4135
4162
  }
4163
+ settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
4136
4164
  if (resolved.action !== "allow")
4137
4165
  return resolved;
4138
4166
  if (resolved.updatedInput !== undefined) {
4139
- 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);
4140
4168
  }
4141
4169
  recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
4142
4170
  return { action: "allow" };
@@ -4178,24 +4206,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4178
4206
  };
4179
4207
  }
4180
4208
  const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
4181
- if (pc.autoMode !== undefined && !frozenClassifierExcluded(decision)) {
4182
- const verdict = await pc.autoMode.decider
4183
- .decide({ req: { ...creq, args: presentedArgs }, ...(decision.message !== undefined ? { askMessage: decision.message } : {}) }, csignal ?? abortController.signal)
4184
- .catch(() => ({ kind: "unavailable", cause: "error" }));
4185
- if (verdict.kind === "allow") {
4186
- return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
4187
- }
4188
- if (verdict.kind === "block") {
4189
- const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
4190
- const category = verdict.category ? inlineUntrusted(verdict.category) : "";
4191
- return {
4192
- action: "deny",
4193
- message: `auto-mode classifier blocked this call at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
4194
- decisionReason: "classifier",
4195
- };
4196
- }
4197
- }
4198
- 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)) {
4199
4216
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
4200
4217
  return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
4201
4218
  }
@@ -4204,27 +4221,29 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4204
4221
  toolName: creq.toolName,
4205
4222
  toolCallId: creq.toolCallId,
4206
4223
  args: presentedArgs,
4207
- ...ruleOffersOf(creq.toolName, presentedArgs, { ...(decision.action === "ask" ? decision : {}), ancestorResolved: true }),
4208
- 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)`,
4209
4226
  ...askSourceIdentity(),
4210
4227
  ...riskAxesOf(creq.toolName),
4211
- ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4212
- ...(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 } : {}),
4213
4231
  ruleEvidence: inheritedAskEvidence,
4214
- }, 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 }));
4215
4233
  const askWaitMs = Math.max(0, now() - askT0);
4216
4234
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
4217
4235
  if (markInheritedUnavailable(creq.toolCallId))
4218
- return decision;
4236
+ return inheritedAsk;
4219
4237
  return {
4220
4238
  action: "deny",
4221
4239
  message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
4222
4240
  };
4223
4241
  }
4242
+ settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
4224
4243
  if (resolved.action !== "allow")
4225
4244
  return resolved;
4226
4245
  if (resolved.updatedInput !== undefined) {
4227
- 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);
4228
4247
  }
4229
4248
  if (pc.preToolUse === undefined) {
4230
4249
  recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
@@ -4418,7 +4437,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4418
4437
  });
4419
4438
  peerLaneRefs.askEffective = wiringManifest.ask.effective;
4420
4439
  if (peerLaneActive && internals?.onTaskNotification !== undefined) {
4421
- 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
+ });
4422
4451
  }
4423
4452
  const parkLaneArmed = wiringManifest.parkLane.effective === true;
4424
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 });
@@ -4480,6 +4509,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4480
4509
  ...riskAxesOf(req.toolName),
4481
4510
  ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4482
4511
  ...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
4512
+ ...(decision.action === "ask" && decision.denialLimitFallback !== undefined ? { denialLimitFallback: decision.denialLimitFallback } : {}),
4483
4513
  ...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
4484
4514
  ...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
4485
4515
  ...(decision.action === "ask" && decision.ruleEvidence !== undefined ? { ruleEvidence: decision.ruleEvidence } : {}),
@@ -5291,7 +5321,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5291
5321
  ...(notifyPermissionDenied ? { permissionDenied: notifyPermissionDenied } : {}),
5292
5322
  onHookError: notifyHookError,
5293
5323
  shellGated: (e.toolName === "Bash" && shellGatedBash) || (e.toolName === "Monitor" && shellGatedMonitor),
5294
- ...(autoModeDecider ? { autoMode: { decider: autoModeDecider } } : {}),
5324
+ ...(autoModeDecider ? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}) } } : {}),
5325
+ onHeadlessDenialLimit: stopForDenialLimit,
5295
5326
  ...(permissionRuleLane
5296
5327
  ? {
5297
5328
  persistedRules: {
@@ -5729,7 +5760,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5729
5760
  const effectiveReadFaceObserved = carrierReadFace();
5730
5761
  const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
5731
5762
  const preparedHolder = {};
5732
- 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 } : {}) });
5733
5764
  const prepared = buildPrepared();
5734
5765
  preparedHolder.current = prepared;
5735
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() };
@@ -3888,6 +3894,9 @@ export class Runner {
3888
3894
  if (threw === undefined && prepared.brainCallGuardrailRef.timedOut !== undefined) {
3889
3895
  threw = prepared.brainCallGuardrailRef.timedOut;
3890
3896
  }
3897
+ if (threw === undefined && prepared.gateStopRef.terminal !== undefined) {
3898
+ threw = prepared.gateStopRef.terminal;
3899
+ }
3891
3900
  if (rs.limits.platformTerminal !== undefined) {
3892
3901
  threw = rs.limits.platformTerminal;
3893
3902
  }
@@ -4071,6 +4080,7 @@ export class Runner {
4071
4080
  model: prepared.model.id,
4072
4081
  unpricedSpend: rs.telemetry.unpricedSpend,
4073
4082
  rewindNotes: prepared.rewindNotes,
4083
+ editedFiles: prepared.editedFilesSnapshot(),
4074
4084
  haltedOnUserRejection: prepared.batchHaltRef.current !== undefined,
4075
4085
  userHalted: loopLatch.userHalted,
4076
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;