@sema-agent/core 7.5.2 → 7.6.1

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 (138) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +4 -5
  4. package/dist/agents/cascade.js +12 -10
  5. package/dist/agents/repair-loop.d.ts +7 -5
  6. package/dist/agents/repair-loop.js +13 -15
  7. package/dist/agents/retain-ledger.d.ts +2 -3
  8. package/dist/agents/send-message-tool.d.ts +2 -2
  9. package/dist/agents/session-util.d.ts +2 -2
  10. package/dist/agents/subagent.d.ts +27 -46
  11. package/dist/agents/subagent.js +119 -105
  12. package/dist/agents/suspend-guard.d.ts +31 -19
  13. package/dist/agents/suspend-guard.js +14 -8
  14. package/dist/agents/teacher.d.ts +2 -2
  15. package/dist/agents/teacher.js +9 -9
  16. package/dist/agents/team.d.ts +6 -5
  17. package/dist/agents/team.js +10 -8
  18. package/dist/agents/verify.d.ts +8 -9
  19. package/dist/agents/verify.js +17 -17
  20. package/dist/core/a2a.js +2 -1
  21. package/dist/core/agent-definition.d.ts +172 -0
  22. package/dist/core/agent-definition.js +1 -0
  23. package/dist/core/ask-origin.d.ts +60 -7
  24. package/dist/core/ask-origin.js +26 -1
  25. package/dist/core/checkpoint-store.d.ts +78 -76
  26. package/dist/core/checkpoint-store.js +17 -1
  27. package/dist/core/delegation-frames.d.ts +298 -0
  28. package/dist/core/delegation-frames.js +21 -0
  29. package/dist/core/engine-notice.d.ts +555 -0
  30. package/dist/core/engine-notice.js +55 -0
  31. package/dist/core/gate-fold.d.ts +12 -0
  32. package/dist/core/gate-fold.js +158 -0
  33. package/dist/core/gate-lanes.d.ts +93 -0
  34. package/dist/core/gate-lanes.js +626 -0
  35. package/dist/core/gate-outcome.d.ts +189 -0
  36. package/dist/core/gate-outcome.js +70 -0
  37. package/dist/core/hands-band.d.ts +134 -0
  38. package/dist/core/hands-band.js +1 -0
  39. package/dist/core/hooks.d.ts +22 -177
  40. package/dist/core/hooks.js +53 -851
  41. package/dist/core/mcp-failure.d.ts +142 -0
  42. package/dist/core/mcp-failure.js +145 -0
  43. package/dist/core/mcp-server-spec.d.ts +217 -0
  44. package/dist/core/mcp-server-spec.js +1 -0
  45. package/dist/core/mcp.d.ts +21 -77
  46. package/dist/core/mcp.js +76 -150
  47. package/dist/core/model-seat.d.ts +99 -0
  48. package/dist/core/model-seat.js +1 -0
  49. package/dist/core/pause-registry.d.ts +131 -0
  50. package/dist/core/pause-registry.js +27 -0
  51. package/dist/core/reminder-mint.d.ts +10 -0
  52. package/dist/core/reminder-mint.js +3 -0
  53. package/dist/core/runner/assemble-result.d.ts +32 -41
  54. package/dist/core/runner/assemble-result.js +55 -74
  55. package/dist/core/runner/contracts.d.ts +427 -69
  56. package/dist/core/runner/denial-limit-arms.d.ts +1 -1
  57. package/dist/core/runner/denial-limit-arms.js +3 -3
  58. package/dist/core/runner/gate-exit.d.ts +242 -0
  59. package/dist/core/runner/gate-exit.js +124 -0
  60. package/dist/core/runner/park-commit.d.ts +17 -23
  61. package/dist/core/runner/park-commit.js +14 -15
  62. package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
  63. package/dist/core/runner/prepare-ask-lane.js +3 -5
  64. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
  65. package/dist/core/runner/prepare-boundary-parks.js +3 -3
  66. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  67. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  68. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  69. package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
  70. package/dist/core/runner/prepare-gate-stations.js +29 -54
  71. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  72. package/dist/core/runner/prepare-memory.d.ts +44 -26
  73. package/dist/core/runner/prepare-park-ask.d.ts +2 -4
  74. package/dist/core/runner/prepare-park-ask.js +5 -5
  75. package/dist/core/runner/prepare-task.d.ts +2 -2
  76. package/dist/core/runner/prepare-task.js +8 -9
  77. package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
  78. package/dist/core/runner/prepare-wiring-manifest.js +9 -10
  79. package/dist/core/runner/runtask.d.ts +20 -102
  80. package/dist/core/runner/runtask.js +119 -121
  81. package/dist/core/runner/terminal-projection.d.ts +22 -0
  82. package/dist/core/runner/terminal-projection.js +28 -0
  83. package/dist/core/runner-deps.d.ts +1416 -0
  84. package/dist/core/runner-deps.js +1 -0
  85. package/dist/core/runtime-caps.d.ts +164 -0
  86. package/dist/core/runtime-caps.js +1 -0
  87. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  88. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  89. package/dist/core/task-event.d.ts +910 -0
  90. package/dist/core/task-event.js +1 -0
  91. package/dist/core/task-limits.d.ts +110 -0
  92. package/dist/core/task-limits.js +1 -0
  93. package/dist/core/task-result.d.ts +809 -0
  94. package/dist/core/task-result.js +1 -0
  95. package/dist/core/task-spec.d.ts +1370 -0
  96. package/dist/core/task-spec.js +1 -0
  97. package/dist/core/task-stream.d.ts +382 -0
  98. package/dist/core/task-stream.js +1 -0
  99. package/dist/core/terminal-cause.d.ts +137 -0
  100. package/dist/core/terminal-cause.js +9 -0
  101. package/dist/core/tool-policy.d.ts +43 -139
  102. package/dist/core/tool-policy.js +79 -112
  103. package/dist/core/tool-spec.d.ts +1174 -0
  104. package/dist/core/tool-spec.js +1 -0
  105. package/dist/core/types.d.ts +27 -7789
  106. package/dist/core/types.js +2 -76
  107. package/dist/core/warm-resume.d.ts +2 -2
  108. package/dist/core/wiring-manifest.d.ts +6 -3
  109. package/dist/core/workflow-journal-store.js +3 -4
  110. package/dist/engine/harness/agent-harness.d.ts +1 -1
  111. package/dist/index.d.ts +12 -8
  112. package/dist/index.js +9 -6
  113. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  114. package/dist/orchestration/builtin-workflows.js +1 -1
  115. package/dist/orchestration/goal.d.ts +2 -2
  116. package/dist/orchestration/goal.js +8 -7
  117. package/dist/orchestration/run-spec.d.ts +2 -2
  118. package/dist/orchestration/run-spec.js +5 -3
  119. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  120. package/dist/orchestration/run-workflow-tool.js +4 -4
  121. package/dist/orchestration/workflow-governance.d.ts +4 -4
  122. package/dist/orchestration/workflow-governance.js +4 -2
  123. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  124. package/dist/orchestration/workflow-primitives.js +1 -1
  125. package/dist/orchestration/workflow.d.ts +15 -4
  126. package/dist/orchestration/workflow.js +64 -39
  127. package/dist/prompts/supervisor.d.ts +1 -1
  128. package/dist/prompts/supervisor.js +3 -3
  129. package/dist/scenarios/scenario-registry.d.ts +3 -3
  130. package/dist/scenarios/scenario-registry.js +1 -1
  131. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  132. package/dist/server/http.d.ts +2 -2
  133. package/dist/stores/file/fs-atomic.d.ts +88 -12
  134. package/dist/stores/file/fs-atomic.js +184 -55
  135. package/dist/stores/file/index.d.ts +1 -0
  136. package/dist/stores/file/index.js +1 -0
  137. package/package.json +3 -1
  138. package/test/export-surface.snapshot.json +82 -22
