@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.
- package/CHANGELOG.md +67 -0
- package/dist/agents/agent-transcript-tool.d.ts +2 -2
- package/dist/agents/cascade.d.ts +4 -5
- package/dist/agents/cascade.js +12 -10
- package/dist/agents/repair-loop.d.ts +7 -5
- package/dist/agents/repair-loop.js +13 -15
- package/dist/agents/retain-ledger.d.ts +2 -3
- package/dist/agents/send-message-tool.d.ts +2 -2
- package/dist/agents/session-util.d.ts +2 -2
- package/dist/agents/subagent.d.ts +27 -46
- package/dist/agents/subagent.js +119 -105
- package/dist/agents/suspend-guard.d.ts +31 -19
- package/dist/agents/suspend-guard.js +14 -8
- package/dist/agents/teacher.d.ts +2 -2
- package/dist/agents/teacher.js +9 -9
- package/dist/agents/team.d.ts +6 -5
- package/dist/agents/team.js +10 -8
- package/dist/agents/verify.d.ts +8 -9
- package/dist/agents/verify.js +17 -17
- package/dist/core/a2a.js +2 -1
- package/dist/core/agent-definition.d.ts +172 -0
- package/dist/core/agent-definition.js +1 -0
- package/dist/core/ask-origin.d.ts +60 -7
- package/dist/core/ask-origin.js +26 -1
- package/dist/core/checkpoint-store.d.ts +78 -76
- package/dist/core/checkpoint-store.js +17 -1
- package/dist/core/delegation-frames.d.ts +298 -0
- package/dist/core/delegation-frames.js +21 -0
- package/dist/core/engine-notice.d.ts +555 -0
- package/dist/core/engine-notice.js +55 -0
- package/dist/core/gate-fold.d.ts +12 -0
- package/dist/core/gate-fold.js +158 -0
- package/dist/core/gate-lanes.d.ts +93 -0
- package/dist/core/gate-lanes.js +626 -0
- package/dist/core/gate-outcome.d.ts +189 -0
- package/dist/core/gate-outcome.js +70 -0
- package/dist/core/hands-band.d.ts +134 -0
- package/dist/core/hands-band.js +1 -0
- package/dist/core/hooks.d.ts +22 -177
- package/dist/core/hooks.js +53 -851
- package/dist/core/mcp-failure.d.ts +142 -0
- package/dist/core/mcp-failure.js +145 -0
- package/dist/core/mcp-server-spec.d.ts +217 -0
- package/dist/core/mcp-server-spec.js +1 -0
- package/dist/core/mcp.d.ts +21 -77
- package/dist/core/mcp.js +76 -150
- package/dist/core/model-seat.d.ts +99 -0
- package/dist/core/model-seat.js +1 -0
- package/dist/core/pause-registry.d.ts +131 -0
- package/dist/core/pause-registry.js +27 -0
- package/dist/core/reminder-mint.d.ts +10 -0
- package/dist/core/reminder-mint.js +3 -0
- package/dist/core/runner/assemble-result.d.ts +32 -41
- package/dist/core/runner/assemble-result.js +55 -74
- package/dist/core/runner/contracts.d.ts +427 -69
- package/dist/core/runner/denial-limit-arms.d.ts +1 -1
- package/dist/core/runner/denial-limit-arms.js +3 -3
- package/dist/core/runner/gate-exit.d.ts +242 -0
- package/dist/core/runner/gate-exit.js +124 -0
- package/dist/core/runner/park-commit.d.ts +17 -23
- package/dist/core/runner/park-commit.js +14 -15
- package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
- package/dist/core/runner/prepare-ask-lane.js +3 -5
- package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
- package/dist/core/runner/prepare-boundary-parks.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
- package/dist/core/runner/prepare-gate-stations.js +29 -54
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +44 -26
- package/dist/core/runner/prepare-park-ask.d.ts +2 -4
- package/dist/core/runner/prepare-park-ask.js +5 -5
- package/dist/core/runner/prepare-task.d.ts +2 -2
- package/dist/core/runner/prepare-task.js +8 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
- package/dist/core/runner/prepare-wiring-manifest.js +9 -10
- package/dist/core/runner/runtask.d.ts +20 -102
- package/dist/core/runner/runtask.js +119 -121
- package/dist/core/runner/terminal-projection.d.ts +22 -0
- package/dist/core/runner/terminal-projection.js +28 -0
- package/dist/core/runner-deps.d.ts +1416 -0
- package/dist/core/runner-deps.js +1 -0
- package/dist/core/runtime-caps.d.ts +164 -0
- package/dist/core/runtime-caps.js +1 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
- package/dist/core/task-event.d.ts +910 -0
- package/dist/core/task-event.js +1 -0
- package/dist/core/task-limits.d.ts +110 -0
- package/dist/core/task-limits.js +1 -0
- package/dist/core/task-result.d.ts +809 -0
- package/dist/core/task-result.js +1 -0
- package/dist/core/task-spec.d.ts +1370 -0
- package/dist/core/task-spec.js +1 -0
- package/dist/core/task-stream.d.ts +382 -0
- package/dist/core/task-stream.js +1 -0
- package/dist/core/terminal-cause.d.ts +137 -0
- package/dist/core/terminal-cause.js +9 -0
- package/dist/core/tool-policy.d.ts +43 -139
- package/dist/core/tool-policy.js +79 -112
- package/dist/core/tool-spec.d.ts +1174 -0
- package/dist/core/tool-spec.js +1 -0
- package/dist/core/types.d.ts +27 -7789
- package/dist/core/types.js +2 -76
- package/dist/core/warm-resume.d.ts +2 -2
- package/dist/core/wiring-manifest.d.ts +6 -3
- package/dist/core/workflow-journal-store.js +3 -4
- package/dist/engine/harness/agent-harness.d.ts +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.js +9 -6
- package/dist/orchestration/builtin-workflows.d.ts +2 -2
- package/dist/orchestration/builtin-workflows.js +1 -1
- package/dist/orchestration/goal.d.ts +2 -2
- package/dist/orchestration/goal.js +8 -7
- package/dist/orchestration/run-spec.d.ts +2 -2
- package/dist/orchestration/run-spec.js +5 -3
- package/dist/orchestration/run-workflow-tool.d.ts +4 -4
- package/dist/orchestration/run-workflow-tool.js +4 -4
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-governance.js +4 -2
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +1 -1
- package/dist/orchestration/workflow.d.ts +15 -4
- package/dist/orchestration/workflow.js +64 -39
- package/dist/prompts/supervisor.d.ts +1 -1
- package/dist/prompts/supervisor.js +3 -3
- package/dist/scenarios/scenario-registry.d.ts +3 -3
- package/dist/scenarios/scenario-registry.js +1 -1
- package/dist/scenarios/teacher-quickstart.d.ts +2 -2
- package/dist/server/http.d.ts +2 -2
- package/dist/stores/file/fs-atomic.d.ts +88 -12
- package/dist/stores/file/fs-atomic.js +184 -55
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +1 -0
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +82 -22
package/dist/core/hooks.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
4
|
-
import { denialLimitFallbackMessage, unarmedWindow } from "./auto-mode.js";
|
|
8
|
+
import { isAskOrigin } from "./ask-origin.js";
|
|
5
9
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
317
|
+
const seat = await screening.screen(pass.currentInput);
|
|
488
318
|
if (seat.expired)
|
|
489
319
|
unanswered = seat.cause;
|
|
490
320
|
else
|
|
491
|
-
r =
|
|
321
|
+
r = screening.screened(seat.value);
|
|
492
322
|
}
|
|
493
323
|
catch (err) {
|
|
494
|
-
const reason =
|
|
324
|
+
const reason = screening.crashReason(`this call to "${toolName}"`, err);
|
|
495
325
|
traceHookCrash(input, err, notifier);
|
|
496
|
-
await
|
|
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
|
|
501
|
-
await
|
|
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
|
-
|
|
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
|
}
|