@sema-agent/core 7.13.0 → 7.15.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 +110 -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 +30 -11
- package/dist/core/ask-origin.js +10 -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 +23 -31
- package/dist/core/checkpoint-store.js +7 -4
- package/dist/core/engine-notice.d.ts +3 -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.js +21 -12
- package/dist/core/gate-outcome.d.ts +42 -3
- package/dist/core/gate-outcome.js +22 -0
- package/dist/core/governance-codes.js +1 -1
- package/dist/core/hooks.d.ts +9 -19
- package/dist/core/hooks.js +6 -4
- 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 +5 -0
- package/dist/core/runner/contracts.d.ts +2 -2
- package/dist/core/runner/denial-limit-arms.d.ts +7 -9
- package/dist/core/runner/denial-limit-arms.js +8 -10
- 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 +3 -1
- package/dist/core/runner/permission-rule-lanes.js +1 -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-defer-classify.d.ts +1 -1
- package/dist/core/runner/prepare-defer-classify.js +12 -10
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -5
- package/dist/core/runner/prepare-gate-stations.js +2 -2
- package/dist/core/runner/prepare-hands-readface.d.ts +18 -12
- package/dist/core/runner/prepare-hands-readface.js +32 -43
- package/dist/core/runner/prepare-inherited-gate.d.ts +7 -5
- 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-park-ask.d.ts +5 -6
- package/dist/core/runner/prepare-park-ask.js +3 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +2 -2
- package/dist/core/runner/prepare-policy-chain.js +7 -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-question-face.d.ts +10 -8
- package/dist/core/runner/prepare-question-face.js +1 -3
- package/dist/core/runner/prepare-safety-scan.js +1 -1
- package/dist/core/runner/prepare-task.js +60 -53
- 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/run-compaction-machinery.js +2 -0
- package/dist/core/runner/run-harness-handlers.js +3 -1
- package/dist/core/runner/stream-settle-backstop.js +1 -1
- package/dist/core/runner/tool-defer-gate.d.ts +86 -0
- package/dist/core/runner/tool-defer-gate.js +57 -0
- package/dist/core/runner/tool-disclosure.d.ts +0 -36
- package/dist/core/runner/tool-disclosure.js +0 -43
- package/dist/core/runner-deps.d.ts +16 -9
- 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-spec.d.ts +8 -2
- package/dist/core/terminal-cause.d.ts +6 -2
- package/dist/core/tool-policy.d.ts +34 -28
- package/dist/core/tool-policy.js +29 -5
- package/dist/core/tool-roster.js +2 -0
- package/dist/core/tool-spec.d.ts +20 -16
- package/dist/core/types.d.ts +3 -2
- 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 +7 -6
- package/dist/index.js +5 -4
- 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/tools/fs/fs-bash.d.ts +41 -0
- package/dist/tools/fs/fs-bash.js +86 -29
- 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 +29 -1
|
@@ -1370,15 +1370,22 @@ export interface RunnerDeps {
|
|
|
1370
1370
|
removedBytes: number;
|
|
1371
1371
|
}) => void;
|
|
1372
1372
|
/**
|
|
1373
|
-
* Deferred-tool disclosure policy (design/36).
|
|
1374
|
-
*
|
|
1375
|
-
*
|
|
1376
|
-
*
|
|
1377
|
-
*
|
|
1378
|
-
*
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1381
|
-
*
|
|
1373
|
+
* Deferred-tool disclosure policy (design/36). Two rules govern deferral. ① The caller's list is
|
|
1374
|
+
* literal: {@link TaskSpec.deferTools} names defer on every request, `ToolSpec.alwaysLoad` /
|
|
1375
|
+
* {@link TaskSpec.alwaysLoadTools} pins inline on every request (judged first) — neither reads this
|
|
1376
|
+
* knob. ② The engine's own candidates — `ToolSpec.defer === true`, every REMOTE-PROTOCOL tool name
|
|
1377
|
+
* (MCP **and** A2A peer tools), and built-in tools that declare their own `defer` — sit behind ONE
|
|
1378
|
+
* threshold gate, all or nothing: deferred iff their inlined size (name + description + schema; the
|
|
1379
|
+
* candidate set only — the caller's list, pins and non-candidates are not measured) reaches 10% of the
|
|
1380
|
+
* model's context window; below that they ship inline in full and no ToolSearch is mounted.
|
|
1381
|
+
*
|
|
1382
|
+
* `"auto"` is a CANDIDATE-WIDENING knob, not a second gate: it adds every caller spec — undeclared
|
|
1383
|
+
* caller tools included — to the candidate set, measured and decided by the same threshold. Engine
|
|
1384
|
+
* built-ins the deployment did not name are candidates under no setting. An explicit opt-in (council
|
|
1385
|
+
* minor #6) because it makes `tools[]` depend on the caller roster's total weight, which a deployment
|
|
1386
|
+
* with many thin tools may not want. Any other value refuses at prepare (`config.defer_mode_invalid`).
|
|
1387
|
+
* `classifyDeferred` / `deferralGate` in `src/core/runner/tool-disclosure.ts` are the single authority
|
|
1388
|
+
* if this text drifts.
|
|
1382
1389
|
*/
|
|
1383
1390
|
deferMode?: "auto";
|
|
1384
1391
|
/**
|
|
@@ -84,6 +84,19 @@ export interface WorkflowGovernanceBaseline {
|
|
|
84
84
|
* These six are the caps the ENGINE enforces; `allowUltracode`/`allowBypassPermissions` are shell-UX /
|
|
85
85
|
* service-settings-layer concerns (see {@link RunnerDeps.runtimeCapsResolver}).
|
|
86
86
|
*/
|
|
87
|
+
/**
|
|
88
|
+
* #692 C-d — WHICH plane's ratchet denied auto mode, when the resolver knows: the three settings sources
|
|
89
|
+
* CC 2.1.250's `permissions.disableAutoMode` can come from, as the resolver folds them into the one deny bit
|
|
90
|
+
* ({@link RuntimeCaps.autoMode} `=== false`). `org` = a managed / organization policy plane; `local` = the
|
|
91
|
+
* machine's local settings; `settings` = the user's / project's settings file. Read ONLY beside the deny bit
|
|
92
|
+
* (a source with no denial is a contradiction the value screen refuses as a resolver fault); projected onto
|
|
93
|
+
* the leg's manifest read face (`WiringManifest.autoMode.deniedSource`) so a shell can say "your organization
|
|
94
|
+
* turned auto mode off" vs "your settings did" instead of one word for both.
|
|
95
|
+
*/
|
|
96
|
+
export declare const AUTO_MODE_DENY_SOURCES: readonly ["org", "local", "settings"];
|
|
97
|
+
export type AutoModeDenySource = (typeof AUTO_MODE_DENY_SOURCES)[number];
|
|
98
|
+
/** Membership test for {@link AutoModeDenySource}. */
|
|
99
|
+
export declare function isAutoModeDenySource(v: unknown): v is AutoModeDenySource;
|
|
87
100
|
export interface RuntimeCaps {
|
|
88
101
|
/** `false` DENIES workflow self-orchestration for this principal server-side — even on a deployment that is
|
|
89
102
|
* capable ({@link RunnerDeps.workflowScriptRunner} + governance). The third stage of the workflows gate.
|
|
@@ -136,6 +149,14 @@ export interface RuntimeCaps {
|
|
|
136
149
|
* A denial resolves to a no-op (asks flow the original chain; no warn — the gate, not a mistake).
|
|
137
150
|
*/
|
|
138
151
|
autoMode?: boolean;
|
|
152
|
+
/**
|
|
153
|
+
* #692 C-d (additive) — WHICH source's ratchet set {@link autoMode} to `false`, when the resolver folded
|
|
154
|
+
* several ({@link AutoModeDenySource}). Optional: a resolver that does not track sources omits it and the
|
|
155
|
+
* manifest reads `denied` with no source. Present with `autoMode` not `false`, or spelled outside the set,
|
|
156
|
+
* is a resolver FAULT (announced through `onError`, the deny bit coined fail-closed, `resolver_fault` on the
|
|
157
|
+
* manifest) — a source named for a denial that did not happen is a marshalling error, never a fact.
|
|
158
|
+
*/
|
|
159
|
+
autoModeDenySource?: AutoModeDenySource;
|
|
139
160
|
/**
|
|
140
161
|
* design/383 §3.1 — may this principal declare a session memory-capture OPT-OUT
|
|
141
162
|
* (`TaskSpec.memory.capture: "off"` / the Runner flip verb)? `false` = the deployment REQUIRES
|
|
@@ -97,7 +97,16 @@ export type BrainStatusPhase = "rate_limited" | "retrying" | "reconnecting" | "c
|
|
|
97
97
|
* - `connect_refused` — the attempt got a definite negative about the target itself (nothing accepts
|
|
98
98
|
* at that address, or the name has no address). This is the class the SHORT retry lane serves.
|
|
99
99
|
* - `transport` — any other transport-level failure: a connect timeout, a reset, a mid-stream
|
|
100
|
-
* tear
|
|
100
|
+
* tear. No verdict about the target; the full ladder applies.
|
|
101
|
+
* - `stall` — no failure was observed on the link at all: THIS process cancelled its own
|
|
102
|
+
* request because one of its stall watchdogs reached its deadline while the stream produced
|
|
103
|
+
* nothing. The distinction from `transport` is WHO GAVE UP, and nothing more: a `transport` frame
|
|
104
|
+
* reports something that happened to the call, a `stall` frame reports a local deadline. It is not
|
|
105
|
+
* a verdict on the remedy — a blackholed connection (no reset, no close) produces silence, and
|
|
106
|
+
* silence is what a watchdog measures, so a `stall` can still turn out to be a network fault; what
|
|
107
|
+
* it always tells you truthfully is that the request was cancelled here, at a configured deadline,
|
|
108
|
+
* rather than lost out there. Reporting these as `transport` asserted the opposite — that the link
|
|
109
|
+
* had dropped — when nothing about the link was known at all.
|
|
101
110
|
* - `rate_limit` — the provider asked the caller to slow down.
|
|
102
111
|
* - `server` — the provider reported a failure on its own side.
|
|
103
112
|
* - `http` — a response the status predicate calls terminal, retried anyway because the
|
|
@@ -105,7 +114,7 @@ export type BrainStatusPhase = "rate_limited" | "retrying" | "reconnecting" | "c
|
|
|
105
114
|
* - `output_cap` — not a failure of the connection at all: the request is being re-sent with a
|
|
106
115
|
* lowered output cap after the provider reported the context limit exceeded (no backoff).
|
|
107
116
|
*/
|
|
108
|
-
export type BrainRetryErrClass = "connect_refused" | "transport" | "rate_limit" | "server" | "http" | "output_cap";
|
|
117
|
+
export type BrainRetryErrClass = "connect_refused" | "transport" | "stall" | "rate_limit" | "server" | "http" | "output_cap";
|
|
109
118
|
/** design/99 §E3/§E10 — the payload of a {@link TaskEvent} `status` event (and the brain→runner signal). */
|
|
110
119
|
export interface BrainStatus {
|
|
111
120
|
phase: BrainStatusPhase;
|
|
@@ -3,6 +3,7 @@ import { summarizeWorkflowRun } from "./workflow-run-store.js";
|
|
|
3
3
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
4
4
|
import { boundedRedactedSummary } from "./untrusted-egress.js";
|
|
5
5
|
import { clipWithFilePointer } from "./tool-errors.js";
|
|
6
|
+
import { announceEnvKnob } from "./env-knob-announce.js";
|
|
6
7
|
export function mintCompletionId(target) {
|
|
7
8
|
if (target.completionId === undefined)
|
|
8
9
|
target.completionId = uuidv7();
|
|
@@ -61,14 +62,15 @@ const TASK_OUTPUT_DEFAULT_CHARS = 32_000;
|
|
|
61
62
|
export const TASK_OUTPUT_MAX_CHARS = 160_000;
|
|
62
63
|
const TASK_OUTPUT_MIN_CHARS = 512;
|
|
63
64
|
function taskMaxOutputChars() {
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
65
|
+
const rawEnv = process.env.TASK_MAX_OUTPUT_LENGTH;
|
|
66
|
+
if (rawEnv === undefined || rawEnv.trim() === "")
|
|
66
67
|
return TASK_OUTPUT_DEFAULT_CHARS;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const raw = rawEnv.trim();
|
|
69
|
+
const n = /^\d+$/.test(raw) ? parseInt(raw, 10) : Number.NaN;
|
|
70
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
71
|
+
announceEnvKnob(`TASK_MAX_OUTPUT_LENGTH=${rawEnv} was ignored — it is not a positive whole number of characters. Using ${TASK_OUTPUT_DEFAULT_CHARS} instead.`);
|
|
71
72
|
return TASK_OUTPUT_DEFAULT_CHARS;
|
|
73
|
+
}
|
|
72
74
|
return Math.min(Math.max(n, TASK_OUTPUT_MIN_CHARS), TASK_OUTPUT_MAX_CHARS);
|
|
73
75
|
}
|
|
74
76
|
export function clipTaskOutput(s, fullOutputPath) {
|
|
@@ -25,6 +25,14 @@ import type { NestedUsage } from "./tool-spec.js";
|
|
|
25
25
|
* Orchestrators that map an unexpected `suspended` should map an unexpected `needs_review` the same
|
|
26
26
|
* hard-boundary way (it is, like `suspended`, only safe at the top-level `runTask` boundary).
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* Terminal vs waiting. Three words are TERMINAL — the leg is over and nobody is expected to act: `completed`,
|
|
30
|
+
* `failed`, and `blocked` (the AGENT's own report, through `report_blocked`, that it cannot finish and why —
|
|
31
|
+
* a self-report, not a pause; it waits for nobody). Two words are WAITING — a person's decision resumes the
|
|
32
|
+
* same leg through `runner.resume(token, …)`: `suspended` (a durable pre-action approval of a tool call) and
|
|
33
|
+
* `needs_review` (a durable review pause). The task-registry face spells the two waiting words as `parked`.
|
|
34
|
+
* A consumer that files `blocked` beside `suspended` is waiting on a decision that never comes.
|
|
35
|
+
*/
|
|
28
36
|
export type TaskStatus = "completed" | "blocked" | "failed" | "suspended" | "needs_review";
|
|
29
37
|
/**
|
|
30
38
|
* RB-439-a — one remote-workspace lifecycle failure, reported as data on {@link TaskResult.remoteEnvFailures}.
|
|
@@ -598,6 +606,13 @@ export interface TaskResult {
|
|
|
598
606
|
* the compaction subtotal). OBSERVE-ONLY (never gates budget). The workflow display is unaffected (its agents
|
|
599
607
|
* fail-on-suspend, never durably resume). A `spentToolCalls` ledger fold is a follow-on. */
|
|
600
608
|
toolCalls?: number;
|
|
609
|
+
/** #692 C-b (additive): present (`true`) when the usage figures on this face are a LOWER BOUND rather than a
|
|
610
|
+
* measurement — at least one model round of the leg reported no usage frame (the brain's `usageMissing`
|
|
611
|
+
* mark; the same word the per-turn `turn_end` frame carries), or the result was minted by a host / an
|
|
612
|
+
* orchestration lane that never ran a model round it could measure (a refused spawn's journal entry, a
|
|
613
|
+
* prepare-time refusal, a synthetic abort). The numeric members stay required and stay what was counted:
|
|
614
|
+
* `tokens: 0` beside this key reads "unknown", never "free". Absent ⇔ every round reported usage. */
|
|
615
|
+
usageMissing?: true;
|
|
601
616
|
/** Prompt (input) tokens that MISSED the prefix cache, summed across the task's turns and normalized
|
|
602
617
|
* across providers (Anthropic already reports `input_tokens` this way; OpenAI/vLLM include the cache
|
|
603
618
|
* in theirs, so the subsets are removed). Mutually exclusive with `cachedTokens`/`cacheWriteTokens*`;
|
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -31,6 +31,10 @@ export type ImageInput = {
|
|
|
31
31
|
url: string;
|
|
32
32
|
};
|
|
33
33
|
/** A single self-contained task with its full configuration. */
|
|
34
|
+
/** The shell-gate doctrine vocabulary — {@link TaskSpec.shellGate}'s value set, and the word the engine persists on a
|
|
35
|
+
* shell-gated ask (`RiskDescriptor.shellGateDoctrine`) and folds across a delegation chain (max-rank: `off` <
|
|
36
|
+
* `classify` < `always`). One closed set; every seat that names a doctrine spells it through this alias. */
|
|
37
|
+
export type ShellGateDoctrine = "off" | "classify" | "always";
|
|
34
38
|
export interface TaskSpec {
|
|
35
39
|
taskId?: string;
|
|
36
40
|
/** The instruction / latest user message for this turn. */
|
|
@@ -400,6 +404,8 @@ export interface TaskSpec {
|
|
|
400
404
|
* included — `ToolSpec.defer` only covers caller specs): wire names listed here ship as
|
|
401
405
|
* lightweight placeholders (name + one-line hint, schema bytes OFF the cache prefix) and are
|
|
402
406
|
* activated on demand via the injected ToolSearch (design/36 materialization — prefix untouched).
|
|
407
|
+
* This list is LITERAL — an intentional deferral, deferred on every request regardless of the
|
|
408
|
+
* deferral gate ({@link RunnerDeps.deferMode}) and not counted toward its threshold.
|
|
403
409
|
* "Default-on but not exposed" = the deployment lists the tool here on requests where no
|
|
404
410
|
* activation source fired (e.g. Workflow unless the user said "workflow"/"ultracode"); omitting
|
|
405
411
|
* the name on a later request mounts it fully (activation = not deferring). Unknown names are
|
|
@@ -445,7 +451,7 @@ export interface TaskSpec {
|
|
|
445
451
|
* direction; CC 220 `alwaysLoad` 对位): names listed here are NEVER deferred — not by
|
|
446
452
|
* `ToolSpec.defer`, not by the MCP constant-defer arm (besides the server's own
|
|
447
453
|
* `_meta["anthropic/alwaysLoad"]` declaration this list is the only channel that keeps a chosen
|
|
448
|
-
* MCP tool's full schema inlined), not by `deferTools`, not by `deferMode
|
|
454
|
+
* MCP tool's full schema inlined), not by `deferTools`, not by the deferral gate under any `deferMode`. Unknown names
|
|
449
455
|
* are ignored (the valve only subtracts). Names also in `excludeTools` stay excluded (exclusion
|
|
450
456
|
* wins — an unmounted tool has no schema to keep inline).
|
|
451
457
|
*/
|
|
@@ -862,7 +868,7 @@ export interface TaskSpec {
|
|
|
862
868
|
* task config — a resume that omits it leaves the resumed run's SUBSEQUENT `bash` calls ungated (the approved
|
|
863
869
|
* pending call itself runs once, having been adjudicated). Re-pass the same value the original task used.
|
|
864
870
|
*/
|
|
865
|
-
shellGate?:
|
|
871
|
+
shellGate?: ShellGateDoctrine;
|
|
866
872
|
/**
|
|
867
873
|
* The caller's AUTO-MODE INTENT for this task — the "user turned auto on" half of the classifier
|
|
868
874
|
* arming (CC 2.1.250 polarity: auto mode is something the USER enables at the permission-mode
|
|
@@ -20,7 +20,10 @@ import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
|
20
20
|
* the one cause→plane derivation, for the faces that still speak the word.
|
|
21
21
|
* - `completed` — the run finished (a person's clean halt included — see `TaskResult.haltedByUser`).
|
|
22
22
|
* - `failed` — a limit, a provider failure, an abort, an invalid output…
|
|
23
|
-
* - `blocked` — the
|
|
23
|
+
* - `blocked` — the AGENT reported, through `report_blocked`, that it cannot finish, and why. A TERMINAL
|
|
24
|
+
* cause: the leg is over and it waits for nobody (nothing to resume, no token). The waiting-on-a-person
|
|
25
|
+
* states are the `paused` cause's two status words — `suspended` (a pre-action approval of a tool
|
|
26
|
+
* call) and `needs_review` (a review pause); the task-registry face spells both as `parked`.
|
|
24
27
|
* - `paused` — a durable pause committed a checkpoint and the run is resumable.
|
|
25
28
|
* A `GateOutcome` is the record of ONE gate pass (per call); a `TerminalCause` is the record of ONE run
|
|
26
29
|
* (per leg); a paused run's `gate` and the park row's `gate.kind` are the same word, joined by the token.
|
|
@@ -73,7 +76,8 @@ export type TerminalCause = {
|
|
|
73
76
|
nestedPause?: PausedCause;
|
|
74
77
|
} | {
|
|
75
78
|
kind: "blocked";
|
|
76
|
-
/** Why the agent could not finish.
|
|
79
|
+
/** Why the agent could not finish — the agent's own words from its `report_blocked` call. Terminal: the
|
|
80
|
+
* run is over and nobody is being waited on (contrast `paused`, whose token is the resume capability). */
|
|
77
81
|
reason: string;
|
|
78
82
|
} | {
|
|
79
83
|
kind: "paused";
|
|
@@ -106,6 +106,27 @@ export interface ToolCallRequest {
|
|
|
106
106
|
* deny/ask lane and the person's own allow rules, never over a mandated ask. */
|
|
107
107
|
declare const DECISION_REASONS: readonly ["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable", "read_only"];
|
|
108
108
|
export type DecisionReason = (typeof DECISION_REASONS)[number];
|
|
109
|
+
/** Carry every engine attestation (settlement, classifier cause) from a decision onto a reconstruction of it — the
|
|
110
|
+
* seam for the engine's OWN clone sites outside this module (the inherited chain's final-form re-check carries the
|
|
111
|
+
* post-rewrite args on a spread of an ancestor's deny). Not re-exported from `src/index.ts`. */
|
|
112
|
+
export declare function carryEngineAttestations(from: object, to: object): void;
|
|
113
|
+
/** Carry a PRECEDING decision's settlement onto a later deny that has none of its own — the veto record (a person's
|
|
114
|
+
* yes on the settlement, the refusing layer on the disposition; invariant I3) for the engine's own clone sites,
|
|
115
|
+
* where the deny is minted over a decision that already carried an attested approval. A deny that carries its own
|
|
116
|
+
* settlement keeps it (the latest resolution wins). Not re-exported from `src/index.ts`. */
|
|
117
|
+
export declare function carryPrecedingSettlement(preceding: object, to: object): void;
|
|
118
|
+
/** Attest that `d` is a deny a classifier station minted for `call` out of a round of the named FORM. */
|
|
119
|
+
export declare function attestClassifierDenyCause<T extends object>(d: T, cause: import("./gate-outcome.js").ClassifierDenyCause, call: {
|
|
120
|
+
toolCallId: string;
|
|
121
|
+
toolName: string;
|
|
122
|
+
}): T;
|
|
123
|
+
/** Read the attested classifier cause off a deny FOR the named call (the gate's fold and exit are the
|
|
124
|
+
* consumers): a binding to a different call is a replayed object and answers absence. An internal seam
|
|
125
|
+
* between engine modules — deliberately NOT re-exported from `src/index.ts`. */
|
|
126
|
+
export declare function classifierDenyCauseOf(d: unknown, call: {
|
|
127
|
+
toolCallId: string;
|
|
128
|
+
toolName: string;
|
|
129
|
+
}): import("./gate-outcome.js").ClassifierDenyCause | undefined;
|
|
109
130
|
/** Read the engine-attested settlement off a funneled decision FOR the named call (the gate's exit is the
|
|
110
131
|
* one consumer): an attestation bound to a different call is a replayed object and answers absence.
|
|
111
132
|
* Exported for the gate module only — deliberately NOT re-exported from `src/index.ts` (an internal seam
|
|
@@ -209,15 +230,6 @@ export type PermissionResult = {
|
|
|
209
230
|
* same safe direction as `matchedAskRule`); it cannot state a window of its own, because the
|
|
210
231
|
* route it would be a window for has not been chosen yet. */
|
|
211
232
|
denialLimitFallback?: import("./auto-mode.js").UnarmedDenialLimitFallback;
|
|
212
|
-
/** #616 (additive): the classifier-unavailable FACT on an ASK — `cause` is the verdict's own word
|
|
213
|
-
* ({@link import("./auto-mode.js").AutoModeUnavailableCause}). Since #661 the ENGINE no longer writes
|
|
214
|
-
* it here: an `unavailable` verdict is a DENY (the deny arm's member above), never an ask handed to a
|
|
215
|
-
* person. The member stays on the ask arm as display metadata a policy may self-declare on its own
|
|
216
|
-
* ask (nothing reads it to decide anything); the carry stations still copy it onto the approval
|
|
217
|
-
* request and the durable row when present. Retirement candidate for the next wire window. */
|
|
218
|
-
classifierUnavailable?: {
|
|
219
|
-
readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
|
|
220
|
-
};
|
|
221
233
|
/** #144 disclosure (additive): a persisted allow rule MATCHED this call but could not clear the
|
|
222
234
|
* ask, because the ask is MANDATED (operator shellGate:"always", or the tool's own
|
|
223
235
|
* egress/irreversibility marks) rather than a classifier's hesitation — "allow rules silence
|
|
@@ -287,21 +299,18 @@ export type PermissionResult = {
|
|
|
287
299
|
* exclusions; an approval surface may render the card by it. Optional on the TYPE only because a
|
|
288
300
|
* decision is caller-composable; every ask the gate hands to its resolver carries it. */
|
|
289
301
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
302
|
+
/** #688 C3 (additive): WHICH mechanism stands behind an `origin: "rule_store_unavailable"` — one word from
|
|
303
|
+
* {@link import("./ask-origin.js").RULE_STORE_UNREADABLE_KINDS} (`store`: the wired store could not be read;
|
|
304
|
+
* `call`: the call could not be read against the person's rows — the lexer's sentence is in `message`).
|
|
305
|
+
* ENGINE-STAMPED at the same single stamp point as `origin`, from the gate's own lane read; a policy's
|
|
306
|
+
* self-declared value is overwritten or removed there. Present ⇔ the origin is `rule_store_unavailable`.
|
|
307
|
+
* Carried onto the approval request and the durable row beside the origin word. */
|
|
308
|
+
ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
|
|
290
309
|
} | {
|
|
291
310
|
action: "deny";
|
|
292
311
|
updatedInput?: unknown;
|
|
293
312
|
message?: string;
|
|
294
313
|
decisionReason?: DecisionReason;
|
|
295
|
-
/** #661 (additive): this deny is the classifier's UNAVAILABILITY — the auto-mode classifier was consulted
|
|
296
|
-
* and could not run, and the call is refused with a sentence that says so (CC 2.1.250 `Ze.unavailable` ⇒
|
|
297
|
-
* `deny`, `x1t`). `cause` is the verdict's own word ({@link import("./auto-mode.js").AutoModeUnavailableCause}).
|
|
298
|
-
* ENGINE-STAMPED beside `decisionReason: "classifier"` at the classifier stations (the gate's own and the
|
|
299
|
-
* inherited-lane arms) on an `unavailable` verdict only; a `block` and a `parse_error` deny carry nothing
|
|
300
|
-
* here. Read by the deny observer's payload ({@link import("./hooks.js").PermissionDeniedPayload}) and by
|
|
301
|
-
* nothing that decides. */
|
|
302
|
-
classifierUnavailable?: {
|
|
303
|
-
readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
|
|
304
|
-
};
|
|
305
314
|
};
|
|
306
315
|
/**
|
|
307
316
|
* design/252 G-2 — WHY a piece of rule-provenance evidence is not on an ask.
|
|
@@ -1157,15 +1166,12 @@ export interface AskRequest {
|
|
|
1157
1166
|
* that forwards a decision's member here does not compile, which is the point: the window is the
|
|
1158
1167
|
* route's fact, and forwarding it silently is how a configured window becomes a wait with none. */
|
|
1159
1168
|
readonly denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1160
|
-
/** #
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
1164
|
-
*
|
|
1165
|
-
|
|
1166
|
-
readonly classifierUnavailable?: {
|
|
1167
|
-
readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
|
|
1168
|
-
};
|
|
1169
|
+
/** #688 C3 (additive) — the wire twin of the {@link PermissionResult} ask-arm member of the same name: which
|
|
1170
|
+
* mechanism stands behind an `origin: "rule_store_unavailable"` (`store` / `call`,
|
|
1171
|
+
* {@link import("./ask-origin.js").RULE_STORE_UNREADABLE_KINDS}). Present ⇔ that origin; a consumer that
|
|
1172
|
+
* renders "the rule store is unreachable" vs "this command could not be read against your rules" branches
|
|
1173
|
+
* here, never on the message text. Filled by the gate's own carry station from the engine-stamped decision. */
|
|
1174
|
+
readonly ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
|
|
1169
1175
|
/** (additive) WHICH AUTHORITY raised this ask — the wire twin of the {@link PermissionResult} ask-arm
|
|
1170
1176
|
* member of the same name, one word from the closed {@link import("./ask-origin.js").AskOrigin} set.
|
|
1171
1177
|
* The gate's own mint station copies the engine-stamped word; the three inherited-lane stations
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -29,6 +29,32 @@ function transferEngineSettlement(from, to) {
|
|
|
29
29
|
const v = ENGINE_SETTLEMENTS.get(from);
|
|
30
30
|
if (v !== undefined)
|
|
31
31
|
ENGINE_SETTLEMENTS.set(to, v);
|
|
32
|
+
const c = CLASSIFIER_DENY_CAUSES_SEAT.get(from);
|
|
33
|
+
if (c !== undefined)
|
|
34
|
+
CLASSIFIER_DENY_CAUSES_SEAT.set(to, c);
|
|
35
|
+
}
|
|
36
|
+
export function carryEngineAttestations(from, to) {
|
|
37
|
+
transferEngineSettlement(from, to);
|
|
38
|
+
}
|
|
39
|
+
export function carryPrecedingSettlement(preceding, to) {
|
|
40
|
+
if (ENGINE_SETTLEMENTS.get(to) !== undefined)
|
|
41
|
+
return;
|
|
42
|
+
const v = ENGINE_SETTLEMENTS.get(preceding);
|
|
43
|
+
if (v !== undefined)
|
|
44
|
+
ENGINE_SETTLEMENTS.set(to, v);
|
|
45
|
+
}
|
|
46
|
+
const CLASSIFIER_DENY_CAUSES_SEAT = new WeakMap();
|
|
47
|
+
export function attestClassifierDenyCause(d, cause, call) {
|
|
48
|
+
CLASSIFIER_DENY_CAUSES_SEAT.set(d, { cause, toolCallId: call.toolCallId, toolName: call.toolName });
|
|
49
|
+
return d;
|
|
50
|
+
}
|
|
51
|
+
export function classifierDenyCauseOf(d, call) {
|
|
52
|
+
if (typeof d !== "object" || d === null)
|
|
53
|
+
return undefined;
|
|
54
|
+
const v = CLASSIFIER_DENY_CAUSES_SEAT.get(d);
|
|
55
|
+
if (v === undefined || v.toolCallId !== call.toolCallId || v.toolName !== call.toolName)
|
|
56
|
+
return undefined;
|
|
57
|
+
return v.cause;
|
|
32
58
|
}
|
|
33
59
|
export function engineSettlementOf(d, call) {
|
|
34
60
|
if (typeof d !== "object" || d === null)
|
|
@@ -400,7 +426,6 @@ export function combinePolicies(...policies) {
|
|
|
400
426
|
let ruleAskText;
|
|
401
427
|
let probeMandateSeen = false;
|
|
402
428
|
let fallbackSeen;
|
|
403
|
-
let classifierUnavailableSeen;
|
|
404
429
|
for (const p of policies) {
|
|
405
430
|
const d = refuseOutOfContractDecision(await p.check(current, signal));
|
|
406
431
|
if (d.action === "deny") {
|
|
@@ -410,6 +435,8 @@ export function combinePolicies(...policies) {
|
|
|
410
435
|
}
|
|
411
436
|
if (settledAllow !== undefined && ENGINE_SETTLEMENTS.get(out) === undefined) {
|
|
412
437
|
const owned = out === d ? { ...d } : out;
|
|
438
|
+
if (owned !== d)
|
|
439
|
+
transferEngineSettlement(d, owned);
|
|
413
440
|
transferEngineSettlement(settledAllow, owned);
|
|
414
441
|
return owned;
|
|
415
442
|
}
|
|
@@ -431,16 +458,13 @@ export function combinePolicies(...policies) {
|
|
|
431
458
|
probeMandateSeen = true;
|
|
432
459
|
if (d.action === "ask" && d.denialLimitFallback !== undefined && fallbackSeen === undefined)
|
|
433
460
|
fallbackSeen = d.denialLimitFallback;
|
|
434
|
-
if (d.action === "ask" && d.classifierUnavailable !== undefined && classifierUnavailableSeen === undefined)
|
|
435
|
-
classifierUnavailableSeen = d.classifierUnavailable;
|
|
436
461
|
}
|
|
437
462
|
if (asked) {
|
|
438
463
|
const merged = rewrite?.updatedInput;
|
|
439
464
|
const withRuleAsk = ruleAskText !== undefined && asked.matchedAskRule === undefined ? { ...asked, matchedAskRule: ruleAskText } : asked;
|
|
440
465
|
const withMark = probeMandateSeen && withRuleAsk.probeMandated !== true ? { ...withRuleAsk, probeMandated: true } : withRuleAsk;
|
|
441
466
|
const withFallback = fallbackSeen !== undefined && withMark.denialLimitFallback === undefined ? { ...withMark, denialLimitFallback: fallbackSeen, requiresRealApproval: true } : withMark;
|
|
442
|
-
|
|
443
|
-
return merged !== undefined ? { ...withFact, updatedInput: merged } : withFact;
|
|
467
|
+
return merged !== undefined ? { ...withFallback, updatedInput: merged } : withFallback;
|
|
444
468
|
}
|
|
445
469
|
const allowed = rewrite ?? ALLOW;
|
|
446
470
|
if (settledAllow === undefined)
|
package/dist/core/tool-roster.js
CHANGED
|
@@ -208,6 +208,8 @@ export function schemaKeyBoundProblem(schema) {
|
|
|
208
208
|
return undefined;
|
|
209
209
|
}
|
|
210
210
|
export function rosterMemberBoundProblem(m) {
|
|
211
|
+
if (typeof m.name !== "string")
|
|
212
|
+
return { code: "config.tool_roster_bound", message: `tool has no string name (got ${m.name === null ? "null" : typeof m.name}); every roster row is keyed by a string \`name\`` };
|
|
211
213
|
const over = (label, value, bound) => `${label} ${JSON.stringify(value.slice(0, 40) + "…")} is ${value.length} characters; the roster's bound is ${bound}`;
|
|
212
214
|
const of = (text) => `tool ${JSON.stringify(m.name.slice(0, 40))}: ${text}`;
|
|
213
215
|
if (m.name.length > TOOL_WIRE_NAME_MAX_CHARS)
|
package/dist/core/tool-spec.d.ts
CHANGED
|
@@ -363,9 +363,12 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
|
|
|
363
363
|
* for this tool — it is not a "threshold so low that everything offloads". */
|
|
364
364
|
offloadThresholdChars?: number;
|
|
365
365
|
/**
|
|
366
|
-
* Deferred disclosure (design/36): when `true`, this tool is
|
|
367
|
-
*
|
|
368
|
-
*
|
|
366
|
+
* Deferred disclosure (design/36): when `true`, this tool is a CANDIDATE of the engine's deferral gate.
|
|
367
|
+
* When the gate opens (the candidate set's inlined size reaching 10% of the model's context window; see
|
|
368
|
+
* {@link RunnerDeps.deferMode}) it is NOT inlined with its full JSON Schema: it appears as a lightweight
|
|
369
|
+
* placeholder (`{name, one-line hint, empty params}`) so the model knows it exists; when the gate stays
|
|
370
|
+
* shut it ships inline in full. (A caller that wants a tool deferred on every request names it in
|
|
371
|
+
* {@link TaskSpec.deferTools} — that list is literal.) Activation is a DISCLOSURE fact (has the model seen the schema?), not an
|
|
369
372
|
* execution precondition: the model activates the tool via the injected ToolSearch, or by making a call
|
|
370
373
|
* whose arguments already match the real parameters (see {@link TaskSpec.deferSelfResolve}, on by
|
|
371
374
|
* default). Where the schema then lands depends on {@link TaskSpec.toolMaterializeStrategy} — the next
|
|
@@ -386,7 +389,7 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
|
|
|
386
389
|
* `alwaysLoad`, declared per MCP tool via `_meta["anthropic/alwaysLoad"]`, whose `isDeferredTool`
|
|
387
390
|
* checks it before every deferral arm): `true` pins this tool's full schema inline in every
|
|
388
391
|
* request — it is never deferred, regardless of its own {@link defer}, {@link TaskSpec.deferTools},
|
|
389
|
-
* or
|
|
392
|
+
* or the deferral gate. The deferral sources only ever ADD; this is the explicit subtract valve.
|
|
390
393
|
* Exclusion still wins ({@link TaskSpec.excludeTools} unmounts — nothing left to keep inline).
|
|
391
394
|
*/
|
|
392
395
|
alwaysLoad?: boolean;
|
|
@@ -1077,19 +1080,20 @@ export interface ToolExecuteContext {
|
|
|
1077
1080
|
ttlMs?: number;
|
|
1078
1081
|
};
|
|
1079
1082
|
/**
|
|
1080
|
-
*
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1083
|
+
* The host run's RESOLVED checkpoint seat, for its children — the store object the run parks in (the ONE value
|
|
1084
|
+
* `resolveCheckpointStore(spec, deps)` produced), or the word `"disabled"` when the caller disarmed the run.
|
|
1085
|
+
* Runner-filled, read-only, NEVER a model/tool argument. ABSENT when the tool runs outside a Runner task and
|
|
1086
|
+
* when the host has no seat at all (unset spec, no deployment store): nothing to inherit, so the child's own
|
|
1087
|
+
* door resolves its runner's deployment — a split child-execution runner may carry a store the host has none of.
|
|
1083
1088
|
*
|
|
1084
|
-
* A delegation tool
|
|
1085
|
-
*
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1089
|
-
* as `principal
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
checkpointStoreDisabledForChildren?: true;
|
|
1089
|
+
* A delegation tool copies this seat VERBATIM onto every child spec it builds (sync, background and fork
|
|
1090
|
+
* alike) — one rule: a store the parent resolved at SPEC level reaches the child (a child that re-resolved
|
|
1091
|
+
* `RunnerDeps.checkpointStore` on its own found no store on a deployment that wires it per task, and its ask
|
|
1092
|
+
* fell to the fail-closed deny while the parent could park), and `"disabled"` presses on the whole subtree (a
|
|
1093
|
+
* capability REMOVAL, never re-armed one level down by a deployment store — a machine-started run must not
|
|
1094
|
+
* park in its subtree). Same discipline as `principal`: inherit verbatim off the trusted seat.
|
|
1095
|
+
*/
|
|
1096
|
+
checkpointStoreForChildren?: import("./checkpoint-store.js").CheckpointStore | "disabled";
|
|
1093
1097
|
/**
|
|
1094
1098
|
* design/99 (nested-subagent live observability): the host run's stable taskId (`spec.taskId ?? sessionId`),
|
|
1095
1099
|
* filled by the Runner. A delegation tool (`createSubagentTool`) reads it AT SPAWN TIME and threads it into the
|
package/dist/core/types.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import type { AgentTool, ThinkingLevel, ToolInputValidationContext, ToolInputVerdict } from "../internal/harness.js";
|
|
13
13
|
import type { Model, StreamFn } from "../internal/llm.js";
|
|
14
14
|
export type { ModelRef, ModelRole, RoleSpec, ModelRoles, Brain } from "./model-seat.js";
|
|
15
|
-
export type { StaleToolResultOffloadOptions, ImageInput, TaskSpec } from "./task-spec.js";
|
|
15
|
+
export type { StaleToolResultOffloadOptions, ImageInput, TaskSpec, ShellGateDoctrine } from "./task-spec.js";
|
|
16
16
|
export type { ToolEffect, ToolContentOrigin, ReversibilityVerdict, ToolSpec, ToolReturn, NestedUsage, NestedUsageAccum, ToolExecuteContext } from "./tool-spec.js";
|
|
17
17
|
export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditRequest, TrackEditResult, TrackFileEditHook, FileEditedNotice, FileEditedHook, HandsBandOptions } from "./hands-band.js";
|
|
18
18
|
export type { AgentDefinition, SkillManifest, SkillSpec } from "./agent-definition.js";
|
|
@@ -22,7 +22,8 @@ export type { TaskStatus, RemoteEnvFailureNote, EffectiveMemoryScopes, TaskResul
|
|
|
22
22
|
export type { TerminalCause, PausedCause } from "./terminal-cause.js";
|
|
23
23
|
export type { TaskEventIdentity, BrainStatusPhase, BrainRetryErrClass, BrainStatus, ToolActivity, HumanInputSource, HumanInputDelivery, DelegationTaskType, TaskEvent, HumanInputEvent, CompactOutcome } from "./task-event.js";
|
|
24
24
|
export type { TaskStream } from "./task-stream.js";
|
|
25
|
-
export type { WorkflowGovernanceBaseline, RuntimeCaps } from "./runtime-caps.js";
|
|
25
|
+
export type { WorkflowGovernanceBaseline, RuntimeCaps, AutoModeDenySource } from "./runtime-caps.js";
|
|
26
|
+
export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
|
|
26
27
|
export type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegation-frames.js";
|
|
27
28
|
export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
|
|
28
29
|
export type { ProjectMemoryLoad, ResumePreflightInfo, ResumePreflightVerdict, RunnerDeps } from "./runner-deps.js";
|
package/dist/core/types.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
|
|
1
2
|
export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
|
|
2
3
|
export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, undrainedUserInputNotices } from "./engine-notice.js";
|
|
@@ -74,6 +74,18 @@ export type ParkLaneReason = "no_checkpoint_store" | "no_durable_approval_opt_in
|
|
|
74
74
|
*/
|
|
75
75
|
export declare const AUTO_MODE_ARM_REASONS: readonly ["armed", "no_intent", "no_face", "denied", "resolver_fault"];
|
|
76
76
|
export type AutoModeArmReason = (typeof AUTO_MODE_ARM_REASONS)[number];
|
|
77
|
+
/**
|
|
78
|
+
* The leg's auto-mode arming FACT as the manifest carries it: `armed ⇔ reason === "armed"`; `deniedSource` (#692 C-d,
|
|
79
|
+
* additive) is present only beside a STATED `denied` and names which settings plane's ratchet turned auto mode off
|
|
80
|
+
* ({@link import("./runtime-caps.js").AUTO_MODE_DENY_SOURCES}: `org` / `local` / `settings`) — the resolver's
|
|
81
|
+
* own word, carried through, never inferred; a `denied` with no source is a resolver that does not track sources,
|
|
82
|
+
* and a `resolver_fault` never carries one (the screen strips a source it refused).
|
|
83
|
+
*/
|
|
84
|
+
export interface AutoModeArmFact {
|
|
85
|
+
armed: boolean;
|
|
86
|
+
reason: AutoModeArmReason;
|
|
87
|
+
deniedSource?: import("./runtime-caps.js").AutoModeDenySource;
|
|
88
|
+
}
|
|
77
89
|
/**
|
|
78
90
|
* One declared MCP server's materialization result for THIS leg — the per-server connection-state
|
|
79
91
|
* read face on {@link WiringManifest.mcp}. `status` is the connect-time verdict of this leg's
|
|
@@ -242,10 +254,7 @@ export interface WiringManifest {
|
|
|
242
254
|
* round is a deny that says so, and the next round is classified afresh), so there is no session-level
|
|
243
255
|
* breaker face here and nothing about availability enters {@link configFingerprint}.
|
|
244
256
|
*/
|
|
245
|
-
autoMode?:
|
|
246
|
-
armed: boolean;
|
|
247
|
-
reason: AutoModeArmReason;
|
|
248
|
-
};
|
|
257
|
+
autoMode?: AutoModeArmFact;
|
|
249
258
|
/**
|
|
250
259
|
* EFFECTIVE half only, and ALWAYS present on an engine-minted effective manifest (the `autoMode`
|
|
251
260
|
* posture: every leg has an answer — an empty array is "this leg declared no servers", absence is
|
|
@@ -333,11 +342,9 @@ export interface WiringFacts {
|
|
|
333
342
|
restore: string;
|
|
334
343
|
};
|
|
335
344
|
/** Effective half only — see {@link WiringManifest.autoMode}; the static half has no leg to arm.
|
|
336
|
-
* `armed` must agree with `reason` (`armed ⇔ reason === "armed"`); a contradicting pair is refused
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
reason: AutoModeArmReason;
|
|
340
|
-
};
|
|
345
|
+
* `armed` must agree with `reason` (`armed ⇔ reason === "armed"`); a contradicting pair is refused, and so
|
|
346
|
+
* is a `deniedSource` beside any reason but `denied` or spelled outside its closed set. */
|
|
347
|
+
autoMode?: AutoModeArmFact;
|
|
341
348
|
/** Effective half only — see {@link WiringManifest.mcp}; the static half materializes nothing.
|
|
342
349
|
* Copied entry-wise onto the manifest (the caller's array is never aliased). */
|
|
343
350
|
mcp?: readonly WiringManifestMcpEntry[];
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { canonicalize } from "./canonical-json.js";
|
|
3
3
|
import { resolveCheckpointStore } from "./checkpoint-store.js";
|
|
4
4
|
import { isLiveQuestionFace } from "./ask-question.js";
|
|
5
|
+
import { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
|
|
5
6
|
export const AUTO_MODE_ARM_REASONS = ["armed", "no_intent", "no_face", "denied", "resolver_fault"];
|
|
6
7
|
export function resolveDeclaredDurability(store, storeName) {
|
|
7
8
|
const declared = store?.durability;
|
|
@@ -68,10 +69,14 @@ function deriveParkLane(facts) {
|
|
|
68
69
|
return { capable: true, effective: unresolved ? "unresolved" : false, reasons, ...durability };
|
|
69
70
|
}
|
|
70
71
|
function readAutoModeFact(fact) {
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
const { armed, reason, deniedSource } = fact;
|
|
73
|
+
if (!AUTO_MODE_ARM_REASONS.includes(reason) || armed !== (reason === "armed")) {
|
|
74
|
+
throw new Error(`the auto-mode wiring fact is inconsistent (armed=${String(armed)}, reason=${JSON.stringify(reason)}) — armed must hold exactly when reason is "armed", and reason must be one of ${AUTO_MODE_ARM_REASONS.join("|")}`);
|
|
73
75
|
}
|
|
74
|
-
|
|
76
|
+
if (deniedSource !== undefined && (reason !== "denied" || !isAutoModeDenySource(deniedSource))) {
|
|
77
|
+
throw new Error(`the auto-mode wiring fact is inconsistent (reason=${JSON.stringify(reason)}, deniedSource=${JSON.stringify(deniedSource)}) — deniedSource may stand only beside reason "denied", as one of ${AUTO_MODE_DENY_SOURCES.join("|")}`);
|
|
78
|
+
}
|
|
79
|
+
return { armed, reason, ...(deniedSource !== undefined ? { deniedSource } : {}) };
|
|
75
80
|
}
|
|
76
81
|
export function deriveWiringManifest(facts) {
|
|
77
82
|
if (facts.half === "static" && facts.leg !== undefined) {
|