@@ -1,13 +1,14 @@
1
- import { coreMintedAutoDeniedOf, coreMintedResolutionOf, decisionText, describeThrown, isAskDenyResolution, refuseOutOfContractDecision } from "./tool-policy.js";
1
+ import { decisionText, refuseOutOfContractDecision } from "./tool-policy.js";
2
+ import { createSettlementLedger, engineFailClosedExit, hookDenyExit, traceHookCrash } from "./runner/gate-exit.js";
3
+ import { runGateFold } from "./gate-fold.js";
4
+ import { runGateLanes } from "./gate-lanes.js";
5
+ export { normalizeOrgGateVerdict, normalizePersistedRuleHit, persistedRuleMandateOf } from "./gate-lanes.js";
6
+ export { cloneObserverInput } from "./runner/gate-exit.js";
2
7
  import { brandPolicyAskClass } from "./ask-class.js";
3
- import { askOriginOf, classifierMayAnswer, isAskOrigin } from "./ask-origin.js";
4
- import { denialLimitFallbackMessage, unarmedWindow } from "./auto-mode.js";
8
+ import { isAskOrigin } from "./ask-origin.js";
5
9
  import { inlineUntrusted } from "./untrusted-text.js";
6
- import { mintSystemReminder } from "./reminder-mint.js";
7
- import { PROBE_REASON_MAX, normalizeProbeCause } from "./checkpoint-store.js";
8
- import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
10
+ export { formatHookFeedback } from "./reminder-mint.js";
9
11
  import { createSafeNotifier } from "./safe-notify.js";
10
- import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
11
12
  export const DEFAULT_HOOK_TIMEOUT_MS = 600_000;
12
13
  export const MAX_HOOK_TIMEOUT_MS = 2_147_483_647;
13
14
  const POST_ABORT_GRACE_MS = 1_000;
@@ -94,18 +95,6 @@ export function hookSeatExpiredError(seat, timeoutMs, cause, consequence) {
94
95
  ? `the deployment's ${seat} hook did not answer within its ${timeoutMs}ms bound (Hooks.timeoutMs) — ${consequence}`
95
96
  : `the deployment's ${seat} hook had not answered when the task was cancelled — ${consequence}`);
96
97
  }
97
- export function cloneObserverInput(input) {
98
- try {
99
- return structuredClone(input);
100
- }
101
- catch {
102
- if (Array.isArray(input))
103
- return [...input];
104
- if (input !== null && typeof input === "object")
105
- return { ...input };
106
- return input;
107
- }
108
- }
109
98
  const reflectApply = Reflect.apply;
110
99
  const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
111
100
  const getPrototypeOf = Object.getPrototypeOf;
@@ -179,146 +168,6 @@ export function mintHookInvocationIdentity(facts) {
179
168
  ...(facts.parentToolCallId !== undefined ? { parentToolCallId: facts.parentToolCallId } : {}),
180
169
  }));
181
170
  }
182
- export function formatHookFeedback(text, mark) {
183
- return mintSystemReminder(text, mark);
184
- }
185
- const PARK_FAILURE_CAUSE_MAX = 600;
186
- function withParkFailureCause(reason, parkFailed) {
187
- if (parkFailed === undefined)
188
- return reason;
189
- return (`${reason} — note: a durable approval park was attempted for this call FIRST and could not be minted ` +
190
- `(${inlineUntrusted(parkFailed, PARK_FAILURE_CAUSE_MAX)}), so the refusal above is what the fallback had ` +
191
- `left to say, not the reason the call stopped.`);
192
- }
193
- function isPlainOwnRecord(x) {
194
- try {
195
- if (x === null || typeof x !== "object" || Array.isArray(x))
196
- return false;
197
- const p = Object.getPrototypeOf(x);
198
- return p === Object.prototype || p === null;
199
- }
200
- catch {
201
- return false;
202
- }
203
- }
204
- function ownDataValue(obj, key) {
205
- try {
206
- const d = Object.getOwnPropertyDescriptor(obj, key);
207
- return d && "value" in d ? d.value : undefined;
208
- }
209
- catch {
210
- return undefined;
211
- }
212
- }
213
- export function normalizeOrgGateVerdict(answer, unreadable) {
214
- const clean = (v) => Object.assign(Object.create(null), v);
215
- const readable = (x) => {
216
- try {
217
- return x !== null && (typeof x === "object" || typeof x === "function") && !Array.isArray(x);
218
- }
219
- catch {
220
- return false;
221
- }
222
- };
223
- if (!readable(answer))
224
- return clean({ status: "unavailable", disclosures: [unreadable] });
225
- const status = ownDataValue(answer, "status");
226
- if (status === "unavailable") {
227
- const disclosures = ownDataValue(answer, "disclosures");
228
- let lines = [];
229
- if (Array.isArray(disclosures)) {
230
- try {
231
- for (let i = 0; i < disclosures.length; i++) {
232
- const d = disclosures[i];
233
- if (typeof d === "string")
234
- lines.push(d);
235
- }
236
- }
237
- catch {
238
- lines = [];
239
- }
240
- }
241
- return clean({ status: "unavailable", disclosures: lines.length > 0 ? lines : [unreadable] });
242
- }
243
- if (status !== "available")
244
- return clean({ status: "unavailable", disclosures: [unreadable] });
245
- const revision = ownDataValue(answer, "revision");
246
- const revisionCell = typeof revision === "number" && Number.isFinite(revision) ? { revision } : {};
247
- const verdict = ownDataValue(answer, "verdict");
248
- if (verdict === undefined)
249
- return clean({ status: "available", ...revisionCell });
250
- if (!readable(verdict))
251
- return clean({ status: "unavailable", disclosures: [unreadable] });
252
- const behavior = ownDataValue(verdict, "behavior");
253
- const rule = ownDataValue(verdict, "rule");
254
- if ((behavior !== "deny" && behavior !== "ask") || typeof rule !== "string")
255
- return clean({ status: "unavailable", disclosures: [unreadable] });
256
- return clean({ status: "available", verdict: clean({ behavior: behavior, rule }), ...revisionCell });
257
- }
258
- function copiedUsableDots(dots) {
259
- let copied;
260
- try {
261
- if (Array.isArray(dots)) {
262
- const walked = [];
263
- for (let i = 0; i < dots.length; i++) {
264
- const d = dots[i];
265
- walked.push(isPlainOwnRecord(d) ? { actor: ownDataValue(d, "actor"), counter: ownDataValue(d, "counter") } : { actor: undefined, counter: undefined });
266
- }
267
- copied = walked;
268
- }
269
- }
270
- catch {
271
- copied = undefined;
272
- }
273
- const usable = copied?.every((d) => typeof d.actor === "string" && typeof d.counter === "number" && Number.isFinite(d.counter)) === true && copied.length > 0;
274
- return usable ? { dots: copied } : {};
275
- }
276
- export function normalizePersistedRuleHit(hit) {
277
- if (typeof hit === "string")
278
- return { hit: { rules: [{ rule: hit }] } };
279
- if (!isPlainOwnRecord(hit))
280
- return {};
281
- if (ownDataValue(hit, "unreadable") === true)
282
- return { unreadable: true };
283
- const rulesRaw = ownDataValue(hit, "rules");
284
- if (rulesRaw === undefined) {
285
- const coverageRaw = ownDataValue(hit, "segmentCoverage");
286
- let rows;
287
- try {
288
- if (Array.isArray(coverageRaw)) {
289
- const walked = [];
290
- for (let i = 0; i < coverageRaw.length; i++) {
291
- const r = coverageRaw[i];
292
- walked.push(isPlainOwnRecord(r) ? { segment: ownDataValue(r, "segment"), covered: ownDataValue(r, "covered") } : { segment: undefined, covered: undefined });
293
- }
294
- rows = walked.every((r) => typeof r.segment === "string" && typeof r.covered === "boolean") ? walked : undefined;
295
- }
296
- }
297
- catch {
298
- rows = undefined;
299
- }
300
- return rows !== undefined && rows.length > 0 ? { coverage: rows.map((r) => ({ segment: r.segment, covered: r.covered })) } : {};
301
- }
302
- let members;
303
- try {
304
- if (Array.isArray(rulesRaw)) {
305
- const walked = [];
306
- for (let i = 0; i < rulesRaw.length; i++) {
307
- const m = rulesRaw[i];
308
- walked.push(isPlainOwnRecord(m) ? { rule: ownDataValue(m, "rule"), dots: ownDataValue(m, "dots") } : { rule: undefined, dots: undefined });
309
- }
310
- members = walked;
311
- }
312
- }
313
- catch {
314
- members = undefined;
315
- }
316
- if (members === undefined || members.length === 0)
317
- return {};
318
- if (!members.every((m) => typeof m.rule === "string"))
319
- return {};
320
- return { hit: { rules: members.map((m) => ({ rule: m.rule, ...copiedUsableDots(m.dots) })) } };
321
- }
322
171
  function preToolUseCrashReason(subject, err) {
323
172
  const raw = err instanceof Error ? err.message.trim() || err.name : String(err);
324
173
  const cause = inlineUntrusted(raw, 200);
@@ -382,49 +231,6 @@ export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash, timeo
382
231
  },
