@sema-agent/core 7.12.0 → 7.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +124 -0
- package/dist/agents/subagent.d.ts +4 -9
- package/dist/agents/subagent.js +16 -12
- package/dist/agents/teacher.js +1 -1
- package/dist/brain/reasoning.d.ts +23 -0
- package/dist/brain/reasoning.js +35 -4
- package/dist/brain/stream-engine.js +35 -17
- package/dist/brain/timeout.d.ts +25 -4
- package/dist/brain/timeout.js +1 -1
- package/dist/core/ask-origin.d.ts +83 -9
- package/dist/core/ask-origin.js +31 -5
- package/dist/core/auto-mode.d.ts +3 -3
- package/dist/core/checkpoint-seat.d.ts +18 -0
- package/dist/core/checkpoint-seat.js +6 -0
- package/dist/core/checkpoint-store.d.ts +9 -16
- package/dist/core/checkpoint-store.js +7 -4
- package/dist/core/engine-notice.d.ts +21 -1
- package/dist/core/env-knob-announce.d.ts +16 -0
- package/dist/core/env-knob-announce.js +14 -0
- package/dist/core/gate-fold.js +2 -0
- package/dist/core/gate-lanes.d.ts +0 -40
- package/dist/core/gate-lanes.js +24 -29
- package/dist/core/gate-outcome.d.ts +42 -3
- package/dist/core/gate-outcome.js +22 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +3 -1
- package/dist/core/hooks.d.ts +6 -17
- package/dist/core/hooks.js +7 -5
- package/dist/core/mcp.d.ts +3 -2
- package/dist/core/mcp.js +5 -11
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/runner/advertised-writable-dirs.d.ts +74 -0
- package/dist/core/runner/advertised-writable-dirs.js +48 -0
- package/dist/core/runner/assemble-result.d.ts +50 -1
- package/dist/core/runner/assemble-result.js +4 -1
- package/dist/core/runner/compaction-seams.d.ts +42 -0
- package/dist/core/runner/compaction-seams.js +80 -0
- package/dist/core/runner/contracts.d.ts +44 -6
- package/dist/core/runner/denial-limit-arms.d.ts +7 -9
- package/dist/core/runner/denial-limit-arms.js +11 -12
- package/dist/core/runner/gate-exit.d.ts +15 -3
- package/dist/core/runner/gate-exit.js +6 -4
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
- package/dist/core/runner/permission-rule-lanes.js +2 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +34 -4
- package/dist/core/runner/prepare-caps-and-workflow.js +47 -12
- package/dist/core/runner/prepare-gate-stations.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +10 -4
- package/dist/core/runner/prepare-hands-readface.js +5 -14
- package/dist/core/runner/prepare-inherited-gate.d.ts +5 -3
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +31 -34
- package/dist/core/runner/prepare-memory.js +73 -51
- package/dist/core/runner/prepare-policy-chain.js +8 -4
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -0
- package/dist/core/runner/prepare-prompt-inputs.js +2 -2
- package/dist/core/runner/prepare-protocol-tools.js +5 -0
- package/dist/core/runner/prepare-question-face.d.ts +10 -8
- package/dist/core/runner/prepare-question-face.js +1 -3
- package/dist/core/runner/prepare-safety-scan.js +11 -16
- package/dist/core/runner/prepare-task.d.ts +9 -1
- package/dist/core/runner/prepare-task.js +63 -49
- package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
- package/dist/core/runner/prepare-wiring-manifest.js +1 -1
- package/dist/core/runner/resume-claim.d.ts +2 -2
- package/dist/core/runner/resume-preflight.d.ts +2 -2
- package/dist/core/runner/run-attachment-seats.d.ts +2 -2
- package/dist/core/runner/run-compaction-machinery.js +2 -0
- package/dist/core/runner/run-git-lane.d.ts +1 -1
- package/dist/core/runner/run-harness-handlers.js +5 -1
- package/dist/core/runner/run-identity-wiring.d.ts +7 -0
- package/dist/core/runner/run-identity-wiring.js +3 -1
- package/dist/core/runner/run-leg.d.ts +7 -5
- package/dist/core/runner/run-leg.js +256 -5
- package/dist/core/runner/run-notification-lane.d.ts +4 -3
- package/dist/core/runner/run-recovery-lanes.d.ts +4 -17
- package/dist/core/runner/run-recovery-lanes.js +5 -4
- package/dist/core/runner/run-settle-and-teardown.d.ts +14 -10
- package/dist/core/runner/run-settle-and-teardown.js +112 -4
- package/dist/core/runner/run-stop-and-final-verify.d.ts +2 -2
- package/dist/core/runner/run-terminal-adoption.d.ts +16 -16
- package/dist/core/runner/run-terminal-adoption.js +87 -7
- package/dist/core/runner/run-turn-boundary.js +5 -4
- package/dist/core/runner/runtask.d.ts +10 -90
- package/dist/core/runner/runtask.js +77 -596
- package/dist/core/runner/stream-lifecycle-verbs.js +10 -1
- package/dist/core/runner/stream-settle-backstop.js +1 -1
- package/dist/core/runtime-caps.d.ts +21 -0
- package/dist/core/runtime-caps.js +5 -1
- package/dist/core/task-event.d.ts +11 -2
- package/dist/core/task-registry-shared.js +8 -6
- package/dist/core/task-result.d.ts +15 -0
- package/dist/core/task-stream.d.ts +10 -1
- package/dist/core/terminal-cause.d.ts +6 -2
- package/dist/core/tool-face.d.ts +8 -0
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +39 -31
- package/dist/core/tool-policy.js +29 -5
- package/dist/core/tool-registry.d.ts +11 -3
- package/dist/core/tool-registry.js +7 -1
- package/dist/core/tool-roster.d.ts +26 -0
- package/dist/core/tool-roster.js +40 -7
- package/dist/core/tool-spec.d.ts +13 -12
- package/dist/core/types.d.ts +2 -1
- package/dist/core/types.js +1 -0
- package/dist/core/wiring-manifest.d.ts +16 -9
- package/dist/core/wiring-manifest.js +8 -3
- package/dist/index.d.ts +8 -7
- package/dist/index.js +7 -6
- package/dist/orchestration/run-workflow-tool.d.ts +8 -8
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +4 -3
- package/dist/orchestration/workflow-primitives.js +3 -3
- package/dist/orchestration/workflow-types.d.ts +14 -0
- package/dist/orchestration/workflow.d.ts +41 -4
- package/dist/orchestration/workflow.js +23 -5
- package/dist/server/http.js +2 -1
- package/dist/tools/fs/fs-shared.js +9 -3
- package/dist/tools/fs/read-deny.d.ts +24 -8
- package/dist/tools/fs/read-deny.js +20 -1
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +35 -1
|
@@ -48,46 +48,6 @@ export declare function normalizePersistedRuleHit(hit: PersistedRuleAnswer): {
|
|
|
48
48
|
reason?: string;
|
|
49
49
|
coverage?: readonly import("./permission-rule-model.js").SegmentCoverage[];
|
|
50
50
|
};
|
|
51
|
-
/**
|
|
52
|
-
* The mandate provenance of one call, judged from the SAME mark inputs the gate is driven with —
|
|
53
|
-
* the single source for "could a persisted allow rule clear this ask?". Allow rules silence the
|
|
54
|
-
* classifier's questions, never a mandated one, and this predicate is the mandated-family half of
|
|
55
|
-
* that boundary (the real-approval/governance half rides the decision's own `requiresRealApproval`
|
|
56
|
-
* bit, which the org layer stamps):
|
|
57
|
-
* · `probeMandated` — this CALL's own reversibility probe declared its demotion STRUCTURAL
|
|
58
|
-
* (`"probe_mandate"`), judged FIRST because it is the only per-call member here: the three below
|
|
59
|
-
* are properties of the TOOL and are true of every call on the seat, so a mandate that is true of
|
|
60
|
-
* this one call must not be shadowed by the tier that happens to carry it (#502: the built-in
|
|
61
|
-
* shell probe raises it for a listed reader naming a path outside the session's roots — a boundary
|
|
62
|
-
* the deployment declared, which is exactly what the classify tier alone cannot say);
|
|
63
|
-
* · `egress` — the tool's own external-write mark, judged next: it is the tool's declaration even
|
|
64
|
-
* when the coarse doctrine also installed a shell tier on the same seat;
|
|
65
|
-
* · `shellGated` + tier `"always"` — the operator's per-call confirmation doctrine
|
|
66
|
-
* (`"operator_always"`); the classify doctrine installs `"maybe"`, and THOSE asks stay the rule
|
|
67
|
-
* lane's home turf (`undefined`) — that is the don't-ask-again main case, and the per-call member
|
|
68
|
-
* above is deliberately the ONLY thing that carves a mandate out of it;
|
|
69
|
-
* · a tool's OWN `"always"`/`"maybe"` irreversibility tier without the doctrine (`"tool_marks"`).
|
|
70
|
-
*
|
|
71
|
-
* Two consumers, one derivation: the gate's silencing arm (a matching rule is disclosed as shadowed
|
|
72
|
-
* instead of clearing the ask) and the runner's suggestion factory (a mandated ask offers no
|
|
73
|
-
* "stop asking me this" option — a rule minted from it would never clear it). A drift between the
|
|
74
|
-
* two would let a card offer a rule the lane then refuses to honor. The per-call member reaches both
|
|
75
|
-
* the same way every other per-call fact does: the gate stamps it on the surviving ask, the
|
|
76
|
-
* synchronous sites read it off the decision they spread, and the park leg threads its own parameter.
|
|
77
|
-
*/
|
|
78
|
-
export declare function persistedRuleMandateOf(marks: {
|
|
79
|
-
egress?: boolean;
|
|
80
|
-
shellGated?: boolean;
|
|
81
|
-
irreversibility?: "never" | "maybe" | "always";
|
|
82
|
-
/** #502: the surviving ask's engine-stamped `probeMandated` — see
|
|
83
|
-
* {@link import("./types.js").ReversibilityVerdict.mandated}. */
|
|
84
|
-
probeMandated?: boolean;
|
|
85
|
-
/** The pass's own record that the probe was consulted and did not answer (`GatePass.probeUnanswered`):
|
|
86
|
-
* the boundary is unknown, which no standing yes may stand in for. The runner's suggestion factory does
|
|
87
|
-
* not see the pass and never passes it — a card may offer a rule the lane then discloses as shadowed
|
|
88
|
-
* (loud, never a silent override), the same drift window the `matchedAskRule` conjunct already has. */
|
|
89
|
-
probeUnanswered?: boolean;
|
|
90
|
-
}): "operator_always" | "tool_marks" | "probe_mandate" | "probe_unanswered" | undefined;
|
|
91
51
|
/**
|
|
92
52
|
* Run the lanes over the pass: the org layer over the fold's verdict, then — for a surviving ask — the rewrite
|
|
93
53
|
* merge, the rule lane, the stamp, the classifier, the sandbox leg, the parks, the content lane, the resolution
|
package/dist/core/gate-lanes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { decisionText, describeThrown } from "./tool-policy.js";
|
|
2
|
-
import { askOriginOf, classifierMayAnswer } from "./ask-origin.js";
|
|
1
|
+
import { attestClassifierDenyCause, classifierDenyCauseOf, decisionText, describeThrown } from "./tool-policy.js";
|
|
2
|
+
import { askOriginOf, classifierMayAnswer, persistedRuleMandateOf, probeBoundaryMandate } from "./ask-origin.js";
|
|
3
3
|
import { CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, classifierUnavailableDenyMessage, denialLimitFallbackMessage, unarmedWindow } from "./auto-mode.js";
|
|
4
4
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
5
5
|
import { isRuleBehavior } from "./permission-rule-model.js";
|
|
@@ -144,21 +144,6 @@ export function normalizePersistedRuleHit(hit) {
|
|
|
144
144
|
return {};
|
|
145
145
|
return { hit: { behavior, rules: members.map((m) => ({ rule: m.rule, ...copiedUsableDots(m.dots) })) } };
|
|
146
146
|
}
|
|
147
|
-
export function persistedRuleMandateOf(marks) {
|
|
148
|
-
return marks.probeMandated === true
|
|
149
|
-
? "probe_mandate"
|
|
150
|
-
: marks.probeUnanswered === true
|
|
151
|
-
? "probe_unanswered"
|
|
152
|
-
: marks.egress === true
|
|
153
|
-
? "tool_marks"
|
|
154
|
-
: marks.shellGated === true
|
|
155
|
-
? marks.irreversibility === "always"
|
|
156
|
-
? "operator_always"
|
|
157
|
-
: undefined
|
|
158
|
-
: marks.irreversibility === "always" || marks.irreversibility === "maybe"
|
|
159
|
-
? "tool_marks"
|
|
160
|
-
: undefined;
|
|
161
|
-
}
|
|
162
147
|
function parkWith(suspendAsk, parkArgs, carry) {
|
|
163
148
|
return suspendAsk(...parkArgs, carry);
|
|
164
149
|
}
|
|
@@ -235,6 +220,7 @@ export async function runGateLanes(pass) {
|
|
|
235
220
|
if (behavior === "deny") {
|
|
236
221
|
orgTightenCount += 1;
|
|
237
222
|
pass.deniedBy = "org";
|
|
223
|
+
pass.denyCause = undefined;
|
|
238
224
|
return { decision: { action: "deny", message: `an organization policy rule (${rule}) denies this call`, decisionReason: ORG_RULE_DECISION_REASON }, org: undefined };
|
|
239
225
|
}
|
|
240
226
|
if (decided.action === "deny")
|
|
@@ -257,6 +243,7 @@ export async function runGateLanes(pass) {
|
|
|
257
243
|
};
|
|
258
244
|
const orgAnswer = await applyOrgLayer(pass.decision, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput);
|
|
259
245
|
pass.decision = orgAnswer.decision;
|
|
246
|
+
const ruleStoreUnreadableOf = (lane) => lane?.unreadable === true ? (lane.reason !== undefined ? "call" : "store") : undefined;
|
|
260
247
|
const readPersistedRules = async (req) => {
|
|
261
248
|
let pendingHit;
|
|
262
249
|
try {
|
|
@@ -274,6 +261,8 @@ export async function runGateLanes(pass) {
|
|
|
274
261
|
pass.decision = tightened.decision;
|
|
275
262
|
if (tightened.tightened === "deny")
|
|
276
263
|
pass.deniedBy = "persisted_rule";
|
|
264
|
+
if (tightened.tightened === "deny")
|
|
265
|
+
pass.denyCause = undefined;
|
|
277
266
|
}
|
|
278
267
|
if (pass.decision.action === "ask") {
|
|
279
268
|
pass.decision = { ...pass.decision };
|
|
@@ -353,9 +342,10 @@ export async function runGateLanes(pass) {
|
|
|
353
342
|
};
|
|
354
343
|
}
|
|
355
344
|
}
|
|
356
|
-
const originFacts = askOriginFacts(orgAnswer.org, laneAnswer
|
|
345
|
+
const originFacts = askOriginFacts(orgAnswer.org, ruleStoreUnreadableOf(laneAnswer));
|
|
357
346
|
if (pass.decision.action === "ask") {
|
|
358
|
-
const
|
|
347
|
+
const { ruleStoreUnreadable: _selfDeclared, ...unstamped } = pass.decision;
|
|
348
|
+
const stamped = { ...unstamped, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}), ...(originFacts.ruleStore !== undefined ? { ruleStoreUnreadable: originFacts.ruleStore } : {}) };
|
|
359
349
|
pass.decision = { ...stamped, origin: askOriginOf(stamped, originFacts) };
|
|
360
350
|
}
|
|
361
351
|
if (allowLayerMayClear() && pass.decision.action === "ask" && persistedRuleMandate === undefined && pass.decision.matchedAskRule === undefined && pass.req.toolName === COMMAND_RULE_TOOL) {
|
|
@@ -375,7 +365,7 @@ export async function runGateLanes(pass) {
|
|
|
375
365
|
}
|
|
376
366
|
if (pass.decision.action === "ask")
|
|
377
367
|
ledger.supersede();
|
|
378
|
-
if (input.autoMode && pass.decision.action === "ask" && pass.decision.origin !== undefined && classifierMayAnswer(pass.decision.origin)) {
|
|
368
|
+
if (input.autoMode && pass.decision.action === "ask" && pass.decision.origin !== undefined && classifierMayAnswer(pass.decision.origin) && !probeBoundaryMandate(persistedRuleMandate)) {
|
|
379
369
|
const verdict = await input.autoMode.decider
|
|
380
370
|
.decide({ req: pass.req, askMessage: decisionText(pass.decision) }, input.abortSignal)
|
|
381
371
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
@@ -389,12 +379,11 @@ export async function runGateLanes(pass) {
|
|
|
389
379
|
};
|
|
390
380
|
}
|
|
391
381
|
else if (verdict.kind === "unavailable") {
|
|
392
|
-
pass.decision = {
|
|
382
|
+
pass.decision = attestClassifierDenyCause({
|
|
393
383
|
action: "deny",
|
|
394
384
|
message: classifierUnavailableDenyMessage(toolName, verdict.cause),
|
|
395
385
|
decisionReason: "classifier",
|
|
396
|
-
|
|
397
|
-
};
|
|
386
|
+
}, "unavailable", pass.req);
|
|
398
387
|
}
|
|
399
388
|
else {
|
|
400
389
|
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE;
|
|
@@ -411,15 +400,18 @@ export async function runGateLanes(pass) {
|
|
|
411
400
|
pass.decision = { ...fallbackAsk, origin: askOriginOf(fallbackAsk, originFacts) };
|
|
412
401
|
}
|
|
413
402
|
else {
|
|
414
|
-
|
|
403
|
+
const denied = {
|
|
415
404
|
action: "deny",
|
|
416
405
|
message: verdict.kind === "block" ? `auto-mode classifier blocked this call${reason ? `: ${reason}` : category ? `: [${category}]` : ""}` : reason,
|
|
417
406
|
decisionReason: "classifier",
|
|
418
407
|
};
|
|
408
|
+
pass.decision = verdict.kind === "parse_error" ? attestClassifierDenyCause(denied, "parse_error", pass.req) : denied;
|
|
419
409
|
}
|
|
420
410
|
}
|
|
421
|
-
if (pass.decision.action === "deny")
|
|
411
|
+
if (pass.decision.action === "deny") {
|
|
422
412
|
pass.deniedBy = "classifier";
|
|
413
|
+
pass.denyCause = classifierDenyCauseOf(pass.decision, pass.req);
|
|
414
|
+
}
|
|
423
415
|
}
|
|
424
416
|
if (input.sandboxAdmission !== undefined &&
|
|
425
417
|
pass.decision.action === "ask" &&
|
|
@@ -431,6 +423,7 @@ export async function runGateLanes(pass) {
|
|
|
431
423
|
pass.hookAsk === undefined &&
|
|
432
424
|
input.egress !== true &&
|
|
433
425
|
input.peerMessage !== true &&
|
|
426
|
+
!probeBoundaryMandate(persistedRuleMandate) &&
|
|
434
427
|
input.sandboxAdmission.boundaryCapable !== true) {
|
|
435
428
|
const recorded = input.sandboxAdmission.askClassesOf(toolCallId);
|
|
436
429
|
const admissible = recorded !== undefined && recorded.length > 0 && recorded.every((r) => r.cls === "sandbox_local");
|
|
@@ -460,7 +453,7 @@ export async function runGateLanes(pass) {
|
|
|
460
453
|
...(callSignal !== undefined ? { signal: callSignal } : {}),
|
|
461
454
|
...(d.action === "ask" && d.denialLimitFallback !== undefined ? { denialLimitFallback: unarmedWindow(d.denialLimitFallback) } : {}),
|
|
462
455
|
...(d.action === "ask" && d.origin !== undefined ? { origin: d.origin } : {}),
|
|
463
|
-
...(d.action === "ask" && d.
|
|
456
|
+
...(d.action === "ask" && d.ruleStoreUnreadable !== undefined ? { ruleStoreUnreadable: d.ruleStoreUnreadable } : {}),
|
|
464
457
|
});
|
|
465
458
|
if (suspendAsk && pass.decision.action === "ask") {
|
|
466
459
|
const parkArgs = [pass.req, pass.currentInput, safety, undefined, realApprovalOf(pass.decision), pass.decision.action === "ask" ? pass.decision.persistedRuleShadowed : undefined, pass.decision.action === "ask" ? pass.decision.decisionReason : undefined, pass.decision.action === "ask" ? pass.decision.probeReason : undefined, pass.decision.action === "ask" ? pass.decision.probeCause : undefined, pass.decision.action === "ask" ? pass.decision.segmentCoverage : undefined, pass.decision.action === "ask" ? pass.decision.matchedAskRule : undefined, pass.decision.action === "ask" ? pass.decision.probeMandated : undefined];
|
|
@@ -628,6 +621,7 @@ export async function runGateLanes(pass) {
|
|
|
628
621
|
if (recheck.action === "deny" && refusedBy !== undefined) {
|
|
629
622
|
editDenied = recheck;
|
|
630
623
|
pass.deniedBy = refusedBy;
|
|
624
|
+
pass.denyCause = ledger.causeOf(recheck);
|
|
631
625
|
break;
|
|
632
626
|
}
|
|
633
627
|
}
|
|
@@ -644,7 +638,7 @@ export async function runGateLanes(pass) {
|
|
|
644
638
|
let editRuleStore;
|
|
645
639
|
if (input.persistedRules !== undefined && toolName !== ASK_USER_QUESTION_TOOL_NAME) {
|
|
646
640
|
const editLane = await readPersistedRules({ toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
|
|
647
|
-
editRuleStore = editLane
|
|
641
|
+
editRuleStore = ruleStoreUnreadableOf(editLane);
|
|
648
642
|
const editTightened = applyPersistedTightening(recheck, editLane);
|
|
649
643
|
recheck = editTightened.decision;
|
|
650
644
|
if (editTightened.tightened === "deny")
|
|
@@ -665,9 +659,10 @@ export async function runGateLanes(pass) {
|
|
|
665
659
|
editArgs = recheck.updatedInput;
|
|
666
660
|
editRewrittenSinceHuman = true;
|
|
667
661
|
}
|
|
668
|
-
const
|
|
662
|
+
const { ruleStoreUnreadable: _selfDeclaredOnEdit, ...recheckUnstamped } = recheck;
|
|
663
|
+
const editAskSnapshot = { ...recheckUnstamped, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
|
|
669
664
|
markedThisRound = undefined;
|
|
670
|
-
const editAskDecision = { ...editAskSnapshot, origin: askOriginOf(editAskSnapshot, askOriginFacts(editOrg.org, editRuleStore)) };
|
|
665
|
+
const editAskDecision = { ...editAskSnapshot, ...(editRuleStore !== undefined ? { ruleStoreUnreadable: editRuleStore } : {}), origin: askOriginOf(editAskSnapshot, askOriginFacts(editOrg.org, editRuleStore)) };
|
|
671
666
|
const editAskReq = { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
|
|
672
667
|
const rr = await (callSignal !== undefined ? resolveAsk(editAskDecision, editAskReq, callSignal) : resolveAsk(editAskDecision, editAskReq));
|
|
673
668
|
ledger.fromResolved(rr, editAskDecision, toolName);
|
|
@@ -153,12 +153,49 @@ export declare const DENIED_BY_MAY_VETO: {
|
|
|
153
153
|
};
|
|
154
154
|
/** Compile-time fence: `never` while every deny layer has a veto row. */
|
|
155
155
|
export type VetoTableCoversEveryDeniedBy = AssertAllKeysHandled<Exclude<DeniedBy, keyof typeof DENIED_BY_MAY_VETO>>;
|
|
156
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* The FORM of an auto-mode classifier deny — the one structural position a consumer branches on when a
|
|
158
|
+
* deny came through the classifier station. The classifier ends a round three ways, and until this
|
|
159
|
+
* member the wire carried the three as one layer word plus prose:
|
|
160
|
+
* - `unavailable` — the classifier could not run (the model leg threw / rejected / ran past the cap):
|
|
161
|
+
* a fail-closed refusal that SAYS SO — the call may simply be retried later (CC 2.1.250 `x1t`);
|
|
162
|
+
* - `parse_error` — the classifier answered outside its verdict contract: blocked for safety, counted by
|
|
163
|
+
* the denial limit like a ruling (CC 2.1.250 `Ure`);
|
|
164
|
+
* - ABSENT beside `deniedBy: "classifier"` — the classifier's own RULING (a `<block>yes` verdict).
|
|
165
|
+
* The WHY behind an `unavailable` (error vs timeout) is not a branching fact: the deny sentence spells it
|
|
166
|
+
* and the `auto_mode.classified` trace frame carries it (`cause: "error" | "timeout"`).
|
|
167
|
+
*/
|
|
168
|
+
export declare const CLASSIFIER_DENY_CAUSES: readonly ["unavailable", "parse_error"];
|
|
169
|
+
export type ClassifierDenyCause = (typeof CLASSIFIER_DENY_CAUSES)[number];
|
|
170
|
+
/** Membership test for {@link ClassifierDenyCause}. */
|
|
171
|
+
export declare function isClassifierDenyCause(v: unknown): v is ClassifierDenyCause;
|
|
172
|
+
/** Which deny layers a classifier cause may stand beside: the gate's own classifier station (`classifier`),
|
|
173
|
+
* and an ANCESTOR's frozen classifier reached through the inherited policy chain, whose deny the child's
|
|
174
|
+
* gate attributes to the chain (`policy`, station row 14) — the cause is attested by the station that
|
|
175
|
+
* minted the deny, so the word beside it is whichever layer word the child's ledger assigns. */
|
|
176
|
+
export declare const DENIED_BY_MAY_CARRY_CAUSE: {
|
|
177
|
+
readonly policy: true;
|
|
178
|
+
readonly hook: false;
|
|
179
|
+
readonly org: false;
|
|
180
|
+
readonly persisted_rule: false;
|
|
181
|
+
readonly classifier: true;
|
|
182
|
+
readonly plan_mode: false;
|
|
183
|
+
readonly compliance: false;
|
|
184
|
+
readonly write_protection: false;
|
|
185
|
+
readonly ask_resolution: false;
|
|
186
|
+
};
|
|
187
|
+
/** Compile-time fence: `never` while every deny layer has a cause row. */
|
|
188
|
+
export type CauseTableCoversEveryDeniedBy = AssertAllKeysHandled<Exclude<DeniedBy, keyof typeof DENIED_BY_MAY_CARRY_CAUSE>>;
|
|
189
|
+
/** The final disposition of a gate pass. `cause` is present exactly when the deny came out of an auto-mode
|
|
190
|
+
* classifier round that did not RULE ({@link ClassifierDenyCause}); a ruling block and every non-classifier
|
|
191
|
+
* deny carry none. Invariant I5 ({@link screenGateOutcome}): a cause stands only beside a layer word in
|
|
192
|
+
* {@link DENIED_BY_MAY_CARRY_CAUSE}. */
|
|
157
193
|
export type GateDisposition = {
|
|
158
194
|
kind: "allowed";
|
|
159
195
|
} | {
|
|
160
196
|
kind: "denied";
|
|
161
197
|
deniedBy: DeniedBy;
|
|
198
|
+
cause?: ClassifierDenyCause;
|
|
162
199
|
};
|
|
163
200
|
/**
|
|
164
201
|
* The WHOLE record of one tool-gate pass: the final disposition, plus — when this pass settled an ask —
|
|
@@ -173,7 +210,9 @@ export type GateDisposition = {
|
|
|
173
210
|
* I3 `settlement.kind === "human_allowed"` ∧ `disposition.kind === "denied"` ⇒ `deniedBy` is a veto
|
|
174
211
|
* layer ({@link DENIED_BY_MAY_VETO}) — a person approved, a later re-check refused; the approver stays
|
|
175
212
|
* on the settlement (it is not the approver's refusal);
|
|
176
|
-
* I4 `disposition.kind === "allowed"` ⇒ `settlement` absent or `human_allowed
|
|
213
|
+
* I4 `disposition.kind === "allowed"` ⇒ `settlement` absent or `human_allowed`;
|
|
214
|
+
* I5 `disposition.cause` present ⇒ a member of {@link CLASSIFIER_DENY_CAUSES} beside a layer word
|
|
215
|
+
* {@link DENIED_BY_MAY_CARRY_CAUSE} admits (the classifier station, or the inherited chain's `policy`).
|
|
177
216
|
* Nothing here is read out of an ABSENCE: an ordinary allow with no ask is `{ disposition: { kind: "allowed" } }`,
|
|
178
217
|
* a direct policy deny is `{ disposition: { kind: "denied", deniedBy: "policy" } }`, and neither carries a
|
|
179
218
|
* settlement because neither settled one.
|
|
@@ -184,7 +223,7 @@ export interface GateOutcome {
|
|
|
184
223
|
origin?: AskOrigin;
|
|
185
224
|
}
|
|
186
225
|
/**
|
|
187
|
-
* The one invariant screen for a {@link GateOutcome} (I1–
|
|
226
|
+
* The one invariant screen for a {@link GateOutcome} (I1–I5 above, plus the shape of each member). Runs
|
|
188
227
|
* where an outcome enters a wire face — the engine's settlement sideband before the `tool_end` frame, and
|
|
189
228
|
* the decide lane before a durable row is resolved. Returns the defects as sentences (an empty list is a
|
|
190
229
|
* clean record); the caller decides the fail-loud form (report + withhold, or refuse), never silently
|
|
@@ -47,6 +47,22 @@ export const DENIED_BY_MAY_VETO = {
|
|
|
47
47
|
write_protection: true,
|
|
48
48
|
ask_resolution: false,
|
|
49
49
|
};
|
|
50
|
+
export const CLASSIFIER_DENY_CAUSES = ["unavailable", "parse_error"];
|
|
51
|
+
const CLASSIFIER_DENY_CAUSE_SET = new Set(CLASSIFIER_DENY_CAUSES);
|
|
52
|
+
export function isClassifierDenyCause(v) {
|
|
53
|
+
return CLASSIFIER_DENY_CAUSE_SET.has(v);
|
|
54
|
+
}
|
|
55
|
+
export const DENIED_BY_MAY_CARRY_CAUSE = {
|
|
56
|
+
policy: true,
|
|
57
|
+
hook: false,
|
|
58
|
+
org: false,
|
|
59
|
+
persisted_rule: false,
|
|
60
|
+
classifier: true,
|
|
61
|
+
plan_mode: false,
|
|
62
|
+
compliance: false,
|
|
63
|
+
write_protection: false,
|
|
64
|
+
ask_resolution: false,
|
|
65
|
+
};
|
|
50
66
|
export function screenGateOutcome(g) {
|
|
51
67
|
const defects = [];
|
|
52
68
|
const d = g.disposition;
|
|
@@ -67,5 +83,11 @@ export function screenGateOutcome(g) {
|
|
|
67
83
|
}
|
|
68
84
|
if (d.kind === "allowed" && s !== undefined && s.kind !== "human_allowed")
|
|
69
85
|
defects.push(`I4: an allowed disposition cannot carry a "${s.kind}" settlement`);
|
|
86
|
+
if (d.kind === "denied" && d.cause !== undefined) {
|
|
87
|
+
if (!isClassifierDenyCause(d.cause))
|
|
88
|
+
defects.push(`disposition.cause "${String(d.cause)}" is outside the closed set`);
|
|
89
|
+
else if (isDeniedBy(d.deniedBy) && !DENIED_BY_MAY_CARRY_CAUSE[d.deniedBy])
|
|
90
|
+
defects.push(`I5: a classifier cause cannot stand beside deniedBy "${d.deniedBy}"`);
|
|
91
|
+
}
|
|
70
92
|
return defects;
|
|
71
93
|
}
|
|
@@ -104,7 +104,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
104
104
|
* src/ for notice mint shapes and names any code that is minted but unregistered, or registered but
|
|
105
105
|
* no longer minted.
|
|
106
106
|
*/
|
|
107
|
-
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "route.fallback_to_primary", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "tool_result.offload_put_failed"];
|
|
107
|
+
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "route.fallback_to_primary", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "tool_result.offload_put_failed"];
|
|
108
108
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
109
109
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
110
110
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -141,6 +141,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
141
141
|
"task.turn_interrupted",
|
|
142
142
|
"task.halt_unconsumed",
|
|
143
143
|
"task.late_approval",
|
|
144
|
+
"task.interrupt_unconsumed",
|
|
144
145
|
"memory.capture_opted_out",
|
|
145
146
|
"memory.capture_optout_unpersisted",
|
|
146
147
|
"tool_result.offload_put_failed",
|
|
@@ -159,10 +160,11 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
159
160
|
"steering.parked_input_blocked": "user",
|
|
160
161
|
"task.halt_unconsumed": "user",
|
|
161
162
|
"task.late_approval": "user",
|
|
163
|
+
"task.interrupt_unconsumed": "user",
|
|
162
164
|
"config.durable_gate_unavailable": "user",
|
|
163
165
|
"memory.capture_opted_out": "user",
|
|
164
166
|
"memory.capture_optout_unpersisted": "user",
|
|
165
|
-
"memory.consolidation_withheld": "
|
|
167
|
+
"memory.consolidation_withheld": "operator",
|
|
166
168
|
"delegation.ask_unresolvable": "user",
|
|
167
169
|
"config.autocompact_window_clamped": "operator",
|
|
168
170
|
"config.env_timeout_discarded": "operator",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -2,10 +2,10 @@ import type { ActorAssertion, DocumentContent, ImageContent, TextContent } from
|
|
|
2
2
|
import type { ExecutionEnv, FileError, Result, SessionTreeEntry } from "../internal/harness-types.js";
|
|
3
3
|
import type { DecisionReason, PermissionResult, ResolvedAsk, ToolCallFace, ToolCallRequest, ToolPolicy } from "./tool-policy.js";
|
|
4
4
|
import type { GateOutcome } from "./gate-outcome.js";
|
|
5
|
-
export { normalizeOrgGateVerdict, normalizePersistedRuleHit
|
|
5
|
+
export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./gate-lanes.js";
|
|
6
6
|
export { cloneObserverInput } from "./runner/gate-exit.js";
|
|
7
7
|
import { type AskClass } from "./ask-class.js";
|
|
8
|
-
import {
|
|
8
|
+
import type { AutoModeDenialTracker } from "./auto-mode.js";
|
|
9
9
|
export { formatHookFeedback } from "./reminder-mint.js";
|
|
10
10
|
import type { WiringLegKind } from "./wiring-manifest.js";
|
|
11
11
|
/**
|
|
@@ -411,12 +411,6 @@ export interface PermissionDeniedPayload {
|
|
|
411
411
|
* settled none.
|
|
412
412
|
*/
|
|
413
413
|
gate: GateOutcome;
|
|
414
|
-
/** #661 (additive): present ⇔ this deny is the auto-mode classifier's UNAVAILABILITY (`deniedBy: "classifier"`
|
|
415
|
-
* with the verdict's cause word) — the structured half of the deny text's "the classifier is temporarily
|
|
416
|
-
* unavailable" sentence. A classifier BLOCK or parse-failure deny carries no member here. */
|
|
417
|
-
classifierUnavailable?: {
|
|
418
|
-
readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
|
|
419
|
-
};
|
|
420
414
|
/** {@link HookSeatSignal} — this invocation's own abort signal. On an OBSERVATION seat the deny has
|
|
421
415
|
* already happened and nothing this callback does can change it, so the signal says exactly one
|
|
422
416
|
* thing: stop reading, nobody is waiting for your answer any more. */
|
|
@@ -1676,12 +1670,9 @@ export interface AskCarry {
|
|
|
1676
1670
|
/** The surviving ask's engine-stamped origin word ({@link import("./ask-origin.js").AskOrigin}), persisted
|
|
1677
1671
|
* on the row so a durable card is rendered by the same word the synchronous card is. */
|
|
1678
1672
|
readonly origin?: import("./ask-origin.js").AskOrigin;
|
|
1679
|
-
/** #
|
|
1680
|
-
*
|
|
1681
|
-
|
|
1682
|
-
readonly classifierUnavailable?: {
|
|
1683
|
-
readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
|
|
1684
|
-
};
|
|
1673
|
+
/** #688 C3: the mechanism behind an `origin: "rule_store_unavailable"` (`store` / `call`), persisted on the row
|
|
1674
|
+
* so a durable card says the same thing the synchronous card does. */
|
|
1675
|
+
readonly ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
|
|
1685
1676
|
}
|
|
1686
1677
|
/** What a durable row persists of an {@link AskCarry}: the fallback member COPIED (the row never aliases
|
|
1687
1678
|
* the gate's object; its window is already `0` — the gate's carry mint is the one writer of that
|
|
@@ -1690,9 +1681,7 @@ export interface AskCarry {
|
|
|
1690
1681
|
export declare function askCarryRowMembers(carry: AskCarry | undefined): {
|
|
1691
1682
|
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1692
1683
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
1693
|
-
|
|
1694
|
-
readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
|
|
1695
|
-
};
|
|
1684
|
+
ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
|
|
1696
1685
|
};
|
|
1697
1686
|
/**
|
|
1698
1687
|
* The design/37 **two-phase tool gate** — the single chokepoint that makes the load-bearing invariant
|
package/dist/core/hooks.js
CHANGED
|
@@ -2,11 +2,10 @@ import { decisionText, refuseOutOfContractDecision } from "./tool-policy.js";
|
|
|
2
2
|
import { createSettlementLedger, engineFailClosedExit, hookDenyExit, traceHookCrash } from "./runner/gate-exit.js";
|
|
3
3
|
import { runGateFold } from "./gate-fold.js";
|
|
4
4
|
import { runGateLanes } from "./gate-lanes.js";
|
|
5
|
-
export { normalizeOrgGateVerdict, normalizePersistedRuleHit
|
|
5
|
+
export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./gate-lanes.js";
|
|
6
6
|
export { cloneObserverInput } from "./runner/gate-exit.js";
|
|
7
7
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
8
|
-
import { isAskOrigin } from "./ask-origin.js";
|
|
9
|
-
import { isAutoModeUnavailableCause } from "./auto-mode.js";
|
|
8
|
+
import { isAskOrigin, isRuleStoreUnreadable } from "./ask-origin.js";
|
|
10
9
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
11
10
|
export { formatHookFeedback } from "./reminder-mint.js";
|
|
12
11
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
@@ -235,10 +234,12 @@ export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash, timeo
|
|
|
235
234
|
export function askCarryRowMembers(carry) {
|
|
236
235
|
if (carry === undefined)
|
|
237
236
|
return {};
|
|
237
|
+
const origin = carry.origin;
|
|
238
|
+
const ruleStoreUnreadable = carry.ruleStoreUnreadable;
|
|
238
239
|
return {
|
|
239
240
|
...(carry.denialLimitFallback !== undefined ? { denialLimitFallback: { ...carry.denialLimitFallback } } : {}),
|
|
240
|
-
...(isAskOrigin(
|
|
241
|
-
...(
|
|
241
|
+
...(isAskOrigin(origin) ? { origin } : {}),
|
|
242
|
+
...(isRuleStoreUnreadable(ruleStoreUnreadable) ? { ruleStoreUnreadable } : {}),
|
|
242
243
|
};
|
|
243
244
|
}
|
|
244
245
|
export async function runToolGate(input) {
|
|
@@ -308,6 +309,7 @@ export async function runToolGate(input) {
|
|
|
308
309
|
hookAsk: undefined,
|
|
309
310
|
parkFailed: undefined,
|
|
310
311
|
deniedBy: undefined,
|
|
312
|
+
denyCause: undefined,
|
|
311
313
|
decision: { action: "allow" },
|
|
312
314
|
tightenedBy: undefined,
|
|
313
315
|
policyRewrite: undefined,
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -330,8 +330,9 @@ export declare const MCP_TOOL_TIMEOUT_DEFAULT_MS = 100000000;
|
|
|
330
330
|
*/
|
|
331
331
|
export declare const MCP_ENV_MS_MIN = 1000;
|
|
332
332
|
export declare const MCP_ENV_MS_MAX = 2147483647;
|
|
333
|
-
/** Test seam
|
|
334
|
-
* the
|
|
333
|
+
/** Test seam kept under the name its callers already use; the ledger it clears is the shared one
|
|
334
|
+
* ({@link __resetEnvKnobAnnouncements} — the MCP knobs are not a family of their own, they are
|
|
335
|
+
* process-level env knobs like the output-length pair). Never called by production code. */
|
|
335
336
|
export declare function __resetMcpEnvAnnouncements(): void;
|
|
336
337
|
/** Resolve the per-call tool timeout in ms (env `MCP_TOOL_TIMEOUT`, else the large default). Exported for tests. */
|
|
337
338
|
export declare function mcpToolTimeoutMs(): number;
|
package/dist/core/mcp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { redactSecrets } from "./untrusted-egress.js";
|
|
2
|
+
import { announceEnvKnob, __resetEnvKnobAnnouncements } from "./env-knob-announce.js";
|
|
2
3
|
import { MCP_NAMESPACE } from "./protocol-table.js";
|
|
3
4
|
import { findNamespacePrefixCollision, mintNamespacePrefix, mintNamespacedToolName, normalizeNameSegment } from "./protocol-naming.js";
|
|
4
5
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -38,7 +39,7 @@ function mcpMaxOutputTokens() {
|
|
|
38
39
|
return MCP_OUTPUT_TOKENS_DEFAULT;
|
|
39
40
|
const n = parseWholeNumber(raw);
|
|
40
41
|
if (n === undefined || n <= 0) {
|
|
41
|
-
|
|
42
|
+
announceEnvKnob(`MAX_MCP_OUTPUT_TOKENS=${rawEnv} was ignored — it is not a positive whole number of tokens. Using ${MCP_OUTPUT_TOKENS_DEFAULT} instead.`);
|
|
42
43
|
return MCP_OUTPUT_TOKENS_DEFAULT;
|
|
43
44
|
}
|
|
44
45
|
return n;
|
|
@@ -132,15 +133,8 @@ function parseWholeNumber(raw) {
|
|
|
132
133
|
return parseInt(raw.replace(ENV_GROUP_SEPARATORS_RE, ""), 10);
|
|
133
134
|
return undefined;
|
|
134
135
|
}
|
|
135
|
-
const announcedMcpEnvKnobs = new Set();
|
|
136
|
-
function announceMcpEnvKnob(line) {
|
|
137
|
-
if (announcedMcpEnvKnobs.has(line))
|
|
138
|
-
return;
|
|
139
|
-
announcedMcpEnvKnobs.add(line);
|
|
140
|
-
console.warn(line);
|
|
141
|
-
}
|
|
142
136
|
export function __resetMcpEnvAnnouncements() {
|
|
143
|
-
|
|
137
|
+
__resetEnvKnobAnnouncements();
|
|
144
138
|
}
|
|
145
139
|
function parseEnvMs(name) {
|
|
146
140
|
const rawEnv = process.env[name];
|
|
@@ -151,12 +145,12 @@ function parseEnvMs(name) {
|
|
|
151
145
|
return undefined;
|
|
152
146
|
const n = parseWholeNumber(raw);
|
|
153
147
|
if (n === undefined || n <= 0) {
|
|
154
|
-
|
|
148
|
+
announceEnvKnob(`${name}=${rawEnv} was ignored — it is not a positive whole number of milliseconds. Using the built-in default instead.`);
|
|
155
149
|
return undefined;
|
|
156
150
|
}
|
|
157
151
|
const clamped = Math.min(Math.max(n, MCP_ENV_MS_MIN), MCP_ENV_MS_MAX);
|
|
158
152
|
if (clamped !== n) {
|
|
159
|
-
|
|
153
|
+
announceEnvKnob(`${name}=${rawEnv} is outside the range this runtime can honor (${MCP_ENV_MS_MIN}..${MCP_ENV_MS_MAX} ms). Using ${clamped}ms instead.`);
|
|
160
154
|
}
|
|
161
155
|
return clamped;
|
|
162
156
|
}
|
|
@@ -1663,8 +1663,8 @@ export declare class MemoryEngine {
|
|
|
1663
1663
|
* @returns the rejection to report, or `undefined` when the index is clean/absent.
|
|
1664
1664
|
*/
|
|
1665
1665
|
private gateDerivedIndex;
|
|
1666
|
-
/** Names of read-only sibling scope dirs directly under the writable root (excluded from scans). */
|
|
1667
|
-
|
|
1666
|
+
/** Names of read-only sibling scope dirs directly under the writable root (excluded from scans; public since #691 — the runner refuses to admit a writable root that contains another scope's directory to the file tools' allowlist). */
|
|
1667
|
+
readonlyDirNamesUnderRoot(handle: MemorySessionHandle): Set<string>;
|
|
1668
1668
|
/** Projection write: reads the target to skip a no-op, then writes — the same check-then-use shape
|
|
1669
1669
|
* as the containment restores, so it takes the same no-follow write. A link at a projection path
|
|
1670
1670
|
* is never a supported layout (the harvest scan refuses symlinked entry files outright), and this
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The advertised-writable-directory seat — ONE rule for every directory the prompt tells the model
|
|
3
|
+
* it may write with the file tools:
|
|
4
|
+
*
|
|
5
|
+
* a directory the prompt advertises as writable is a directory the root fence ADMITS; the same
|
|
6
|
+
* admission decides the hole and the teaching, so a directory the fence cannot admit is neither
|
|
7
|
+
* opened nor taught, and the skip is announced.
|
|
8
|
+
*
|
|
9
|
+
* Two advertisers share it: the deployment's scratchpad (`TaskSpec.envFacts.scratchpadDir`, spoken
|
|
10
|
+
* by the `# Environment` block) and the memory engine's write root (spoken by the `# Memory`
|
|
11
|
+
* instruction, "write to it directly with the Write tool"). Before this seat each advertiser judged
|
|
12
|
+
* on its own: the scratchpad opened its hole in the hands phase, the memory instruction checked only
|
|
13
|
+
* that a `Write` tool was mounted — and the fence refused the very path the instruction named
|
|
14
|
+
* (`path_not_in_root`), a contradictory gate that taught the model to route around the tools.
|
|
15
|
+
*
|
|
16
|
+
* Admission = fail-closed canonicalization through the SAME ExecutionEnv the fence judges with (a
|
|
17
|
+
* raw or dangling entry is never admitted — the ancestor-canonicalize fallback would let a symlinked
|
|
18
|
+
* prefix carry writes outside every root). An advertiser that OWNS its directory's creation may ask
|
|
19
|
+
* for materialization on a DEFINITIVE `not_found` (the exclusive-create marker, so a concurrently
|
|
20
|
+
* planted path is never written through); an advertiser whose directory is created elsewhere passes
|
|
21
|
+
* no marker and an absent directory narrows.
|
|
22
|
+
*
|
|
23
|
+
* Reverse check (the seat's own invariant): nothing enters `roots` except through `admit`, and every
|
|
24
|
+
* caller of `admit` is an advertiser deciding its advertisement off the returned verdict — so there is
|
|
25
|
+
* no directory the fence opens that the prompt does not advertise, and none the prompt advertises
|
|
26
|
+
* that the fence refuses.
|
|
27
|
+
*/
|
|
28
|
+
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
29
|
+
export interface AdvertisedWritableDir {
|
|
30
|
+
/** The directory as the advertisement spells it. */
|
|
31
|
+
path: string;
|
|
32
|
+
/** Who advertises it — names the skip line and the trace. */
|
|
33
|
+
advertiser: "scratchpad" | "memory";
|
|
34
|
+
/**
|
|
35
|
+
* How the directory comes to exist when the env reports it absent. The advertiser that owns the
|
|
36
|
+
* directory's creation supplies the marker file name for the atomic exclusive-create
|
|
37
|
+
* materialization (parent dirs created like writeFile; a marker that already exists — planted
|
|
38
|
+
* concurrently, symlink or not — is never written through; an env without an exclusive primitive
|
|
39
|
+
* narrows instead of emulating one with an overwriting write). An advertiser whose directory is
|
|
40
|
+
* created elsewhere (the memory engine materializes its own root) passes nothing — absent narrows.
|
|
41
|
+
*/
|
|
42
|
+
materializeMarker?: string;
|
|
43
|
+
/**
|
|
44
|
+
* The canonical spelling the advertiser's OWN gates key on, when it has one (the memory engine keys its
|
|
45
|
+
* write gate and its harvest boundary on `MemorySessionHandle.writableRoot`). A fence that resolves the
|
|
46
|
+
* directory to a different canonical path would admit writes those gates never see — such a mismatch is
|
|
47
|
+
* a skip, announced with both spellings, never an admission.
|
|
48
|
+
*/
|
|
49
|
+
expectCanonical?: string;
|
|
50
|
+
}
|
|
51
|
+
/** The skip announcement — the one loud outlet for a directory that was advertised but could not be admitted. */
|
|
52
|
+
export type AdvertisedWritableDirSkipped = (dir: AdvertisedWritableDir, reason: string) => void;
|
|
53
|
+
export declare class AdvertisedWritableDirs {
|
|
54
|
+
private readonly env;
|
|
55
|
+
private readonly onSkipped;
|
|
56
|
+
private readonly admitted;
|
|
57
|
+
private readonly verdicts;
|
|
58
|
+
constructor(env: ExecutionEnv, onSkipped: AdvertisedWritableDirSkipped);
|
|
59
|
+
/** The admitted roots, CANONICAL, in admission order, deduplicated. The hands phase folds this list into the
|
|
60
|
+
* write-capable containment allowlist before the toolkit compiles; it is not a live view after that. */
|
|
61
|
+
get roots(): readonly string[];
|
|
62
|
+
/** What became of an advertiser's directory: admitted, skipped (announced), or never asked (the advertiser
|
|
63
|
+
* had nothing to advertise, or decided not to). The prompt side reads this to withhold an advertisement. */
|
|
64
|
+
verdictOf(advertiser: AdvertisedWritableDir["advertiser"]): "admitted" | "skipped" | "not-asked";
|
|
65
|
+
/**
|
|
66
|
+
* Admit one advertised directory. TRUE ⇔ the fence will admit writes under it (its canonical form is on
|
|
67
|
+
* {@link roots}); FALSE ⇔ it was skipped (announced through the seat's outlet) and the advertiser must not
|
|
68
|
+
* advertise it. The env's answers are Result-shaped, so the judgment itself never throws; the skip outlet is
|
|
69
|
+
* the caller's own sink (`deps.onError` in the driver) and is deliberately not guarded here — every phase
|
|
70
|
+
* lets that sink's own exceptions propagate, and a seat that swallowed them would hide the deployment's bug.
|
|
71
|
+
*/
|
|
72
|
+
admit(dir: AdvertisedWritableDir): Promise<boolean>;
|
|
73
|
+
private judge;
|
|
74
|
+
}
|