@sema-agent/core 7.9.2 → 7.11.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 +102 -0
- package/dist/agents/child-model-seat.d.ts +71 -14
- package/dist/agents/child-model-seat.js +23 -7
- package/dist/agents/subagent.d.ts +2 -2
- package/dist/agents/subagent.js +8 -5
- package/dist/agents/teacher.js +2 -2
- package/dist/core/ask-unresolvable-notice.d.ts +52 -0
- package/dist/core/ask-unresolvable-notice.js +25 -0
- package/dist/core/auto-mode-defaults.d.ts +15 -3
- package/dist/core/auto-mode-defaults.js +1 -0
- package/dist/core/auto-mode.d.ts +86 -23
- package/dist/core/auto-mode.js +43 -12
- package/dist/core/checkpoint-store.d.ts +14 -0
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/engine-notice.d.ts +28 -7
- package/dist/core/gate-fold.js +1 -0
- package/dist/core/gate-lanes.d.ts +6 -1
- package/dist/core/gate-lanes.js +60 -18
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +4 -0
- package/dist/core/hooks.d.ts +37 -1
- package/dist/core/hooks.js +2 -0
- package/dist/core/permission-rule-model.d.ts +53 -16
- package/dist/core/permission-rule-model.js +59 -21
- package/dist/core/permission-rules.d.ts +6 -4
- package/dist/core/permission-rules.js +14 -14
- package/dist/core/persisted-rule-arms.js +4 -3
- package/dist/core/read-only-shell-table.d.ts +87 -0
- package/dist/core/read-only-shell-table.js +485 -0
- package/dist/core/read-only-shell.d.ts +42 -0
- package/dist/core/read-only-shell.js +316 -0
- package/dist/core/roles.d.ts +3 -2
- package/dist/core/runner/contracts.d.ts +26 -2
- package/dist/core/runner/denial-limit-arms.d.ts +14 -3
- package/dist/core/runner/denial-limit-arms.js +15 -5
- package/dist/core/runner/gate-exit.d.ts +5 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +7 -1
- package/dist/core/runner/permission-rule-lanes.js +9 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.js +35 -14
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -2
- package/dist/core/runner/prepare-gate-stations.js +12 -0
- package/dist/core/runner/prepare-policy-chain.js +7 -6
- package/dist/core/runner/prepare-task.js +1 -1
- package/dist/core/runner/prepare-turn-wiring.js +1 -1
- package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -1
- package/dist/core/runner/prepare-wiring-manifest.js +8 -1
- package/dist/core/runner/runtask.d.ts +33 -32
- package/dist/core/runner/runtask.js +63 -33
- package/dist/core/runner-deps.d.ts +9 -2
- package/dist/core/shell-lexer.d.ts +18 -0
- package/dist/core/shell-lexer.js +17 -10
- package/dist/core/shell-wrapper-table.js +8 -5
- package/dist/core/swappable-deps.d.ts +90 -0
- package/dist/core/swappable-deps.js +55 -0
- package/dist/core/tool-policy.d.ts +30 -1
- package/dist/core/tool-policy.js +6 -2
- package/dist/core/tools.d.ts +28 -7
- package/dist/core/tools.js +44 -4
- package/dist/core/trace.d.ts +15 -0
- package/dist/core/wiring-manifest.d.ts +15 -1
- package/dist/core/wiring-manifest.js +10 -2
- package/dist/core/workflow-journal-store.d.ts +21 -2
- package/dist/core/workflow-journal-store.js +1 -1
- package/dist/engine/execution-env/node-execution-env.d.ts +2 -0
- package/dist/engine/execution-env/node-execution-env.js +2 -1
- package/dist/engine/harness/agent-harness.d.ts +3 -1
- package/dist/engine/harness/agent-harness.js +1 -1
- package/dist/engine/harness/types.d.ts +15 -2
- package/dist/index.d.ts +8 -4
- package/dist/index.js +7 -3
- package/dist/orchestration/run-workflow-tool.d.ts +22 -2
- package/dist/orchestration/run-workflow-tool.js +14 -1
- package/dist/orchestration/workflow-governance.d.ts +3 -2
- package/dist/orchestration/workflow-observe.d.ts +1 -1
- package/dist/orchestration/workflow-observe.js +2 -0
- package/dist/orchestration/workflow-primitives.d.ts +4 -1
- package/dist/orchestration/workflow-primitives.js +1 -6
- package/dist/orchestration/workflow-types.d.ts +37 -2
- package/dist/orchestration/workflow-types.js +16 -0
- package/dist/orchestration/workflow.d.ts +53 -6
- package/dist/orchestration/workflow.js +319 -51
- package/dist/prompt-assembly/turn-snapshot.d.ts +4 -2
- package/dist/stores/file/workflow-journal-store.js +10 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +85 -5
package/dist/core/auto-mode.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import type { ToolCallRequest } from "./tool-policy.js";
|
|
2
|
+
/**
|
|
3
|
+
* WHY an `unavailable` verdict could not run — the closed set behind the verdict arm, the ask's
|
|
4
|
+
* `classifierUnavailable` fact (the card's "asked because the classifier could not run (timeout)" sentence)
|
|
5
|
+
* and the `auto_mode.classified` trace frame's `cause`, so the three spell one word. `error` = the model leg
|
|
6
|
+
* threw or rejected (a route failure at classify time reads here too — the derived-route pre-flight fell
|
|
7
|
+
* back BEFORE any decide, so there is no separate word for it); `timeout` = the round-trip cap;
|
|
8
|
+
* `breaker_open` = the session latch already tripped and the round was short-circuited.
|
|
9
|
+
*/
|
|
10
|
+
export declare const AUTO_MODE_UNAVAILABLE_CAUSES: readonly ["error", "timeout", "breaker_open"];
|
|
11
|
+
export type AutoModeUnavailableCause = (typeof AUTO_MODE_UNAVAILABLE_CAUSES)[number];
|
|
12
|
+
/** Whether a value is a member of the unavailable-cause set — the screen a row reader applies to a stored word. */
|
|
13
|
+
export declare function isAutoModeUnavailableCause(v: unknown): v is AutoModeUnavailableCause;
|
|
2
14
|
/** The classifier's verdict over one pending `ask`. */
|
|
3
15
|
export type AutoModeVerdict = {
|
|
4
16
|
kind: "allow";
|
|
@@ -12,7 +24,7 @@ export type AutoModeVerdict = {
|
|
|
12
24
|
/** The classifier could not run: model error/timeout, or the session breaker is open. NOT a decision. */
|
|
13
25
|
| {
|
|
14
26
|
kind: "unavailable";
|
|
15
|
-
cause:
|
|
27
|
+
cause: AutoModeUnavailableCause;
|
|
16
28
|
}
|
|
17
29
|
/** The model responded but not in the `<block>…` contract shape. NOT a decision. */
|
|
18
30
|
| {
|
|
@@ -20,30 +32,44 @@ export type AutoModeVerdict = {
|
|
|
20
32
|
raw: string;
|
|
21
33
|
};
|
|
22
34
|
/**
|
|
23
|
-
*
|
|
35
|
+
* The failure kinds that COUNT toward the one-way breaker — and the word a trip records as its `lastCause`:
|
|
36
|
+
* the two health failures of the unavailable arm plus the contract failure. `breaker_open` is deliberately
|
|
37
|
+
* not one: it is what the latch ANSWERS once tripped, never what trips it; a cancelled round (the run's own
|
|
38
|
+
* abort) counts nothing (see the decider's catch arm).
|
|
39
|
+
*/
|
|
40
|
+
export declare const AUTO_MODE_BREAKER_CAUSES: readonly ["error", "timeout", "parse_error"];
|
|
41
|
+
export type AutoModeBreakerCause = (typeof AUTO_MODE_BREAKER_CAUSES)[number];
|
|
42
|
+
/** Whether a value is a member of the breaker-cause set (the manifest's value screen over a recorded trip). */
|
|
43
|
+
export declare function isAutoModeBreakerCause(v: unknown): v is AutoModeBreakerCause;
|
|
44
|
+
/**
|
|
45
|
+
* Parse the classifier's output contract — the CC 2.1.250 verdict reader (`wCe`, its thinking strip
|
|
46
|
+
* `xCe`, its reason reader `yCe`) ported as is:
|
|
24
47
|
* blocked: `<block>yes</block><category>Rule Name</category><reason>[Rule Name] …</reason>`
|
|
25
48
|
* allowed: `<block>no</block>`
|
|
26
|
-
* The contract says the ENTIRE response must begin with `<block>` — we strip `<thinking>` blocks
|
|
27
|
-
* first (CC-anchored: the 206 body's `dJi` removes closed thinking blocks AND a dangling unclosed
|
|
28
|
-
* tail before its own block-scan — a reasoning model's scratch space is not part of the verdict),
|
|
29
|
-
* then tolerate leading whitespace (models routinely emit a leading newline) but nothing else: any
|
|
30
|
-
* other preamble is a `parse_error` (fail-closed; 207 vocabulary: `automode-parsing-error`).
|
|
31
49
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
50
|
+
* The verdict is the set of `<block>yes|no` hits, NOT a prefix match:
|
|
51
|
+
* 1. CONFLICT, read on the RAW text (thinking included): `<block>(yes|no)\b` collected over the whole
|
|
52
|
+
* response — more than one distinct answer ⇒ no verdict (`parse_error`). A scratch-space "yes"
|
|
53
|
+
* contradicting a final "no" is a contradiction, not an allow.
|
|
54
|
+
* 2. HITS, read on the text with `<thinking>` blocks removed (closed ones anywhere, then an unclosed
|
|
55
|
+
* trailing one — a reasoning model's scratch space is not part of the verdict): `<block>(yes|no)\b`
|
|
56
|
+
* with the closing tag OPTIONAL, at ANY position. Zero hits ⇒ `parse_error` (a reply that only
|
|
57
|
+
* answered inside its thinking has no verdict); the one distinct answer ⇒ `no` = allow, `yes` = block.
|
|
58
|
+
* So a preamble before the tag, prose after `<block>no`, a missing `</block>` and a reply that repeats
|
|
59
|
+
* its answer all read as the answer they carry; the tag must be spelled `<block>yes` / `<block>no` with
|
|
60
|
+
* no whitespace inside (the word-boundary form — `<block>yesterday` is not a hit) and case does not matter.
|
|
61
|
+
*
|
|
62
|
+
* A stated BLOCK stands with or without its tail: `<category>` (the BLOCK rule NAME the prompt asked
|
|
63
|
+
* for, read case-insensitively as free text — the deployment authors those names, so there is no
|
|
64
|
+
* fixed vocabulary to screen them against) and `<reason>` (first `<reason>…</reason>` pair on the
|
|
65
|
+
* stripped text, the tag spelled as the prompt spells it, trimmed) are carried when present and `""`
|
|
66
|
+
* when absent; the consumers render their
|
|
67
|
+
* own fallback line from the tool name in that case. The block intent is unambiguous either way, and
|
|
68
|
+
* downgrading a stated block to `parse_error` (⇒ the ask may later auto-deny OR a person may approve)
|
|
69
|
+
* would weaken the classifier's explicit verdict on the exact calls it flagged.
|
|
35
70
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* a second conflicting `<block>yes</block>`, trailing prose, any tail at all — violates the
|
|
39
|
-
* contract and maps to `parse_error` (the ask flows the original human chain, never a silent
|
|
40
|
-
* allow on a malformed response);
|
|
41
|
-
* - a stated BLOCK tolerates a tail (that is where `<category>`/`<reason>` live, and extra
|
|
42
|
-
* content after a block verdict can only ever be judged in the blocking direction) — a
|
|
43
|
-
* `<block>yes</block>` with MISSING/empty `<category>`/`<reason>` still maps to `block`
|
|
44
|
-
* (with "" placeholders): the block intent is unambiguous, and downgrading a stated block
|
|
45
|
-
* to parse_error (⇒ the ask may later auto-deny OR a human may approve) would weaken the
|
|
46
|
-
* classifier's explicit verdict on the exact calls it flagged.
|
|
71
|
+
* `parse_error` is fail-closed: the ask flows the original chain (a person, a durable park, a headless
|
|
72
|
+
* auto-deny) exactly as if auto mode were absent — never a silent allow.
|
|
47
73
|
*/
|
|
48
74
|
export declare function parseAutoModeResponse(text: string): AutoModeVerdict;
|
|
49
75
|
/** What the decider hands the deployment's `classify` hook (the assembled prompt is the hook's job
|
|
@@ -65,10 +91,11 @@ export interface AutoModeDeciderOptions {
|
|
|
65
91
|
/** Consecutive-failure threshold that opens the session breaker (default 3 "连续 N 失败").
|
|
66
92
|
* Failures = unavailable(error|timeout) + parse_error. A successful round (allow/block) resets it. */
|
|
67
93
|
failureThreshold?: number;
|
|
68
|
-
/** Fired ONCE when the breaker opens ("本 session 退回非 auto + 一次性告警").
|
|
94
|
+
/** Fired ONCE when the breaker opens ("本 session 退回非 auto + 一次性告警"). `lastCause` is the failure that tripped it
|
|
95
|
+
* ({@link AutoModeBreakerCause}). */
|
|
69
96
|
onBreakerOpen?: (info: {
|
|
70
97
|
consecutiveFailures: number;
|
|
71
|
-
lastCause:
|
|
98
|
+
lastCause: AutoModeBreakerCause;
|
|
72
99
|
}) => void;
|
|
73
100
|
/**
|
|
74
101
|
* Fired once per `decide` call with the verdict the gate is about to act on and the wall time it
|
|
@@ -117,6 +144,42 @@ export interface AutoModeDecider {
|
|
|
117
144
|
* One instance per run/session — the breaker state is the session's "退回非 auto" latch.
|
|
118
145
|
*/
|
|
119
146
|
export declare function createAutoModeDecider(opts: AutoModeDeciderOptions): AutoModeDecider;
|
|
147
|
+
/**
|
|
148
|
+
* ONE breaker trip as the session-level read face records it (`WiringManifest.autoMode.breaker`): when the
|
|
149
|
+
* latch opened, what tripped it, how many consecutive failures it took, and which run's decider it was.
|
|
150
|
+
* A decider is minted per run (its latch is a RUN fact), so a trip names one leg; the ledger below carries
|
|
151
|
+
* the most recent one forward per session, which is how the session's NEXT leg can say "auto mode fell
|
|
152
|
+
* back to asking on this session, at T, because the classifier timed out three times" — the sentence a
|
|
153
|
+
* shell's doctor line and a capabilities read face owe a person who wonders why auto mode is asking.
|
|
154
|
+
*/
|
|
155
|
+
export interface AutoModeBreakerTrip {
|
|
156
|
+
/** Epoch ms at which the latch opened. */
|
|
157
|
+
readonly openedAtMs: number;
|
|
158
|
+
/** The failure that tripped it — the streak's last failure ({@link AutoModeBreakerCause}). */
|
|
159
|
+
readonly lastCause: AutoModeBreakerCause;
|
|
160
|
+
/** The consecutive-failure count at the trip (the threshold, or more under concurrent rounds). */
|
|
161
|
+
readonly failures: number;
|
|
162
|
+
/** The run whose decider tripped. */
|
|
163
|
+
readonly runId: string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The Runner-lived, per-session record of the most recent breaker trip — the ONE writer is the engine's
|
|
167
|
+
* own `onBreakerOpen` wrap at the arming site (the deployment's alarm hook, when wired, is called after
|
|
168
|
+
* the record lands), the ONE reader the wiring-manifest phase of a later leg. Never persisted (a trip is
|
|
169
|
+
* process-local, like the decider it describes); bounded FIFO so a long-lived Runner cannot grow it
|
|
170
|
+
* without limit — an evicted session simply reads as "no trip recorded", the same as a session that
|
|
171
|
+
* never tripped. Threaded through the trusted `RunInternals` channel exactly as the per-session read-file
|
|
172
|
+
* seats are (the Runner sets it on every prepare it drives; a standalone prepare has none).
|
|
173
|
+
*/
|
|
174
|
+
export declare class AutoModeBreakerLedger {
|
|
175
|
+
private readonly cap;
|
|
176
|
+
private readonly trips;
|
|
177
|
+
constructor(cap?: number);
|
|
178
|
+
/** Record THIS session's most recent trip (replacing an earlier one). */
|
|
179
|
+
record(sessionId: string, trip: AutoModeBreakerTrip): void;
|
|
180
|
+
/** The most recent trip recorded for this session, or undefined (never tripped, or evicted). */
|
|
181
|
+
lastTrip(sessionId: string): AutoModeBreakerTrip | undefined;
|
|
182
|
+
}
|
|
120
183
|
/** The deployment's bounds for the denial limit (`RunnerDeps.autoMode.denialLimit`). Every member
|
|
121
184
|
* optional; an omitted member takes its CC default. A present member with a bad value is REFUSED
|
|
122
185
|
* loudly at construction (never clamped, never read as the default). */
|
package/dist/core/auto-mode.js
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
import { AUTO_MODE_DEFAULT_FAILURE_THRESHOLD, AUTO_MODE_DEFAULT_TIMEOUT_MS, AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS, AUTO_MODE_DENIAL_LIMIT_DEFAULTS } from "./auto-mode-defaults.js";
|
|
2
|
+
export const AUTO_MODE_UNAVAILABLE_CAUSES = ["error", "timeout", "breaker_open"];
|
|
3
|
+
const AUTO_MODE_UNAVAILABLE_CAUSE_SET = new Set(AUTO_MODE_UNAVAILABLE_CAUSES);
|
|
4
|
+
export function isAutoModeUnavailableCause(v) {
|
|
5
|
+
return AUTO_MODE_UNAVAILABLE_CAUSE_SET.has(v);
|
|
6
|
+
}
|
|
7
|
+
export const AUTO_MODE_BREAKER_CAUSES = ["error", "timeout", "parse_error"];
|
|
8
|
+
const AUTO_MODE_BREAKER_CAUSE_SET = new Set(AUTO_MODE_BREAKER_CAUSES);
|
|
9
|
+
export function isAutoModeBreakerCause(v) {
|
|
10
|
+
return AUTO_MODE_BREAKER_CAUSE_SET.has(v);
|
|
11
|
+
}
|
|
2
12
|
export function parseAutoModeResponse(text) {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
13
|
+
const rawAnswers = new Set([...text.matchAll(/<block>(yes|no)\b/gi)].map((m) => m[1].toLowerCase()));
|
|
14
|
+
if (rawAnswers.size > 1)
|
|
15
|
+
return { kind: "parse_error", raw: text };
|
|
16
|
+
const stripped = text.replace(/<thinking>[\s\S]*?<\/thinking>/g, "").replace(/<thinking>[\s\S]*$/, "");
|
|
17
|
+
const hits = [...stripped.matchAll(/<block>(yes|no)\b(<\/block>)?/gi)];
|
|
18
|
+
if (hits.length === 0)
|
|
9
19
|
return { kind: "parse_error", raw: text };
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
const answers = new Set(hits.map((m) => m[1].toLowerCase()));
|
|
21
|
+
if (answers.size !== 1)
|
|
22
|
+
return { kind: "parse_error", raw: text };
|
|
23
|
+
if (!answers.has("yes"))
|
|
13
24
|
return { kind: "allow" };
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const reason = /<reason>([\s\S]*?)<\/reason>/i.exec(t)?.[1]?.trim() ?? "";
|
|
25
|
+
const category = /<category>([\s\S]*?)<\/category>/i.exec(stripped)?.[1]?.trim() ?? "";
|
|
26
|
+
const reason = /<reason>([\s\S]*?)<\/reason>/.exec(stripped)?.[1]?.trim() ?? "";
|
|
17
27
|
return { kind: "block", category, reason };
|
|
18
28
|
}
|
|
19
29
|
export function createAutoModeDecider(opts) {
|
|
@@ -117,6 +127,27 @@ class AutoModeTimeout extends Error {
|
|
|
117
127
|
super("auto-mode classify timeout");
|
|
118
128
|
}
|
|
119
129
|
}
|
|
130
|
+
export class AutoModeBreakerLedger {
|
|
131
|
+
cap;
|
|
132
|
+
trips = new Map();
|
|
133
|
+
constructor(cap = 1024) {
|
|
134
|
+
this.cap = cap;
|
|
135
|
+
}
|
|
136
|
+
record(sessionId, trip) {
|
|
137
|
+
if (this.trips.has(sessionId))
|
|
138
|
+
this.trips.delete(sessionId);
|
|
139
|
+
this.trips.set(sessionId, trip);
|
|
140
|
+
while (this.trips.size > this.cap) {
|
|
141
|
+
const oldest = this.trips.keys().next().value;
|
|
142
|
+
if (oldest === undefined)
|
|
143
|
+
break;
|
|
144
|
+
this.trips.delete(oldest);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
lastTrip(sessionId) {
|
|
148
|
+
return this.trips.get(sessionId);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
120
151
|
export function readDenialLimitFallback(v) {
|
|
121
152
|
if (typeof v !== "object" || v === null)
|
|
122
153
|
return undefined;
|
|
@@ -813,6 +813,14 @@ export type PendingAction = {
|
|
|
813
813
|
* Written only when it is a member of the set; a pre-existing row reads absent (unreported).
|
|
814
814
|
* Display metadata — the resume belts keep reading the gate's own bit. */
|
|
815
815
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
816
|
+
/** #616 (additive, same precedent) — the PARK twin of `AskRequest.classifierUnavailable`: present ⇔ the
|
|
817
|
+
* auto-mode classifier was consulted on the parked ask and could not run, with the verdict's own cause
|
|
818
|
+
* word ({@link import("./auto-mode.js").AutoModeUnavailableCause}). Written only when the cause is a
|
|
819
|
+
* member of the set; a pre-existing row reads absent. Display metadata (a durable card's "asked because
|
|
820
|
+
* the classifier was unavailable" sentence) — the resume belts never read it. */
|
|
821
|
+
classifierUnavailable?: {
|
|
822
|
+
readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
|
|
823
|
+
};
|
|
816
824
|
/**
|
|
817
825
|
* design/80 D-1 §2 (slice 1a.2): the server-minted **opaque** boundInputHash of {@link args} — a
|
|
818
826
|
* SHA-256 (hex) via {@link import("./canonical-json.js").boundInputHashOf}, computed ONCE here at
|
|
@@ -1920,6 +1928,12 @@ export interface CheckpointSummary {
|
|
|
1920
1928
|
* value is a member of the closed set ({@link import("./ask-origin.js").isAskOrigin}) — a bad value
|
|
1921
1929
|
* reads as absent, never as a word. OMITTED when the row carries none (a pre-existing row). */
|
|
1922
1930
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
1931
|
+
/** The row's `PendingAction.tool_approval.classifierUnavailable` (#616), projected for the same one-call
|
|
1932
|
+
* reason. Echoed ONLY when the stored cause is a member of the closed set
|
|
1933
|
+
* ({@link import("./auto-mode.js").isAutoModeUnavailableCause}); OMITTED when the row carries none. */
|
|
1934
|
+
classifierUnavailable?: {
|
|
1935
|
+
readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
|
|
1936
|
+
};
|
|
1923
1937
|
/**
|
|
1924
1938
|
* #438: PRESENCE ONLY — this pending `tool_approval` row's payload carries at least one DIRECTIONAL
|
|
1925
1939
|
* format control (the {@link import("./tool-policy.js").AskRequest.hasBidiControls} character class;
|
|
@@ -2,7 +2,7 @@ import { randomBytes, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { uuidv7 } from "../internal/harness.js";
|
|
3
3
|
import { PROBE_CAUSE_PATH_MAX, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
4
4
|
import { carriesBidiControls } from "./tool-policy.js";
|
|
5
|
-
import { readDenialLimitFallback } from "./auto-mode.js";
|
|
5
|
+
import { isAutoModeUnavailableCause, readDenialLimitFallback } from "./auto-mode.js";
|
|
6
6
|
import { isAskOrigin } from "./ask-origin.js";
|
|
7
7
|
import { checkpointExecutionRecorded, executionRecordDisposition, ExecutionOutcomeConflictError, } from "./checkpoint-execution-record.js";
|
|
8
8
|
import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-rule-model.js";
|
|
@@ -278,6 +278,7 @@ export function summarizeCheckpoint(cp) {
|
|
|
278
278
|
return fb !== undefined ? { denialLimitFallback: fb } : {};
|
|
279
279
|
})(),
|
|
280
280
|
...(tool !== undefined && isAskOrigin(tool.origin) ? { origin: tool.origin } : {}),
|
|
281
|
+
...(tool !== undefined && isAutoModeUnavailableCause(tool.classifierUnavailable?.cause) ? { classifierUnavailable: { cause: tool.classifierUnavailable.cause } } : {}),
|
|
281
282
|
...(hasBidi ? { hasBidiControls: true } : {}),
|
|
282
283
|
};
|
|
283
284
|
}
|
|
@@ -69,15 +69,25 @@ export interface EngineNotice {
|
|
|
69
69
|
* (`MCP_INJECTION_DROP_REASONS`, CLOSED-SETS), `field` names the bad key for `malformed_entry`
|
|
70
70
|
* when known. `"user"` audience; `sessionId` REQUIRED at the mint (a user row with no session
|
|
71
71
|
* routes nowhere — refused, never fabricated). Dedup unit: per leg per server name, the host's.
|
|
72
|
-
* - `"config.models_swapped"` — `Runner.
|
|
73
|
-
* (zero-restart model switching
|
|
74
|
-
* never the
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
72
|
+
* - `"config.models_swapped"` — `Runner.swapDeps` replaced the model catalog generation
|
|
73
|
+
* (zero-restart model switching; the `models` / `tiers` seats of the swappable set). `detail:
|
|
74
|
+
* { models, tiers, occurrenceId }` — key COUNTS only, never the catalog itself. In-flight tasks
|
|
75
|
+
* finish on the models they resolved at prepare (natural snapshot); every later prepare resolves
|
|
76
|
+
* against the new generation. A failed swap (illegal tier binding, a refused candidate) throws
|
|
77
|
+
* atomically and mints nothing.
|
|
78
78
|
* `occurrenceId` (#495, uuidv7 minted at the announcement): the verb carries no run and no
|
|
79
79
|
* session, and the payload is a pair of counts, so two swaps of the same shape were byte-
|
|
80
80
|
* identical lines. Dedup key: (code, occurrenceId).
|
|
81
|
+
* - `"config.read_face_swapped"` — `Runner.swapDeps` replaced the deployment's read-face
|
|
82
|
+
* declaration (`RunnerDeps.readFace`, the `readFace` seat of the swappable set) without a
|
|
83
|
+
* restart. `detail: { from, to, occurrenceId }` — the seat's previous and new value, each
|
|
84
|
+
* `"open"` | `"roots"` | `null` (`null` = no declaration; the engine default `roots` applies).
|
|
85
|
+
* The SEAT is announced, not a leg's resolved face: resolution (task seat, governance, the
|
|
86
|
+
* read-only mount) still runs per prepare. In-flight legs keep the face they prepared under;
|
|
87
|
+
* every later prepare resolves against the new declaration. A candidate outside the value gate
|
|
88
|
+
* (`assertReadFaceValue`) throws atomically and mints nothing. Own `occurrenceId` for the same
|
|
89
|
+
* reason as the catalog line (no run, no session, a two-word payload). Dedup key:
|
|
90
|
+
* (code, occurrenceId).
|
|
81
91
|
* - `"route.fallback_to_primary"` (key↔URL pairing, `src/brain/route-adjudicator.ts`) — a
|
|
82
92
|
* DERIVED-leg model (role/tier/system-default resolution, never a caller-explicit one) failed
|
|
83
93
|
* the pairing pre-flight and the seat fell back to the primary model instead of sinking the
|
|
@@ -90,7 +100,7 @@ export interface EngineNotice {
|
|
|
90
100
|
* session falling back on the same seat used to mint byte-identical lines; `runId` is what
|
|
91
101
|
* tells one task falling back twice apart from two tasks falling back once each.
|
|
92
102
|
* Explicitly-named models never mint this: they refuse at the brain's request gate instead.
|
|
93
|
-
* - `"route.base_url_changed_key_unchanged"` (key↔URL pairing) — `Runner.
|
|
103
|
+
* - `"route.base_url_changed_key_unchanged"` (key↔URL pairing) — `Runner.swapDeps` moved a
|
|
94
104
|
* same-name entry's `baseUrl` while its Model-visible credential half (auth-bearing headers)
|
|
95
105
|
* did not change: legal (a provider changing domains), but worth one loud line — if the
|
|
96
106
|
* PROVIDER changed, the credential reference needs the same update. Advisory only, never a
|
|
@@ -484,6 +494,17 @@ export interface EngineNotice {
|
|
|
484
494
|
* access scope of the read that found the gap — from the continuation read faces (SendMessage preflight /
|
|
485
495
|
* AgentTranscript's durable leg); the per-call honest refusals are unchanged, and the declared
|
|
486
496
|
* tier is NOT auto-downgraded (declaration-制 — observation reports, it never re-adjudicates).
|
|
497
|
+
* - `"delegation.ask_unresolvable"` (#648) — an `ask` reached its FINAL deny with nobody having judged
|
|
498
|
+
* it: the approver consulted for the call (the run's own seat, or an ancestor's frozen seat on a
|
|
499
|
+
* delegated child — the sync / workflow lanes, or a background child outside the park family)
|
|
500
|
+
* answered `unavailable` (no operator reachable for this ask) and no durable approval park took
|
|
501
|
+
* the call afterwards. The deny itself is unchanged (`tool_end.gate.settlement.kind:
|
|
502
|
+
* "approver_unavailable"`, the fail-closed sentence as the tool result); this is its person-facing
|
|
503
|
+
* half. Minted at the one gate exit both unavailable deny arms reach, once per tool call, by
|
|
504
|
+
* `ask-unresolvable-notice.ts`. `detail: { sessionId, toolName, toolCallId, settlementKind,
|
|
505
|
+
* parkLaneExisted }` — `parkLaneExisted` says whether a park lane was armed and declined/failed
|
|
506
|
+
* (`true`) or none existed (`false`, the usual shape). Audience `"user"`: the refused work is the
|
|
507
|
+
* session user's own; the operator hears it through the sink like every code.
|
|
487
508
|
*
|
|
488
509
|
* - `"memory.consolidation_recommended"` (design/339 §2.2/§6.2) — the engine-minted per-scope
|
|
489
510
|
* session count crossed the consolidation thresholds (time gate open ∧ enough distinct
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -82,7 +82,12 @@ export declare function persistedRuleMandateOf(marks: {
|
|
|
82
82
|
/** #502: the surviving ask's engine-stamped `probeMandated` — see
|
|
83
83
|
* {@link import("./types.js").ReversibilityVerdict.mandated}. */
|
|
84
84
|
probeMandated?: boolean;
|
|
85
|
-
|
|
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;
|
|
86
91
|
/**
|
|
87
92
|
* Run the lanes over the pass: the org layer over the fold's verdict, then — for a surviving ask — the rewrite
|
|
88
93
|
* 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
|
@@ -5,6 +5,9 @@ import { inlineUntrusted } from "./untrusted-text.js";
|
|
|
5
5
|
import { isRuleBehavior } from "./permission-rule-model.js";
|
|
6
6
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
7
7
|
import { applyPersistedTightening, disclosedRuleSet } from "./persisted-rule-arms.js";
|
|
8
|
+
import { readOnlyShellVerdict } from "./read-only-shell.js";
|
|
9
|
+
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
10
|
+
import { catalogRuleFaceOf } from "./tool-registry.js";
|
|
8
11
|
import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
9
12
|
import { exitGate, traceHookCrash } from "./runner/gate-exit.js";
|
|
10
13
|
function isPlainOwnRecord(x) {
|
|
@@ -144,15 +147,17 @@ export function normalizePersistedRuleHit(hit) {
|
|
|
144
147
|
export function persistedRuleMandateOf(marks) {
|
|
145
148
|
return marks.probeMandated === true
|
|
146
149
|
? "probe_mandate"
|
|
147
|
-
: marks.
|
|
148
|
-
? "
|
|
149
|
-
: marks.
|
|
150
|
-
?
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
:
|
|
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;
|
|
156
161
|
}
|
|
157
162
|
function parkWith(suspendAsk, parkArgs, carry) {
|
|
158
163
|
return suspendAsk(...parkArgs, carry);
|
|
@@ -160,9 +165,11 @@ function parkWith(suspendAsk, parkArgs, carry) {
|
|
|
160
165
|
export async function runGateLanes(pass) {
|
|
161
166
|
const { input, toolName, toolCallId, callSignal, ledger, notifier, screening, adjudicate, resolveAsk, suspendAsk } = pass;
|
|
162
167
|
let orgRealApprovalRequired = false;
|
|
168
|
+
let markedThisRound;
|
|
169
|
+
const markedUnresolvable = () => (markedThisRound ??= input.isMarkedUnresolvable?.(input.event.toolCallId) === true);
|
|
163
170
|
const askOriginFacts = (org, ruleStore) => ({
|
|
164
171
|
contentQuestion: toolName === ASK_USER_QUESTION_TOOL_NAME,
|
|
165
|
-
markedUnresolvable:
|
|
172
|
+
markedUnresolvable: markedUnresolvable(),
|
|
166
173
|
org,
|
|
167
174
|
...(ruleStore !== undefined ? { ruleStore } : {}),
|
|
168
175
|
tightened: pass.tightenedBy,
|
|
@@ -269,6 +276,7 @@ export async function runGateLanes(pass) {
|
|
|
269
276
|
pass.deniedBy = "persisted_rule";
|
|
270
277
|
}
|
|
271
278
|
if (pass.decision.action === "ask") {
|
|
279
|
+
pass.decision = { ...pass.decision };
|
|
272
280
|
if (pass.policyRewrite !== undefined) {
|
|
273
281
|
pass.currentInput = pass.policyRewrite;
|
|
274
282
|
pass.req.args = pass.policyRewrite;
|
|
@@ -278,18 +286,19 @@ export async function runGateLanes(pass) {
|
|
|
278
286
|
shellGated: input.shellGated,
|
|
279
287
|
irreversibility: input.irreversibility,
|
|
280
288
|
probeMandated: pass.decision.action === "ask" && pass.decision.probeMandated === true,
|
|
289
|
+
probeUnanswered: pass.probeUnanswered === true,
|
|
281
290
|
});
|
|
282
291
|
let personalEvidence = { dotsAbsent: input.persistedRules === undefined ? "not_wired" : "not_adjudicated" };
|
|
283
292
|
if (laneAnswer?.unreadable === true)
|
|
284
293
|
personalEvidence = { dotsAbsent: "unavailable" };
|
|
285
294
|
let laneCoverage;
|
|
286
|
-
|
|
287
|
-
!orgRealApprovalRequired &&
|
|
295
|
+
const allowLayerMayClear = () => !orgRealApprovalRequired &&
|
|
288
296
|
pass.decision.action === "ask" &&
|
|
289
297
|
pass.decision.requiresRealApproval !== true &&
|
|
290
298
|
pass.decision.decisionReason !== "hook" &&
|
|
291
299
|
pass.req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
292
|
-
|
|
300
|
+
!markedUnresolvable();
|
|
301
|
+
if (input.persistedRules && allowLayerMayClear()) {
|
|
293
302
|
const answer = laneAnswer ?? {};
|
|
294
303
|
const hitRules = answer.hit?.behavior === "allow" ? answer.hit.rules : undefined;
|
|
295
304
|
laneCoverage = answer.coverage;
|
|
@@ -328,11 +337,13 @@ export async function runGateLanes(pass) {
|
|
|
328
337
|
const shownRule = disclosedRuleSet(hitRules);
|
|
329
338
|
const mandateNoun = persistedRuleMandate === "probe_mandate"
|
|
330
339
|
? "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"
|
|
331
|
-
: persistedRuleMandate === "
|
|
332
|
-
? "this
|
|
333
|
-
: persistedRuleMandate
|
|
334
|
-
? "this
|
|
335
|
-
:
|
|
340
|
+
: persistedRuleMandate === "probe_unanswered"
|
|
341
|
+
? "the reversibility check did not answer for this call (it timed out or failed), so whether the call reads outside the directories allowed for this session is unknown — it is cleared by confirming this call, never by a standing allow rule"
|
|
342
|
+
: persistedRuleMandate === "operator_always"
|
|
343
|
+
? "this deployment mandates per-call confirmation for shell commands (shellGate: always)"
|
|
344
|
+
: persistedRuleMandate !== undefined
|
|
345
|
+
? "this tool carries egress/irreversibility marks (a mandated confirmation a rule cannot clear)"
|
|
346
|
+
: "an explicit ask rule matched this call (a person's ask-me-each-time outranks a standing allow rule)";
|
|
336
347
|
pass.decision = {
|
|
337
348
|
...pass.decision,
|
|
338
349
|
persistedRuleShadowed: shownRule,
|
|
@@ -347,6 +358,21 @@ export async function runGateLanes(pass) {
|
|
|
347
358
|
const stamped = { ...pass.decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
|
|
348
359
|
pass.decision = { ...stamped, origin: askOriginOf(stamped, originFacts) };
|
|
349
360
|
}
|
|
361
|
+
if (allowLayerMayClear() && pass.decision.action === "ask" && persistedRuleMandate === undefined && pass.decision.matchedAskRule === undefined && pass.req.toolName === COMMAND_RULE_TOOL) {
|
|
362
|
+
const commandParam = catalogRuleFaceOf(COMMAND_RULE_TOOL)?.primaryParams[0] ?? "command";
|
|
363
|
+
const args = pass.req.args;
|
|
364
|
+
const command = typeof args === "object" && args !== null && !Array.isArray(args) ? args[commandParam] : undefined;
|
|
365
|
+
const backgrounded = typeof args === "object" && args !== null && args.run_in_background === true;
|
|
366
|
+
if (typeof command === "string" && !backgrounded && readOnlyShellVerdict(command).readOnly) {
|
|
367
|
+
pass.decision = {
|
|
368
|
+
action: "allow",
|
|
369
|
+
message: "the command is read-only (upstream's read-only command tables) — allowed without asking",
|
|
370
|
+
decisionReason: "read_only",
|
|
371
|
+
...(pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {}),
|
|
372
|
+
};
|
|
373
|
+
await notifier.notifyAsync(() => input.onReadOnlyAllowed?.({ toolName: pass.req.toolName, toolCallId, command }), "toolGate.readOnlyAllowed");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
350
376
|
if (pass.decision.action === "ask")
|
|
351
377
|
ledger.supersede();
|
|
352
378
|
if (input.autoMode && pass.decision.action === "ask" && pass.decision.origin !== undefined && classifierMayAnswer(pass.decision.origin)) {
|
|
@@ -385,6 +411,9 @@ export async function runGateLanes(pass) {
|
|
|
385
411
|
pass.deniedBy = "classifier";
|
|
386
412
|
}
|
|
387
413
|
}
|
|
414
|
+
else if (verdict.kind === "unavailable") {
|
|
415
|
+
pass.decision = { ...pass.decision, classifierUnavailable: { cause: verdict.cause } };
|
|
416
|
+
}
|
|
388
417
|
}
|
|
389
418
|
if (input.sandboxAdmission !== undefined &&
|
|
390
419
|
pass.decision.action === "ask" &&
|
|
@@ -425,6 +454,7 @@ export async function runGateLanes(pass) {
|
|
|
425
454
|
...(callSignal !== undefined ? { signal: callSignal } : {}),
|
|
426
455
|
...(d.action === "ask" && d.denialLimitFallback !== undefined ? { denialLimitFallback: unarmedWindow(d.denialLimitFallback) } : {}),
|
|
427
456
|
...(d.action === "ask" && d.origin !== undefined ? { origin: d.origin } : {}),
|
|
457
|
+
...(d.action === "ask" && d.classifierUnavailable !== undefined ? { classifierUnavailable: { cause: d.classifierUnavailable.cause } } : {}),
|
|
428
458
|
});
|
|
429
459
|
if (suspendAsk && pass.decision.action === "ask") {
|
|
430
460
|
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];
|
|
@@ -492,6 +522,16 @@ export async function runGateLanes(pass) {
|
|
|
492
522
|
return { suspend: suspended, preToolContext: pass.preToolContext };
|
|
493
523
|
}
|
|
494
524
|
}
|
|
525
|
+
const noteUnresolvable = (r) => {
|
|
526
|
+
if (r.action !== "deny" || r.approverUnavailable !== true)
|
|
527
|
+
return;
|
|
528
|
+
try {
|
|
529
|
+
input.onAskUnresolvable?.({ toolName, toolCallId, settlementKind: "approver_unavailable", parkLaneExisted: suspendAsk !== undefined });
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
noteUnresolvable(resolved);
|
|
495
535
|
if (askBeforeResolve.action === "ask" && askBeforeResolve.denialLimitFallback !== undefined) {
|
|
496
536
|
if (pass.decision.action === "allow") {
|
|
497
537
|
input.autoMode?.denialTracking?.recordAllow();
|
|
@@ -620,10 +660,12 @@ export async function runGateLanes(pass) {
|
|
|
620
660
|
editRewrittenSinceHuman = true;
|
|
621
661
|
}
|
|
622
662
|
const editAskSnapshot = { ...recheck, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
|
|
663
|
+
markedThisRound = undefined;
|
|
623
664
|
const editAskDecision = { ...editAskSnapshot, origin: askOriginOf(editAskSnapshot, askOriginFacts(editOrg.org, editRuleStore)) };
|
|
624
665
|
const editAskReq = { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
|
|
625
666
|
const rr = await (callSignal !== undefined ? resolveAsk(editAskDecision, editAskReq, callSignal) : resolveAsk(editAskDecision, editAskReq));
|
|
626
667
|
ledger.fromResolved(rr, editAskDecision, toolName);
|
|
668
|
+
noteUnresolvable(rr);
|
|
627
669
|
if (rr.action !== "allow") {
|
|
628
670
|
editDenied = rr;
|
|
629
671
|
pass.deniedBy = "ask_resolution";
|
|
@@ -103,7 +103,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
103
103
|
* src/ for notice mint shapes and names any code that is minted but unregistered, or registered but
|
|
104
104
|
* no longer minted.
|
|
105
105
|
*/
|
|
106
|
-
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_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", "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"];
|
|
106
|
+
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
107
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
108
108
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
109
109
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -97,6 +97,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
97
97
|
"config.env_timeout_discarded",
|
|
98
98
|
"config.materialize_env_discarded",
|
|
99
99
|
"config.models_swapped",
|
|
100
|
+
"config.read_face_swapped",
|
|
100
101
|
"config.read_face_deployment_clamped",
|
|
101
102
|
"config.tool_model_gate_removed",
|
|
102
103
|
"config.tool_model_gate_unknown_class",
|
|
@@ -113,6 +114,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
113
114
|
"classifier.denial_limit",
|
|
114
115
|
"checkpoint.execution_outcome_unrecorded",
|
|
115
116
|
"delegation.transcript_integrity",
|
|
117
|
+
"delegation.ask_unresolvable",
|
|
116
118
|
"mcp.injection_dropped",
|
|
117
119
|
"mcp.revocation_probe_failed",
|
|
118
120
|
"workflow.governance_key_stripped",
|
|
@@ -160,10 +162,12 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
160
162
|
"memory.capture_opted_out": "user",
|
|
161
163
|
"memory.capture_optout_unpersisted": "user",
|
|
162
164
|
"memory.consolidation_withheld": "user",
|
|
165
|
+
"delegation.ask_unresolvable": "user",
|
|
163
166
|
"config.autocompact_window_clamped": "operator",
|
|
164
167
|
"config.env_timeout_discarded": "operator",
|
|
165
168
|
"config.materialize_env_discarded": "operator",
|
|
166
169
|
"config.models_swapped": "operator",
|
|
170
|
+
"config.read_face_swapped": "operator",
|
|
167
171
|
"config.read_face_deployment_clamped": "operator",
|
|
168
172
|
"config.tool_model_gate_removed": "operator",
|
|
169
173
|
"config.tool_model_gate_unknown_class": "operator",
|