383
232
  }, "hook");
384
233
  }
385
- function traceHookCrash(input, err, notifier) {
386
- notifier.notify(() => input.onHookError?.(err), "toolGate.onHookError");
387
- }
388
- function withProbeTimeout(p, ms, signal) {
389
- if (ms === undefined && signal === undefined)
390
- return p;
391
- return new Promise((resolve, reject) => {
392
- let settled = false;
393
- const done = (fn, v) => {
394
- if (settled)
395
- return;
396
- settled = true;
397
- if (timer !== undefined)
398
- clearTimeout(timer);
399
- signal?.removeEventListener("abort", onAbort);
400
- fn(v);
401
- };
402
- const onAbort = () => done(reject, new Error("reversibilityProbe aborted (task ended)"));
403
- const timer = ms !== undefined
404
- ? setTimeout(() => done(reject, new Error(`reversibilityProbe timed out after ${ms}ms`)), ms)
405
- : undefined;
406
- if (signal?.aborted) {
407
- onAbort();
408
- return;
409
- }
410
- signal?.addEventListener("abort", onAbort);
411
- p.then((v) => done(resolve, v), (err) => done(reject, err));
412
- });
413
- }
414
- const DEFAULT_PROBE_TIMEOUT_MS = 30_000;
415
- export function persistedRuleMandateOf(marks) {
416
- return marks.probeMandated === true
417
- ? "probe_mandate"
418
- : marks.egress === true
419
- ? "tool_marks"
420
- : marks.shellGated === true
421
- ? marks.irreversibility === "always"
422
- ? "operator_always"
423
- : undefined
424
- : marks.irreversibility === "always" || marks.irreversibility === "maybe"
425
- ? "tool_marks"
426
- : undefined;
427
- }
428
234
  export function askCarryRowMembers(carry) {
429
235
  if (carry === undefined)
430
236
  return {};
@@ -433,9 +239,6 @@ export function askCarryRowMembers(carry) {
433
239
  ...(isAskOrigin(carry.origin) ? { origin: carry.origin } : {}),
434
240
  };
435
241
  }
436
- function parkWith(suspendAsk, parkArgs, carry) {
437
- return suspendAsk(...parkArgs, carry);
438
- }
439
242
  export async function runToolGate(input) {
440
243
  const { event, preToolUse, adjudicate, resolveAsk, suspendAsk } = input;
441
244
  const { toolCallId, toolName } = event;
@@ -451,12 +254,7 @@ export async function runToolGate(input) {
451
254
  ...(input.hookEnv !== undefined ? { env: input.hookEnv } : {}),
452
255
  ...(input.identity !== undefined ? { identity: input.identity } : {}),
453
256
  });
454
- let currentInput = event.input;
455
- const preToolContext = [];
456
- let hookAsk;
457
- let parkFailed;
458
- let askDenyResolution;
459
- let askAutoDenied = false;
257
+ const modelInput = event.input;
460
258
  const notifier = createSafeNotifier(input.onNotifyError !== undefined ? { onError: input.onNotifyError } : undefined);
461
259
  const hookSeatMs = resolveHookTimeoutMs(input.hookTimeoutMs, (err) => traceHookCrash(input, err, notifier));
462
260
  const seatBound = (abortEnds) => ({
@@ -480,667 +278,71 @@ export async function runToolGate(input) {
480
278
  throw hookSeatExpiredError("permissionDenied", hookSeatMs, seat.cause, "the deny observation was abandoned; the deny itself is unchanged");
481
279
  }, "toolGate.permissionDenied");
482
280
  };
483
- if (preToolUse) {
281
+ const screening = preToolUse !== undefined
282
+ ? {
283
+ screen: (args) => runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(toolName, args, hookCtx(sig))),
284
+ screened: screenPreToolUseResult,
285
+ crashReason: preToolUseCrashReason,
286
+ unansweredReason: preToolUseUnansweredBlock,
287
+ }
288
+ : undefined;
289
+ const pass = {
290
+ input,
291
+ event,
292
+ adjudicate,
293
+ resolveAsk,
294
+ suspendAsk,
295
+ toolName,
296
+ toolCallId,
297
+ callSignal,
298
+ notifier,
299
+ ledger: createSettlementLedger({ toolCallId, toolName }),
300
+ notifyPermissionDenied: notifyPermissionDeniedSeat,
301
+ screening,
302
+ afterFold: runGateLanes,
303
+ currentInput: modelInput,
304
+ req: { toolName, args: modelInput, toolCallId },
305
+ preToolContext: [],
306
+ hookAsk: undefined,
307
+ parkFailed: undefined,
308
+ deniedBy: undefined,
309
+ decision: { action: "allow" },
310
+ tightenedBy: undefined,
311
+ policyRewrite: undefined,
312
+ };
313
+ if (screening !== undefined) {
484
314
  let r;
485
315
  let unanswered;
486
316
  try {
487
- const seat = await runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(toolName, currentInput, hookCtx(sig)));
317
+ const seat = await screening.screen(pass.currentInput);
488
318
  if (seat.expired)
489
319
  unanswered = seat.cause;
490
320
  else
491
- r = screenPreToolUseResult(seat.value);
321
+ r = screening.screened(seat.value);
492
322
  }
493
323
  catch (err) {
494
- const reason = preToolUseCrashReason(`this call to "${toolName}"`, err);
324
+ const reason = screening.crashReason(`this call to "${toolName}"`, err);
495
325
  traceHookCrash(input, err, notifier);
496
- await notifyPermissionDeniedSeat({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason, source: "hook", ...(input.identity !== undefined ? { identity: input.identity } : {}) });
497
- return { block: true, reason: formatHookFeedback(reason, input.reminderMark), preToolContext };
326
+ return await engineFailClosedExit(pass, reason);
498
327
  }
499
328
  if (unanswered !== undefined) {
500
- const reason = await preToolUseUnansweredBlock(`this call to "${toolName}"`, unanswered);
501
- await notifyPermissionDeniedSeat({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason, source: "hook", ...(input.identity !== undefined ? { identity: input.identity } : {}) });
502
- return { block: true, reason: formatHookFeedback(reason, input.reminderMark), preToolContext };
329
+ const reason = await screening.unansweredReason(`this call to "${toolName}"`, unanswered);
330
+ return await engineFailClosedExit(pass, reason);
503
331
  }
504
332
  if (r) {
505
333
  if (r.additionalContext) {
506
- preToolContext.push(r.additionalContext);
334
+ pass.preToolContext.push(r.additionalContext);
507
335
  }
508
336
  if (r.action === "deny") {
509
- return {
510
- block: true,
511
- reason: formatHookFeedback(decisionText(r) ?? `tool "${toolName}" blocked by a PreToolUse hook`, input.reminderMark),
512
- preToolContext,
513
- };
337
+ return hookDenyExit(pass, r);
514
338
  }
515
339
  if (r.updatedInput !== undefined) {
516
- currentInput = r.updatedInput;
340
+ pass.currentInput = r.updatedInput;
517
341
  }
518
342
  if (r.action === "ask") {
519
- hookAsk = r;
520
- }
521
- }
522
- }
523
- const req = { toolName, args: currentInput, toolCallId };
524
- let decision = adjudicate ? await (callSignal !== undefined ? adjudicate(req, callSignal) : adjudicate(req)) : { action: "allow" };
525
- let denySource = "policy";
526
- let policyRewrite;
527
- if ((decision.action === "allow" || decision.action === "ask") && decision.updatedInput !== undefined) {
528
- policyRewrite = decision.updatedInput;
529
- }
530
- else if (hookAsk?.updatedInput !== undefined) {
531
- policyRewrite = hookAsk.updatedInput;
532
- }
533
- if (decision.action === "allow" && hookAsk) {
534
- decision = {
535
- action: "ask",
536
- ...(hookAsk.message !== undefined ? { message: hookAsk.message } : {}),
537
- decisionReason: "hook",
538
- ...(hookAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
539
- ...(hookAsk.updatedInput !== undefined ? { updatedInput: hookAsk.updatedInput } : {}),
540
- };
541
- denySource = "hook";
542
- }
543
- else if (decision.action === "ask" && hookAsk) {
544
- const hookMessage = hookAsk.message;
545
- const foldedMessage = hookMessage !== undefined && hookMessage !== decision.message
546
- ? `${decision.message !== undefined ? `${decision.message} ` : ""}(a PreToolUse hook also asks: ${hookMessage})`
547
- : decision.message;
548
- decision = {
549
- ...decision,
550
- decisionReason: "hook",
551
- ...(foldedMessage !== undefined ? { message: foldedMessage } : {}),
552
- ...(hookAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
553
- };
554
- }
555
- if (input.egress && decision.action === "allow") {
556
- decision = {
557
- action: "ask",
558
- message: `tool "${toolName}" performs an external write (egress) — explicit approval required`,
559
- decisionReason: "safety",
560
- };
561
- denySource = "safety";
562
- }
563
- const alreadyAsking = decision.action === "ask";
564
- if ((decision.action === "allow" || alreadyAsking) && (input.irreversibility === "always" || input.irreversibility === "maybe")) {
565
- let tighten = !alreadyAsking;
566
- let probeReason;
567
- let probeCause;
568
- let probeMandated;
569
- if (input.irreversibility === "maybe" && input.reversibilityProbe) {
570
- let reversible = false;
571
- const probeArgs = policyRewrite !== undefined ? policyRewrite : currentInput;
572
- const suppliedProbeMs = input.approvalTimeoutMs;
573
- let probeTimeoutMs;
574
- if (suppliedProbeMs === undefined) {
575
- probeTimeoutMs = DEFAULT_PROBE_TIMEOUT_MS;
576
- }
577
- else if (Number.isFinite(suppliedProbeMs) && suppliedProbeMs >= 0 && suppliedProbeMs <= 2_147_483_647) {
578
- probeTimeoutMs = suppliedProbeMs;
343
+ pass.hookAsk = r;
579
344
  }
580
- else {
581
- probeTimeoutMs = DEFAULT_PROBE_TIMEOUT_MS;
582
- traceHookCrash(input, new Error(`approvalTimeoutMs must be a non-negative finite number no greater than 2147483647 (got ${String(suppliedProbeMs)}) — the reversibilityProbe deadline falls back to the ${DEFAULT_PROBE_TIMEOUT_MS}ms default`), notifier);
583
- }
584
- try {
585
- const verdict = await withProbeTimeout(Promise.resolve(input.reversibilityProbe(probeArgs)), probeTimeoutMs, input.abortSignal);
586
- reversible = verdict?.reversible === true;
587
- if (!reversible) {
588
- const raw = verdict?.reason;
589
- if (typeof raw === "string" && raw.trim().length > 0)
590
- probeReason = inlineUntrusted(raw, PROBE_REASON_MAX);
591
- probeCause = normalizeProbeCause(verdict?.cause);
592
- if (verdict?.mandated === true)
593
- probeMandated = true;
594
- }
595
- }
596
- catch (err) {
597
- reversible = false;
598
- if (input.abortSignal?.aborted !== true)
599
- traceHookCrash(input, err, notifier);
600
- }
601
- tighten = !alreadyAsking && !reversible;
602
- }
603
- if (tighten) {
604
- decision = {
605
- action: "ask",
606
- message: `tool "${toolName}" performs an irreversible action — explicit approval required`,
607
- decisionReason: "safety",
608
- ...(probeReason !== undefined ? { probeReason } : {}),
609
- ...(probeCause !== undefined ? { probeCause } : {}),
610
- ...(probeMandated === true ? { probeMandated: true } : {}),
611
- };
612
- denySource = input.shellGated === true ? "shellGate" : "safety";
613
- }
614
- else if (alreadyAsking && probeMandated === true && decision.action === "ask") {
615
- decision = { ...decision, probeMandated: true };
616
345
  }
617
346
  }
618
- const breakerKnownOpen = (d) => {
619
- try {
620
- return d.breakerOpen();
621
- }
622
- catch {
623
- return false;
624
- }
625
- };
626
- if (input.peerMessage === true && decision.action === "allow" && input.autoMode !== undefined && !breakerKnownOpen(input.autoMode.decider)) {
627
- decision = {
628
- action: "ask",
629
- message: `tool "${toolName}" sends a message to another agent — routed for classifier review in auto mode`,
630
- decisionReason: "safety",
631
- };
632
- denySource = "safety";
633
- }
634
- if (decision.action === "allow" && input.writeProtectionCheck !== undefined) {
635
- const writeProtectedHit = input.writeProtectionCheck(toolName, policyRewrite !== undefined ? policyRewrite : currentInput);
636
- if (writeProtectedHit !== null) {
637
- decision = {
638
- action: "ask",
639
- message: `tool "${toolName}" writes to a write-protected path (table entry "${writeProtectedHit.name}") — explicit approval required`,
640
- decisionReason: "safety",
641
- };
642
- denySource = "safety";
643
- }
644
- }
645
- let orgRealApprovalRequired = false;
646
- let orgAskOrigin;
647
- const askOriginFacts = () => ({
648
- contentQuestion: toolName === ASK_USER_QUESTION_TOOL_NAME,
649
- markedUnresolvable: input.isMarkedUnresolvable?.(input.event.toolCallId) === true,
650
- org: orgAskOrigin,
651
- });
652
- let orgTightenCount = 0;
653
- let resolvedApprover;
654
- let orgEvidence = (() => {
655
- const w = input.orgRules === undefined ? "not_wired" : "not_adjudicated";
656
- return { revisionAbsent: w, ruleAbsent: w };
657
- })();
658
- const mintRuleEvidence = (personal) => ({
659
- ...(orgEvidence.revision !== undefined ? { orgRevision: orgEvidence.revision } : { orgRevisionAbsent: orgEvidence.revisionAbsent ?? "not_reported" }),
660
- ...(orgEvidence.rule !== undefined ? { orgRule: orgEvidence.rule } : { orgRuleAbsent: orgEvidence.ruleAbsent ?? "not_reported" }),
661
- ...(personal.dots !== undefined ? { personalRuleDots: personal.dots } : { personalRuleDotsAbsent: personal.dotsAbsent ?? "not_reported" }),
662
- });
663
- const applyOrgLayer = async (current, args) => {
664
- if (input.orgRules === undefined)
665
- return current;
666
- if (toolName === ASK_USER_QUESTION_TOOL_NAME && input.orgRules.contentAskToolMounted === true)
667
- return current;
668
- let decided = current;
669
- let pending;
670
- try {
671
- pending = Promise.resolve(input.orgRules.adjudicate({ toolName, args, toolCallId }));
672
- }
673
- catch (err) {
674
- pending = Promise.resolve({ status: "unavailable", disclosures: [`the org adjudication face threw before answering: ${describeThrown(err)}`] });
675
- }
676
- const raw = await settleOrgVerdictWithin(pending.catch((err) => ({
677
- status: "unavailable",
678
- disclosures: [`the org adjudication face threw: ${describeThrown(err)}`],
679
- })), {
680
- status: "unavailable",
681
- disclosures: [`the org adjudication face did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms (or the task ended first)`],
682
- }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
683
- const answer = normalizeOrgGateVerdict(raw, "the org adjudication face answered something this gate cannot read as a verdict — treated as unreadable governance, never as an empty one");
684
- if (answer.status === "unavailable") {
685
- orgEvidence = { revisionAbsent: "unavailable", ruleAbsent: "unavailable" };
686
- orgRealApprovalRequired = true;
687
- orgAskOrigin = "unavailable";
688
- await notifier.notifyAsync(() => input.orgRules?.onUnavailable?.({ toolName, toolCallId, message: answer.disclosures.join("; ") }), "toolGate.orgSnapshotUnavailable");
689
- if (decided.action === "allow") {
690
- decided = {
691
- action: "ask",
692
- message: `tool "${toolName}" needs approval: this deployment is org-governed and cannot currently adjudicate ` +
693
- `against an organization policy snapshot — every allow tightens to a real approval until it can`,
694
- decisionReason: ORG_UNAVAILABLE_DECISION_REASON,
695
- requiresRealApproval: true,
696
- };
697
- orgTightenCount += 1;
698
- denySource = "org";
699
- }
700
- else if (decided.action === "ask" && decided.requiresRealApproval !== true) {
701
- decided = { ...decided, requiresRealApproval: true };
702
- orgTightenCount += 1;
703
- }
704
- return decided;
705
- }
706
- const revisionCell = answer.revision !== undefined ? { revision: answer.revision } : { revisionAbsent: "not_reported" };
707
- if (answer.verdict === undefined) {
708
- orgEvidence = { ...revisionCell, ruleAbsent: "no_match" };
709
- return decided;
710
- }
711
- const { behavior, rule } = answer.verdict;
712
- orgEvidence = { ...revisionCell, rule };
713
- if (behavior === "deny") {
714
- orgTightenCount += 1;
715
- denySource = "org";
716
- return { action: "deny", message: `an organization policy rule (${rule}) denies this call`, decisionReason: ORG_RULE_DECISION_REASON };
717
- }
718
- if (decided.action === "deny")
719
- return decided;
720
- orgTightenCount += 1;
721
- denySource = "org";
722
- orgRealApprovalRequired = true;
723
- orgAskOrigin = "rule";
724
- return decided.action === "ask"
725
- ? { ...decided, requiresRealApproval: true }
726
- : {
727
- action: "ask",
728
- message: `an organization policy rule (${rule}) requires approval for this call`,
729
- decisionReason: ORG_RULE_DECISION_REASON,
730
- requiresRealApproval: true,
731
- };
732
- };
733
- decision = await applyOrgLayer(decision, policyRewrite !== undefined ? policyRewrite : currentInput);
734
- if (decision.action === "ask") {
735
- if (policyRewrite !== undefined) {
736
- currentInput = policyRewrite;
737
- req.args = policyRewrite;
738
- }
739
- const persistedRuleMandate = persistedRuleMandateOf({
740
- egress: input.egress,
741
- shellGated: input.shellGated,
742
- irreversibility: input.irreversibility,
743
- probeMandated: decision.action === "ask" && decision.probeMandated === true,
744
- });
745
- let personalEvidence = { dotsAbsent: input.persistedRules === undefined ? "not_wired" : "not_adjudicated" };
746
- let laneCoverage;
747
- if (input.persistedRules &&
748
- !orgRealApprovalRequired &&
749
- decision.action === "ask" &&
750
- decision.requiresRealApproval !== true &&
751
- decision.decisionReason !== "hook" &&
752
- req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
753
- input.isMarkedUnresolvable?.(input.event.toolCallId) !== true) {
754
- const answer = await (async () => {
755
- let pendingHit;
756
- try {
757
- pendingHit = Promise.resolve(input.persistedRules?.admits(req));
758
- }
759
- catch {
760
- return { unreadable: true };
761
- }
762
- return await settleOrgVerdictWithin(pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true })), { unreadable: true }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
763
- })();
764
- const hitRules = answer.hit?.rules;
765
- laneCoverage = answer.coverage;
766
- personalEvidence = { dotsAbsent: answer.unreadable === true ? "unavailable" : "no_match" };
767
- const disclosedRuleSet = (rules) => {
768
- const shown = rules.slice(0, 5).map((r) => inlineUntrusted(r.rule, 200));
769
- const rest = rules.length - shown.length;
770
- return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
771
- };
772
- const unionDotsOf = (rules) => {
773
- const seen = new Set();
774
- const union = [];
775
- for (const r of rules) {
776
- if (r.dots === undefined || r.dots.length === 0)
777
- return { dotsAbsent: "not_reported" };
778
- for (const d of r.dots) {
779
- const key = JSON.stringify([d.actor, d.counter]);
780
- if (!seen.has(key)) {
781
- seen.add(key);
782
- union.push(d);
783
- }
784
- }
785
- }
786
- return { dots: union };
787
- };
788
- if (hitRules !== undefined && persistedRuleMandate === undefined && decision.matchedAskRule === undefined) {
789
- decision = {
790
- action: "allow",
791
- message: hitRules.length === 1
792
- ? `a persisted allow rule (${disclosedRuleSet(hitRules)}) covers this call`
793
- : `persisted allow rules (${disclosedRuleSet(hitRules)}) cover every segment of this call`,
794
- decisionReason: "persisted_rule",
795
- ...(policyRewrite !== undefined ? { updatedInput: policyRewrite } : {}),
796
- };
797
- await notifier.notifyAsync(() => input.persistedRules?.onResolved?.({ toolName: req.toolName, toolCallId, rules: hitRules.map((r) => r.rule) }), "toolGate.persistedRuleResolved");
798
- }
799
- else if (hitRules !== undefined) {
800
- personalEvidence = unionDotsOf(hitRules);
801
- const shownRule = disclosedRuleSet(hitRules);
802
- const mandateNoun = persistedRuleMandate === "probe_mandate"
803
- ? "the reversibility check declared this call structurally gated (the built-in shell check raises this for a read outside the directories allowed for this session) — it is cleared by confirming this call, never by a standing allow rule"
804
- : persistedRuleMandate === "operator_always"
805
- ? "this deployment mandates per-call confirmation for shell commands (shellGate: always)"
806
- : persistedRuleMandate !== undefined
807
- ? "this tool carries egress/irreversibility marks (a mandated confirmation a rule cannot clear)"
808
- : "an explicit ask rule matched this call (a person's ask-me-each-time outranks a standing allow rule)";
809
- decision = {
810
- ...decision,
811
- persistedRuleShadowed: shownRule,
812
- message: hitRules.length === 1
813
- ? `${decision.message !== undefined ? `${decision.message} ` : ""}(a persisted allow rule (${shownRule}) matches this call but does not clear the ask — ${mandateNoun})`
814
- : `${decision.message !== undefined ? `${decision.message} ` : ""}(persisted allow rules (${shownRule}) match this call but do not clear the ask — ${mandateNoun})`,
815
- };
816
- }
817
- }
818
- const originFacts = askOriginFacts();
819
- if (decision.action === "ask") {
820
- const stamped = { ...decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
821
- decision = { ...stamped, origin: askOriginOf(stamped, originFacts) };
822
- }
823
- if (input.autoMode && decision.action === "ask" && decision.origin !== undefined && classifierMayAnswer(decision.origin)) {
824
- const verdict = await input.autoMode.decider
825
- .decide({ req, askMessage: decisionText(decision) }, input.abortSignal)
826
- .catch(() => ({ kind: "unavailable", cause: "error" }));
827
- if (verdict.kind === "allow") {
828
- input.autoMode.denialTracking?.recordAllow();
829
- decision = {
830
- action: "allow",
831
- message: "auto-mode classifier allowed this call",
832
- decisionReason: "classifier",
833
- ...(policyRewrite !== undefined ? { updatedInput: policyRewrite } : {}),
834
- };
835
- }
836
- else if (verdict.kind === "block") {
837
- const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
838
- const category = verdict.category ? inlineUntrusted(verdict.category) : "";
839
- const tracked = input.autoMode.denialTracking?.recordBlock();
840
- if (tracked?.limitReached === true) {
841
- const fallbackAsk = {
842
- ...decision,
843
- message: denialLimitFallbackMessage(tracked.fallback, reason || category || toolName),
844
- decisionReason: "classifier",
845
- requiresRealApproval: true,
846
- denialLimitFallback: tracked.fallback,
847
- };
848
- decision = { ...fallbackAsk, origin: askOriginOf(fallbackAsk, originFacts) };
849
- denySource = "classifier";
850
- }
851
- else {
852
- decision = {
853
- action: "deny",
854
- message: `auto-mode classifier blocked this call${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
855
- decisionReason: "classifier",
856
- };
857
- denySource = "classifier";
858
- }
859
- }
860
- }
861
- if (input.sandboxAdmission !== undefined &&
862
- decision.action === "ask" &&
863
- req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
864
- input.isMarkedUnresolvable?.(input.event.toolCallId) !== true &&
865
- decision.requiresRealApproval !== true &&
866
- decision.decisionReason !== "hook" &&
867
- hookAsk === undefined &&
868
- input.egress !== true &&
869
- input.peerMessage !== true &&
870
- input.sandboxAdmission.boundaryCapable !== true) {
871
- const recorded = input.sandboxAdmission.askClassesOf(toolCallId);
872
- const admissible = recorded !== undefined && recorded.length > 0 && recorded.every((r) => r.cls === "sandbox_local");
873
- if (admissible) {
874
- const askClasses = recorded;
875
- input.sandboxAdmission.onAdmitted({ toolCallId, toolName, askClasses, boundary: "sandbox_internal" });
876
- preToolContext.push(`permission.sandbox_admitted: this call's pending approval was auto-admitted because the deployment declares an isolated sandbox execution env and the request stays inside it. Recorded for audit.`);
877
- decision = {
878
- action: "allow",
879
- message: "admitted inside the deployment-declared sandbox (permission.sandbox_admitted)",
880
- decisionReason: "sandbox",
881
- ...(policyRewrite !== undefined ? { updatedInput: policyRewrite } : {}),
882
- };
883
- }
884
- }
885
- const egressTool = input.egress === true;
886
- const irreversibleTool = input.irreversibility === "always" || input.irreversibility === "maybe";
887
- const safety = egressTool || irreversibleTool ? { egress: egressTool, irreversible: irreversibleTool } : undefined;
888
- const realApprovalOf = (d) => d.action === "ask" && d.requiresRealApproval === true
889
- ? { origin: orgAskOrigin !== undefined ? `org_${orgAskOrigin}` : "policy" }
890
- : undefined;
891
- const carryOf = (d) => ({
892
- ...(callSignal !== undefined ? { signal: callSignal } : {}),
893
- ...(d.action === "ask" && d.denialLimitFallback !== undefined ? { denialLimitFallback: unarmedWindow(d.denialLimitFallback) } : {}),
894
- ...(d.action === "ask" && d.origin !== undefined ? { origin: d.origin } : {}),
895
- });
896
- if (suspendAsk && decision.action === "ask") {
897
- const parkArgs = [req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined, decision.action === "ask" ? decision.probeMandated : undefined];
898
- const suspended = await parkWith(suspendAsk, parkArgs, carryOf(decision));
899
- if (suspended) {
900
- if ("parkFailed" in suspended)
901
- parkFailed = suspended.parkFailed;
902
- else
903
- return { suspend: suspended, preToolContext };
904
- }
905
- }
906
- if (decision.action === "ask" && req.toolName === ASK_USER_QUESTION_TOOL_NAME) {
907
- const outcome = input.resolveContentAsk !== undefined ? await (callSignal !== undefined ? input.resolveContentAsk(req, callSignal) : input.resolveContentAsk(req)) : { kind: "unavailable", parkDeclined: false };
908
- if (outcome.kind === "answered") {
909
- decision = { action: "allow", updatedInput: outcome.presentedInput };
910
- }
911
- else if (outcome.kind === "unavailable") {
912
- if (outcome.presentedInput !== undefined) {
913
- currentInput = outcome.presentedInput;
914
- req.args = outcome.presentedInput;
915
- }
916
- if (suspendAsk && outcome.parkDeclined && parkFailed === undefined) {
917
- const parkArgs = [req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined, decision.action === "ask" ? decision.probeMandated : undefined];
918
- const suspended = await parkWith(suspendAsk, parkArgs, carryOf(decision));
919
- if (suspended) {
920
- if ("parkFailed" in suspended)
921
- parkFailed = suspended.parkFailed;
922
- else
923
- return { suspend: suspended, preToolContext };
924
- }
925
- }
926
- decision = {
927
- action: "deny",
928
- message: "AskUserQuestion could not be parked for a durable human answer on this path (no checkpoint " +
929
- "facility, or the suspend failed) and a synchronous permission approver cannot carry an answer — " +
930
- "the question was NOT shown to anyone. Proceed with your best judgment and state the assumption " +
931
- "you made.",
932
- };
933
- }
934
- else {
935
- if (outcome.presentedInput !== undefined)
936
- decision = { action: "allow", updatedInput: outcome.presentedInput };
937
- else
938
- decision = { action: "allow" };
939
- }
940
- }
941
- if (decision.action === "ask") {
942
- const askBeforeResolve = decision;
943
- const resolved = await (callSignal !== undefined ? resolveAsk(decision, req, callSignal) : resolveAsk(decision, req));
944
- if (resolved.action !== "ask" && resolved.approver !== undefined)
945
- resolvedApprover = resolved.approver;
946
- if (resolved.action === "deny" && isAskDenyResolution(resolved.resolution))
947
- askDenyResolution = resolved.resolution;
948
- if (resolved.action === "deny" && resolved.autoDenied === true)
949
- askAutoDenied = true;
950
- decision = resolved;
951
- if (resolved.action === "deny" && resolved.approverUnavailable === true && suspendAsk && parkFailed === undefined) {
952
- const parkArgs = [req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.segmentCoverage : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.matchedAskRule : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeMandated : undefined];
953
- const suspended = await parkWith(suspendAsk, parkArgs, carryOf(askBeforeResolve));
954
- if (suspended) {
955
- if ("parkFailed" in suspended)
956
- parkFailed = suspended.parkFailed;
957
- else
958
- return { suspend: suspended, preToolContext };
959
- }
960
- }
961
- if (askBeforeResolve.action === "ask" && askBeforeResolve.denialLimitFallback !== undefined) {
962
- if (decision.action === "allow") {
963
- input.autoMode?.denialTracking?.recordAllow();
964
- }
965
- else if (askDenyResolution === "no_approver" ||
966
- askDenyResolution === "blanket_allow_refused" ||
967
- askDenyResolution === "approver_unavailable" ||
968
- resolved.approverUnavailable === true) {
969
- try {
970
- input.onHeadlessDenialLimit?.({ toolName, toolCallId, fallback: unarmedWindow(askBeforeResolve.denialLimitFallback) });
971
- }
972
- catch {
973
- }
974
- }
975
- }
976
- if (decision.action === "allow" && decision.updatedInput !== undefined) {
977
- let editArgs = decision.updatedInput;
978
- let editDenied;
979
- let editRewrittenSinceHuman = false;
980
- for (let round = 0;; round++) {
981
- if (round >= 3) {
982
- editDenied = {
983
- action: "deny",
984
- message: `the approval-edit chain for "${toolName}" exceeded the re-approval cap — denied fail-closed; re-submit the edited action directly`,
985
- };
986
- denySource = "policy";
987
- break;
988
- }
989
- if (preToolUse) {
990
- let hr;
991
- let hrUnanswered;
992
- try {
993
- const seat = await runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(toolName, editArgs, hookCtx(sig)));
994
- if (seat.expired)
995
- hrUnanswered = seat.cause;
996
- else
997
- hr = screenPreToolUseResult(seat.value);
998
- }
999
- catch (err) {
1000
- traceHookCrash(input, err, notifier);
1001
- editDenied = { action: "deny", message: preToolUseCrashReason(`the approved edit for "${toolName}"`, err) };
1002
- denySource = "hook";
1003
- break;
1004
- }
1005
- if (hrUnanswered !== undefined) {
1006
- editDenied = { action: "deny", message: await preToolUseUnansweredBlock(`the approved edit for "${toolName}"`, hrUnanswered) };
1007
- denySource = "hook";
1008
- break;
1009
- }
1010
- if (hr) {
1011
- if (hr.additionalContext)
1012
- preToolContext.push(hr.additionalContext);
1013
- if (hr.action === "deny") {
1014
- editDenied = { action: "deny", message: decisionText(hr) ?? `the approved edit for "${toolName}" was blocked by a PreToolUse hook` };
1015
- denySource = "hook";
1016
- break;
1017
- }
1018
- if (hr.action === "ask") {
1019
- editDenied = {
1020
- action: "deny",
1021
- message: `the approved edit for "${toolName}" raises a new PreToolUse approval requirement the original approval does not cover — denied fail-closed; re-submit the edited action directly`,
1022
- };
1023
- denySource = "hook";
1024
- break;
1025
- }
1026
- if (hr.updatedInput !== undefined) {
1027
- editArgs = hr.updatedInput;
1028
- editRewrittenSinceHuman = true;
1029
- }
1030
- }
1031
- }
1032
- if (!adjudicate && input.orgRules === undefined)
1033
- break;
1034
- let recheck = { action: "allow" };
1035
- if (adjudicate) {
1036
- try {
1037
- recheck = await (callSignal !== undefined ? adjudicate({ toolName, args: editArgs, toolCallId }, callSignal) : adjudicate({ toolName, args: editArgs, toolCallId }));
1038
- }
1039
- catch (err) {
1040
- recheck = {
1041
- action: "deny",
1042
- message: `policy re-check of the approved edit for "${toolName}" errored: ${err instanceof Error ? err.message : String(err)}`,
1043
- };
1044
- }
1045
- }
1046
- if (recheck.action === "deny") {
1047
- editDenied = recheck;
1048
- denySource = "policy";
1049
- break;
1050
- }
1051
- if (recheck.updatedInput !== undefined) {
1052
- editArgs = recheck.updatedInput;
1053
- editRewrittenSinceHuman = true;
1054
- }
1055
- const orgTightensBefore = orgTightenCount;
1056
- orgAskOrigin = undefined;
1057
- recheck = await applyOrgLayer(recheck, editArgs);
1058
- const orgRaisedThisRound = orgTightenCount > orgTightensBefore;
1059
- if (recheck.action === "deny") {
1060
- editDenied = recheck;
1061
- break;
1062
- }
1063
- if (recheck.action === "allow") {
1064
- if (recheck.updatedInput !== undefined) {
1065
- editArgs = recheck.updatedInput;
1066
- editRewrittenSinceHuman = true;
1067
- }
1068
- break;
1069
- }
1070
- if (recheck.updatedInput !== undefined) {
1071
- editArgs = recheck.updatedInput;
1072
- editRewrittenSinceHuman = true;
1073
- }
1074
- const editAskSnapshot = { ...recheck, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
1075
- const editAskDecision = { ...editAskSnapshot, origin: askOriginOf(editAskSnapshot, askOriginFacts()) };
1076
- const editAskReq = { toolName, args: editArgs, toolCallId };
1077
- const rr = await (callSignal !== undefined ? resolveAsk(editAskDecision, editAskReq, callSignal) : resolveAsk(editAskDecision, editAskReq));
1078
- resolvedApprover = rr.action !== "ask" ? rr.approver : undefined;
1079
- if (rr.action !== "allow") {
1080
- if (rr.action === "deny" && isAskDenyResolution(rr.resolution))
1081
- askDenyResolution = rr.resolution;
1082
- editDenied = rr;
1083
- if (!orgRaisedThisRound)
1084
- denySource = "policy";
1085
- break;
1086
- }
1087
- if (rr.updatedInput === undefined) {
1088
- if (rr.presentedInput !== undefined)
1089
- editArgs = rr.presentedInput;
1090
- editRewrittenSinceHuman = false;
1091
- break;
1092
- }
1093
- editArgs = rr.updatedInput;
1094
- editRewrittenSinceHuman = false;
1095
- }
1096
- if (editDenied === undefined && editRewrittenSinceHuman && input.writeProtectionCheck !== undefined) {
1097
- const editHit = input.writeProtectionCheck(toolName, editArgs);
1098
- if (editHit !== null) {
1099
- editDenied = {
1100
- action: "deny",
1101
- message: `the approved edit for "${toolName}" was rewritten by the restriction chain onto a write-protected path (table entry "${editHit.name}") that no approval covers — denied fail-closed; re-submit the edited action directly`,
1102
- };
1103
- denySource = "safety";
1104
- }
1105
- }
1106
- decision = editDenied ?? { ...decision, updatedInput: editArgs };
1107
- }
1108
- else if (decision.action === "allow" && decision.updatedInput === undefined && resolved.presentedInput !== undefined) {
1109
- decision = { ...decision, updatedInput: resolved.presentedInput };
1110
- }
1111
- }
1112
- }
1113
- if (decision.action === "deny") {
1114
- const denyReason = withParkFailureCause(decisionText(decision) ?? `tool "${toolName}" denied by policy`, parkFailed);
1115
- if (decision.updatedInput !== undefined) {
1116
- currentInput = decision.updatedInput;
1117
- }
1118
- const denyResolution = askDenyResolution ?? coreMintedResolutionOf(decision, { toolCallId, toolName });
1119
- const denyAutoDenied = askAutoDenied || coreMintedAutoDeniedOf(decision, { toolCallId, toolName });
1120
- await notifyPermissionDeniedSeat({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason: denyReason, source: denySource, ...(denyResolution !== undefined ? { resolution: denyResolution } : {}), ...(denyAutoDenied ? { autoDenied: true } : {}), ...(input.identity !== undefined ? { identity: input.identity } : {}) });
1121
- const denySettledBy = decision.settledBy;
1122
- const denyApprover = denySettledBy !== undefined ? resolvedApprover : undefined;
1123
- return {
1124
- block: true,
1125
- reason: formatHookFeedback(denyReason, input.reminderMark),
1126
- ...(denySettledBy !== undefined ? { settledBy: denySettledBy } : {}),
1127
- ...(denyResolution !== undefined ? { resolution: denyResolution } : {}),
1128
- ...(denyAutoDenied ? { autoDenied: true } : {}),
1129
- ...(denyApprover !== undefined ? { approver: denyApprover } : {}),
1130
- preToolContext,
1131
- };
1132
- }
1133
- if (decision.action === "allow") {
1134
- const rw = decision.updatedInput !== undefined ? decision.updatedInput : policyRewrite;
1135
- if (rw !== undefined)
1136
- currentInput = rw;
1137
- }
1138
- const allowSettledBy = decision.action === "allow" ? decision.settledBy : undefined;
1139
- const allowApprover = decision.action === "allow" && allowSettledBy !== undefined ? resolvedApprover : undefined;
1140
- return {
1141
- updatedInput: currentInput === event.input ? undefined : currentInput,
1142
- ...(allowSettledBy !== undefined ? { settledBy: allowSettledBy } : {}),
1143
- ...(allowApprover !== undefined ? { approver: allowApprover } : {}),
1144
- preToolContext,
1145
- };
347
+ return await runGateFold(pass);
1146
348
  }