@sema-agent/core 5.65.0 → 6.0.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 +46 -0
- package/dist/agents/subagent.d.ts +2 -2
- package/dist/agents/subagent.js +11 -0
- package/dist/agents/verify.d.ts +1 -1
- package/dist/brain/anthropic.js +1 -1
- package/dist/brain/errors.d.ts +29 -0
- package/dist/brain/errors.js +20 -0
- package/dist/brain/open-responses.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +1 -0
- package/dist/brain/status-sink.js +12 -1
- package/dist/brain/stream-engine.js +17 -6
- package/dist/core/auto-compaction.d.ts +26 -0
- package/dist/core/auto-compaction.js +7 -2
- package/dist/core/auto-mode-arming.d.ts +138 -0
- package/dist/core/auto-mode-arming.js +181 -0
- package/dist/core/auto-mode-defaults.d.ts +13 -0
- package/dist/core/auto-mode-defaults.js +5 -0
- package/dist/core/auto-mode-prompt.d.ts +14 -3
- package/dist/core/auto-mode-prompt.js +10 -7
- package/dist/core/auto-mode-rebuild.d.ts +75 -0
- package/dist/core/auto-mode-rebuild.js +41 -0
- package/dist/core/auto-mode.d.ts +15 -0
- package/dist/core/auto-mode.js +4 -2
- package/dist/core/checkpoint-store.d.ts +18 -0
- package/dist/core/context-edit.d.ts +47 -5
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +106 -0
- package/dist/core/file-history-retention.js +36 -0
- package/dist/core/file-history-store.d.ts +768 -0
- package/dist/core/file-history-store.js +880 -0
- package/dist/core/governance-codes.d.ts +2 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +39 -7
- package/dist/core/hooks.js +38 -21
- package/dist/core/lsp.d.ts +2 -2
- package/dist/core/mcp.d.ts +29 -7
- package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
- package/dist/core/memory-engine/consolidation-driver.js +71 -4
- package/dist/core/memory-engine/consolidation.d.ts +25 -2
- package/dist/core/memory-engine/consolidation.js +4 -1
- package/dist/core/memory-engine/distiller.d.ts +84 -1
- package/dist/core/memory-engine/distiller.js +68 -0
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +328 -15
- package/dist/core/memory-engine/engine.js +355 -29
- package/dist/core/memory-engine/file-backend.d.ts +30 -0
- package/dist/core/memory-engine/file-backend.js +14 -13
- package/dist/core/memory-engine/frontmatter.d.ts +22 -1
- package/dist/core/memory-engine/frontmatter.js +3 -0
- package/dist/core/memory-engine/header-hints.d.ts +5 -0
- package/dist/core/memory-engine/index.d.ts +5 -4
- package/dist/core/memory-engine/index.js +5 -4
- package/dist/core/memory-engine/layout.d.ts +88 -2
- package/dist/core/memory-engine/layout.js +112 -3
- package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
- package/dist/core/memory-engine/provenance-wording.js +3 -0
- package/dist/core/memory-engine/tools.d.ts +89 -8
- package/dist/core/memory-engine/tools.js +263 -22
- package/dist/core/memory-engine/types.d.ts +64 -1
- package/dist/core/memory-recall.d.ts +6 -0
- package/dist/core/memory.d.ts +27 -1
- package/dist/core/memory.js +16 -2
- package/dist/core/permission-rule-consent.d.ts +20 -0
- package/dist/core/permission-rule-consent.js +12 -3
- package/dist/core/permission-rule-model.d.ts +67 -7
- package/dist/core/permission-rule-model.js +53 -7
- package/dist/core/permission-rule-store.js +15 -10
- package/dist/core/permission-rule-sync.js +15 -11
- package/dist/core/retention-policy.d.ts +9 -0
- package/dist/core/retention-policy.js +5 -2
- package/dist/core/retention.d.ts +13 -2
- package/dist/core/runner/assemble-result.d.ts +19 -1
- package/dist/core/runner/assemble-result.js +17 -2
- package/dist/core/runner/compaction-call-options.d.ts +93 -0
- package/dist/core/runner/compaction-call-options.js +3 -0
- package/dist/core/runner/memory-capture-optout.d.ts +80 -0
- package/dist/core/runner/memory-capture-optout.js +53 -0
- package/dist/core/runner/prepare-config-doors.d.ts +5 -0
- package/dist/core/runner/prepare-config-doors.js +16 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +110 -5
- package/dist/core/runner/prepare-hands-readface.js +99 -7
- package/dist/core/runner/prepare-memory.d.ts +88 -0
- package/dist/core/runner/prepare-memory.js +305 -24
- package/dist/core/runner/prepare-task.d.ts +141 -1
- package/dist/core/runner/prepare-task.js +443 -79
- package/dist/core/runner/runtask.d.ts +9 -20
- package/dist/core/runner/runtask.js +133 -96
- package/dist/core/runner/session-file-state-replay.d.ts +18 -10
- package/dist/core/runner/session-file-state-replay.js +52 -1
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/turn-attachments.d.ts +22 -12
- package/dist/core/session-store.d.ts +1 -1
- package/dist/core/session-store.js +6 -1
- package/dist/core/session.d.ts +34 -1
- package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/file-history-store-contract.js +720 -0
- package/dist/core/task-registry-shared.js +11 -1
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +172 -1
- package/dist/core/tool-policy.js +32 -1
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/trace.d.ts +24 -0
- package/dist/core/types.d.ts +784 -89
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/workflow-run-store-contract.js +6 -2
- package/dist/core/workflow-run-store.d.ts +4 -1
- package/dist/engine/compaction/compaction.d.ts +88 -10
- package/dist/engine/compaction/compaction.js +109 -30
- package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
- package/dist/engine/execution-env/node-execution-env.js +28 -0
- package/dist/engine/harness/agent-harness.d.ts +52 -1
- package/dist/engine/harness/agent-harness.js +36 -1
- package/dist/engine/harness/types.d.ts +26 -1
- package/dist/engine/llm/types.d.ts +50 -4
- package/dist/engine/loop/agent-loop.d.ts +5 -1
- package/dist/engine/loop/agent-loop.js +25 -0
- package/dist/engine/loop/types.d.ts +19 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
- package/dist/engine/session/session.js +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +14 -6
- package/dist/orchestration/run-workflow-tool.d.ts +20 -2
- package/dist/orchestration/run-workflow-tool.js +22 -3
- package/dist/orchestration/workflow-governance.d.ts +59 -1
- package/dist/orchestration/workflow-governance.js +61 -8
- package/dist/orchestration/workflow-meta.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +56 -13
- package/dist/orchestration/workflow-types.d.ts +78 -2
- package/dist/orchestration/workflow.d.ts +20 -0
- package/dist/orchestration/workflow.js +163 -14
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompts/default.d.ts +7 -7
- package/dist/stores/file/file-history-store.d.ts +368 -0
- package/dist/stores/file/file-history-store.js +1248 -0
- package/dist/stores/file/index.d.ts +22 -13
- package/dist/stores/file/index.js +4 -4
- package/dist/stores/file/permission-rule-store.js +1 -0
- package/dist/stores/file/strategy-store.d.ts +3 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
- package/dist/tools/fs/bash-readonly-classifier.js +106 -4
- package/dist/tools/fs/fs-bash.js +9 -5
- package/dist/tools/fs/fs-shared.d.ts +52 -1
- package/dist/tools/fs/fs-shared.js +14 -0
- package/dist/tools/fs/fs-write.d.ts +5 -5
- package/dist/tools/fs/fs-write.js +71 -14
- package/dist/tools/fs/index.d.ts +6 -1
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/web.js +2 -1
- package/package.json +5 -1
- package/test/export-surface.snapshot.json +155 -23
- package/dist/core/file-snapshot-store.d.ts +0 -165
- package/dist/core/file-snapshot-store.js +0 -259
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
- package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
- package/dist/stores/file/file-snapshot-store.d.ts +0 -58
- package/dist/stores/file/file-snapshot-store.js +0 -353
|
@@ -12,6 +12,7 @@ export declare const GOVERNANCE_CODES: {
|
|
|
12
12
|
readonly "config.compliance_required": "transient";
|
|
13
13
|
readonly "config.compliance_denied": "terminal";
|
|
14
14
|
readonly "config.locked_key": "terminal";
|
|
15
|
+
readonly "memory.capture_optout_denied": "terminal";
|
|
15
16
|
};
|
|
16
17
|
export type GovernanceCode = keyof typeof GOVERNANCE_CODES;
|
|
17
18
|
/**
|
|
@@ -93,7 +94,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
93
94
|
* src/ for notice mint shapes and names any code that is minted but unregistered, or registered but
|
|
94
95
|
* no longer minted.
|
|
95
96
|
*/
|
|
96
|
-
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", "delegation.transcript_integrity", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "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", "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", "tool_result.offload_put_failed"];
|
|
97
|
+
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", "delegation.transcript_integrity", "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", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "tool_result.offload_put_failed"];
|
|
97
98
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
98
99
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
99
100
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -5,6 +5,7 @@ export const GOVERNANCE_CODES = {
|
|
|
5
5
|
"config.compliance_required": "transient",
|
|
6
6
|
"config.compliance_denied": "terminal",
|
|
7
7
|
"config.locked_key": "terminal",
|
|
8
|
+
"memory.capture_optout_denied": "terminal",
|
|
8
9
|
};
|
|
9
10
|
export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
10
11
|
"memory.secret_detected",
|
|
@@ -27,6 +28,7 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
|
27
28
|
"memory.consolidation_incomplete",
|
|
28
29
|
"memory.consolidation_conflict",
|
|
29
30
|
"memory.consolidation_refused",
|
|
31
|
+
"memory.consolidation_withheld",
|
|
30
32
|
"memory.consolidation_disabled",
|
|
31
33
|
"memory.consolidation_gated",
|
|
32
34
|
"memory.consolidation_busy",
|
|
@@ -68,6 +70,10 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
|
68
70
|
"memory.origin_clear_pending",
|
|
69
71
|
"memory.origin_clear_conflict",
|
|
70
72
|
"memory.origin_clear_failed",
|
|
73
|
+
"memory.capture_opted_out",
|
|
74
|
+
"memory.capture_optout_unpersisted",
|
|
75
|
+
"memory.capture_optout_unavailable",
|
|
76
|
+
"memory.capture_optout_sweep_failed",
|
|
71
77
|
]);
|
|
72
78
|
export function governanceRetryClass(code) {
|
|
73
79
|
if (Object.prototype.hasOwnProperty.call(GOVERNANCE_CODES, code)) {
|
|
@@ -97,6 +103,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
97
103
|
"delegation.transcript_integrity",
|
|
98
104
|
"mcp.revocation_probe_failed",
|
|
99
105
|
"workflow.governance_key_stripped",
|
|
106
|
+
"workflow.agent_option_ignored",
|
|
100
107
|
"memory.session_polluted",
|
|
101
108
|
"memory.harvest_quarantined",
|
|
102
109
|
"memory.delegation_static_mark_waived",
|
|
@@ -109,12 +116,15 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
109
116
|
"memory.consolidation_conflict",
|
|
110
117
|
"memory.consolidation_incomplete",
|
|
111
118
|
"memory.consolidation_refused",
|
|
119
|
+
"memory.consolidation_withheld",
|
|
112
120
|
"route.fallback_to_primary",
|
|
113
121
|
"route.base_url_changed_key_unchanged",
|
|
114
122
|
"task.user_steer_undrained",
|
|
115
123
|
"task.user_followup_undrained",
|
|
116
124
|
"steering.parked_input_blocked",
|
|
117
125
|
"task.turn_interrupted",
|
|
126
|
+
"memory.capture_opted_out",
|
|
127
|
+
"memory.capture_optout_unpersisted",
|
|
118
128
|
"tool_result.offload_put_failed",
|
|
119
129
|
];
|
|
120
130
|
const NOTICE_AUDIENCE_TABLE = {
|
|
@@ -128,6 +138,9 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
128
138
|
"task.user_followup_undrained": "user",
|
|
129
139
|
"task.turn_interrupted": "user",
|
|
130
140
|
"steering.parked_input_blocked": "user",
|
|
141
|
+
"memory.capture_opted_out": "user",
|
|
142
|
+
"memory.capture_optout_unpersisted": "user",
|
|
143
|
+
"memory.consolidation_withheld": "user",
|
|
131
144
|
"config.autocompact_window_clamped": "operator",
|
|
132
145
|
"config.env_timeout_discarded": "operator",
|
|
133
146
|
"config.materialize_env_discarded": "operator",
|
|
@@ -139,6 +152,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
139
152
|
"delegation.transcript_integrity": "operator",
|
|
140
153
|
"mcp.revocation_probe_failed": "operator",
|
|
141
154
|
"workflow.governance_key_stripped": "operator",
|
|
155
|
+
"workflow.agent_option_ignored": "operator",
|
|
142
156
|
"memory.content_class_declared": "operator",
|
|
143
157
|
"memory.consolidation_recommended": "operator",
|
|
144
158
|
"memory.consolidation_committed": "operator",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -1289,7 +1289,14 @@ export interface ToolGateInput {
|
|
|
1289
1289
|
* parameter here is positional and `probeReason` is also a `string`, so inserting this beside its
|
|
1290
1290
|
* logical neighbour would silently re-bind three existing call sites' arguments with no type
|
|
1291
1291
|
* error to catch it. */
|
|
1292
|
-
matchedAskRule?: string
|
|
1292
|
+
matchedAskRule?: string,
|
|
1293
|
+
/** #502: the surviving ask's engine-stamped `probeMandated` — threaded for the SAME errand as
|
|
1294
|
+
* `askDecisionReason` and `matchedAskRule`: the park mint's rule-suggestion door must know that
|
|
1295
|
+
* this ask is one no persisted rule may clear, or the parked card offers a "stop asking me this"
|
|
1296
|
+
* rule the lane would then refuse to honor. APPENDED at the tail for the reason its neighbour
|
|
1297
|
+
* states — every parameter here is positional — and typed `boolean` so a mis-bind against the two
|
|
1298
|
+
* trailing `string` seats is a compile error rather than a silent re-binding. */
|
|
1299
|
+
probeMandated?: boolean) => Promise<ToolGateResult["suspend"] | ParkAttemptFailed | undefined>;
|
|
1293
1300
|
/**
|
|
1294
1301
|
* design/174 — route a policy `ask` on the reserved question tool to this run's CONTENT-ask channel
|
|
1295
1302
|
* before it can become a park or a refusal. Called in the `ask` branch with the FINAL post-hook,
|
|
@@ -1321,9 +1328,15 @@ export interface ToolGateInput {
|
|
|
1321
1328
|
irreversibility?: "never" | "maybe" | "always";
|
|
1322
1329
|
/**
|
|
1323
1330
|
* design/77 §4: the `"maybe"`-tier probe from the tool's spec (read at prepare-time, captured here — not a
|
|
1324
|
-
* tool argument). Called
|
|
1325
|
-
*
|
|
1331
|
+
* tool argument). Called when `irreversibility === "maybe"` && the surviving decision is `allow` (the
|
|
1332
|
+
* TIGHTEN arm) — and, since #502, when it is already `ask` (the MANDATE arm: the probe is the only
|
|
1333
|
+
* source of `mandated`, and an ask another layer raised needs it too; there the `reversible` verdict is
|
|
1334
|
+
* discarded, since a probe may never un-ask another layer's question). Both arms are time-bounded by
|
|
1335
|
+
* {@link approvalTimeoutMs} (30s default). Fail-closed: anything other than `{ reversible: true }`
|
|
1326
1336
|
* (including a timeout or a throw) tightens to `ask`. A probe is never trusted to AUTO-ALLOW past the gate.
|
|
1337
|
+
* The invocation set is therefore WIDER than the tighten's: an ask-first policy calls this on every
|
|
1338
|
+
* `"maybe"`-tier call, and a throwing/hanging probe costs an `onError(phase:"hook")` and the wait there
|
|
1339
|
+
* too — see `ToolSpec.reversibilityProbe` for the deployment-facing statement of that cost.
|
|
1327
1340
|
*
|
|
1328
1341
|
* An optional `reason` on a TIGHTENING verdict names the cause. The gate neutralizes + caps it and
|
|
1329
1342
|
* carries it onto both approval routes (`AskRequest.probeReason`, `RiskDescriptor.probeReason`) — see
|
|
@@ -1453,6 +1466,13 @@ export interface ToolGateInput {
|
|
|
1453
1466
|
* tool's own egress/irreversibility marks are structural requirements, not classifier hesitation
|
|
1454
1467
|
* ("allow rules silence the classifier's questions, never a mandated one"). When a rule matches
|
|
1455
1468
|
* but cannot clear, the surviving ask discloses it (message + `persistedRuleShadowed`).
|
|
1469
|
+
* · #502: an ask the call's own reversibility probe declared STRUCTURAL (`probeMandated`) joins that
|
|
1470
|
+
* family. It is the one PER-CALL member of it, and the reason there has to be one: the built-in
|
|
1471
|
+
* shell probe raises it for a read outside the session's allowed directories, which is a boundary
|
|
1472
|
+
* the deployment declared but which arrives wearing the classify doctrine's own "maybe" tier —
|
|
1473
|
+
* the very tier the bullet below keeps as this lane's home turf. Without a per-call member, a
|
|
1474
|
+
* compound was cleared segment-wise by the conjunction arm and an out-of-root read plus a sender
|
|
1475
|
+
* ran on two ordinary standing rules with no question asked.
|
|
1456
1476
|
* · #213: an ask carrying `matchedAskRule` (an explicit `ask` permission rule matched this call)
|
|
1457
1477
|
* is never consumed — a person's standing "ask me each time" outranks the memory of one past
|
|
1458
1478
|
* yes, across lanes exactly as the DSL's own deny > ask > allow order says within one. Same
|
|
@@ -1617,23 +1637,35 @@ timeoutMs?: number): ToolPolicy;
|
|
|
1617
1637
|
* classifier's questions, never a mandated one, and this predicate is the mandated-family half of
|
|
1618
1638
|
* that boundary (the real-approval/governance half rides the decision's own `requiresRealApproval`
|
|
1619
1639
|
* bit, which the org layer stamps):
|
|
1620
|
-
* · `
|
|
1640
|
+
* · `probeMandated` — this CALL's own reversibility probe declared its demotion STRUCTURAL
|
|
1641
|
+
* (`"probe_mandate"`), judged FIRST because it is the only per-call member here: the three below
|
|
1642
|
+
* are properties of the TOOL and are true of every call on the seat, so a mandate that is true of
|
|
1643
|
+
* this one call must not be shadowed by the tier that happens to carry it (#502: the built-in
|
|
1644
|
+
* shell probe raises it for a listed reader naming a path outside the session's roots — a boundary
|
|
1645
|
+
* the deployment declared, which is exactly what the classify tier alone cannot say);
|
|
1646
|
+
* · `egress` — the tool's own external-write mark, judged next: it is the tool's declaration even
|
|
1621
1647
|
* when the coarse doctrine also installed a shell tier on the same seat;
|
|
1622
1648
|
* · `shellGated` + tier `"always"` — the operator's per-call confirmation doctrine
|
|
1623
1649
|
* (`"operator_always"`); the classify doctrine installs `"maybe"`, and THOSE asks stay the rule
|
|
1624
|
-
* lane's home turf (`undefined`)
|
|
1650
|
+
* lane's home turf (`undefined`) — that is the don't-ask-again main case, and the per-call member
|
|
1651
|
+
* above is deliberately the ONLY thing that carves a mandate out of it;
|
|
1625
1652
|
* · a tool's OWN `"always"`/`"maybe"` irreversibility tier without the doctrine (`"tool_marks"`).
|
|
1626
1653
|
*
|
|
1627
1654
|
* Two consumers, one derivation: the gate's silencing arm (a matching rule is disclosed as shadowed
|
|
1628
1655
|
* instead of clearing the ask) and the runner's suggestion factory (a mandated ask offers no
|
|
1629
1656
|
* "stop asking me this" option — a rule minted from it would never clear it). A drift between the
|
|
1630
|
-
* two would let a card offer a rule the lane then refuses to honor.
|
|
1657
|
+
* two would let a card offer a rule the lane then refuses to honor. The per-call member reaches both
|
|
1658
|
+
* the same way every other per-call fact does: the gate stamps it on the surviving ask, the
|
|
1659
|
+
* synchronous sites read it off the decision they spread, and the park leg threads its own parameter.
|
|
1631
1660
|
*/
|
|
1632
1661
|
export declare function persistedRuleMandateOf(marks: {
|
|
1633
1662
|
egress?: boolean;
|
|
1634
1663
|
shellGated?: boolean;
|
|
1635
1664
|
irreversibility?: "never" | "maybe" | "always";
|
|
1636
|
-
|
|
1665
|
+
/** #502: the surviving ask's engine-stamped `probeMandated` — see
|
|
1666
|
+
* {@link import("./types.js").ReversibilityVerdict.mandated}. */
|
|
1667
|
+
probeMandated?: boolean;
|
|
1668
|
+
}): "operator_always" | "tool_marks" | "probe_mandate" | undefined;
|
|
1637
1669
|
/**
|
|
1638
1670
|
* The design/37 **two-phase tool gate** — the single chokepoint that makes the load-bearing invariant
|
|
1639
1671
|
* structural ("a hook's `allow` cannot bypass the policy's `deny`/`ask`"):
|
package/dist/core/hooks.js
CHANGED
|
@@ -411,15 +411,17 @@ function withProbeTimeout(p, ms, signal) {
|
|
|
411
411
|
}
|
|
412
412
|
const DEFAULT_PROBE_TIMEOUT_MS = 30_000;
|
|
413
413
|
export function persistedRuleMandateOf(marks) {
|
|
414
|
-
return marks.
|
|
415
|
-
? "
|
|
416
|
-
: marks.
|
|
417
|
-
?
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
:
|
|
414
|
+
return marks.probeMandated === true
|
|
415
|
+
? "probe_mandate"
|
|
416
|
+
: marks.egress === true
|
|
417
|
+
? "tool_marks"
|
|
418
|
+
: marks.shellGated === true
|
|
419
|
+
? marks.irreversibility === "always"
|
|
420
|
+
? "operator_always"
|
|
421
|
+
: undefined
|
|
422
|
+
: marks.irreversibility === "always" || marks.irreversibility === "maybe"
|
|
423
|
+
? "tool_marks"
|
|
424
|
+
: undefined;
|
|
423
425
|
}
|
|
424
426
|
export async function runToolGate(input) {
|
|
425
427
|
const { event, preToolUse, adjudicate, resolveAsk, suspendAsk } = input;
|
|
@@ -543,10 +545,12 @@ export async function runToolGate(input) {
|
|
|
543
545
|
};
|
|
544
546
|
denySource = "safety";
|
|
545
547
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
+
const alreadyAsking = decision.action === "ask";
|
|
549
|
+
if ((decision.action === "allow" || alreadyAsking) && (input.irreversibility === "always" || input.irreversibility === "maybe")) {
|
|
550
|
+
let tighten = !alreadyAsking;
|
|
548
551
|
let probeReason;
|
|
549
552
|
let probeCause;
|
|
553
|
+
let probeMandated;
|
|
550
554
|
if (input.irreversibility === "maybe" && input.reversibilityProbe) {
|
|
551
555
|
let reversible = false;
|
|
552
556
|
const probeArgs = policyRewrite !== undefined ? policyRewrite : currentInput;
|
|
@@ -570,6 +574,8 @@ export async function runToolGate(input) {
|
|
|
570
574
|
if (typeof raw === "string" && raw.trim().length > 0)
|
|
571
575
|
probeReason = inlineUntrusted(raw, PROBE_REASON_MAX);
|
|
572
576
|
probeCause = normalizeProbeCause(verdict?.cause);
|
|
577
|
+
if (verdict?.mandated === true)
|
|
578
|
+
probeMandated = true;
|
|
573
579
|
}
|
|
574
580
|
}
|
|
575
581
|
catch (err) {
|
|
@@ -577,7 +583,7 @@ export async function runToolGate(input) {
|
|
|
577
583
|
if (input.abortSignal?.aborted !== true)
|
|
578
584
|
traceHookCrash(input, err, notifier);
|
|
579
585
|
}
|
|
580
|
-
tighten = !reversible;
|
|
586
|
+
tighten = !alreadyAsking && !reversible;
|
|
581
587
|
}
|
|
582
588
|
if (tighten) {
|
|
583
589
|
decision = {
|
|
@@ -586,9 +592,13 @@ export async function runToolGate(input) {
|
|
|
586
592
|
decisionReason: "safety",
|
|
587
593
|
...(probeReason !== undefined ? { probeReason } : {}),
|
|
588
594
|
...(probeCause !== undefined ? { probeCause } : {}),
|
|
595
|
+
...(probeMandated === true ? { probeMandated: true } : {}),
|
|
589
596
|
};
|
|
590
597
|
denySource = input.shellGated === true ? "shellGate" : "safety";
|
|
591
598
|
}
|
|
599
|
+
else if (alreadyAsking && probeMandated === true && decision.action === "ask") {
|
|
600
|
+
decision = { ...decision, probeMandated: true };
|
|
601
|
+
}
|
|
592
602
|
}
|
|
593
603
|
const breakerKnownOpen = (d) => {
|
|
594
604
|
try {
|
|
@@ -706,7 +716,12 @@ export async function runToolGate(input) {
|
|
|
706
716
|
currentInput = policyRewrite;
|
|
707
717
|
req.args = policyRewrite;
|
|
708
718
|
}
|
|
709
|
-
const persistedRuleMandate = persistedRuleMandateOf(
|
|
719
|
+
const persistedRuleMandate = persistedRuleMandateOf({
|
|
720
|
+
egress: input.egress,
|
|
721
|
+
shellGated: input.shellGated,
|
|
722
|
+
irreversibility: input.irreversibility,
|
|
723
|
+
probeMandated: decision.action === "ask" && decision.probeMandated === true,
|
|
724
|
+
});
|
|
710
725
|
let personalEvidence = { dotsAbsent: input.persistedRules === undefined ? "not_wired" : "not_adjudicated" };
|
|
711
726
|
let laneCoverage;
|
|
712
727
|
if (input.persistedRules &&
|
|
@@ -764,11 +779,13 @@ export async function runToolGate(input) {
|
|
|
764
779
|
else if (hitRules !== undefined) {
|
|
765
780
|
personalEvidence = unionDotsOf(hitRules);
|
|
766
781
|
const shownRule = disclosedRuleSet(hitRules);
|
|
767
|
-
const mandateNoun = persistedRuleMandate === "
|
|
768
|
-
? "this
|
|
769
|
-
: persistedRuleMandate
|
|
770
|
-
? "this
|
|
771
|
-
:
|
|
782
|
+
const mandateNoun = persistedRuleMandate === "probe_mandate"
|
|
783
|
+
? "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"
|
|
784
|
+
: persistedRuleMandate === "operator_always"
|
|
785
|
+
? "this deployment mandates per-call confirmation for shell commands (shellGate: always)"
|
|
786
|
+
: persistedRuleMandate !== undefined
|
|
787
|
+
? "this tool carries egress/irreversibility marks (a mandated confirmation a rule cannot clear)"
|
|
788
|
+
: "an explicit ask rule matched this call (a person's ask-me-each-time outranks a standing allow rule)";
|
|
772
789
|
decision = {
|
|
773
790
|
...decision,
|
|
774
791
|
persistedRuleShadowed: shownRule,
|
|
@@ -841,7 +858,7 @@ export async function runToolGate(input) {
|
|
|
841
858
|
? { origin: orgAskOrigin !== undefined ? `org_${orgAskOrigin}` : "policy" }
|
|
842
859
|
: undefined;
|
|
843
860
|
if (suspendAsk && decision.action === "ask") {
|
|
844
|
-
const suspended = await suspendAsk(req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined);
|
|
861
|
+
const suspended = await suspendAsk(req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined, decision.action === "ask" ? decision.probeMandated : undefined);
|
|
845
862
|
if (suspended) {
|
|
846
863
|
if ("parkFailed" in suspended)
|
|
847
864
|
parkFailed = suspended.parkFailed;
|
|
@@ -860,7 +877,7 @@ export async function runToolGate(input) {
|
|
|
860
877
|
req.args = outcome.presentedInput;
|
|
861
878
|
}
|
|
862
879
|
if (suspendAsk && outcome.parkDeclined && parkFailed === undefined) {
|
|
863
|
-
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined);
|
|
880
|
+
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined, decision.action === "ask" ? decision.probeMandated : undefined);
|
|
864
881
|
if (suspended) {
|
|
865
882
|
if ("parkFailed" in suspended)
|
|
866
883
|
parkFailed = suspended.parkFailed;
|
|
@@ -892,7 +909,7 @@ export async function runToolGate(input) {
|
|
|
892
909
|
askDenyResolution = resolved.resolution;
|
|
893
910
|
decision = resolved;
|
|
894
911
|
if (resolved.action === "deny" && resolved.approverUnavailable === true && suspendAsk && parkFailed === undefined) {
|
|
895
|
-
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.segmentCoverage : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.matchedAskRule : undefined);
|
|
912
|
+
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.segmentCoverage : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.matchedAskRule : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeMandated : undefined);
|
|
896
913
|
if (suspended) {
|
|
897
914
|
if ("parkFailed" in suspended)
|
|
898
915
|
parkFailed = suspended.parkFailed;
|
package/dist/core/lsp.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export interface LspSymbolInfo {
|
|
|
55
55
|
* `{kind:"none"}` keeps getting). The failure reasons exist because one literal "No results." used to cover
|
|
56
56
|
* both real emptiness and every transport-level fault — a findReferences that TIMED OUT read as "no
|
|
57
57
|
* references, safe to delete" (CC 2.1.220 keeps these apart: `Error performing ${operation}: ...` vs the
|
|
58
|
-
* plain empty-result strings, pretty220.js
|
|
58
|
+
* plain empty-result strings, (pretty220.js:474840, historical; not relocated in 250 — see cc-250 anchors)).
|
|
59
59
|
*/
|
|
60
60
|
export type LspNoneReason = "unsupported_operation" | "server_error" | "timeout" | "cancelled" | "server_terminated" | "no_call_hierarchy_item"
|
|
61
61
|
/** HRD-LSP-7: the QUERIED file could not be read for the didOpen/didChange re-sync (deleted between the
|
|
@@ -178,7 +178,7 @@ export interface LspTransport {
|
|
|
178
178
|
/** Reads a workspace file's CURRENT text for the session's didOpen/didChange re-sync. TOC = local fs; TOB =
|
|
179
179
|
* the sandbox ExecutionEnv. */
|
|
180
180
|
export type LspReadText = (filePath: string, signal?: AbortSignal) => Promise<string>;
|
|
181
|
-
/** CC 2.1.220's LSP file cap (10MB — `File too large for LSP analysis`, pretty220.js
|
|
181
|
+
/** CC 2.1.220's LSP file cap (10MB — `File too large for LSP analysis`, (pretty220.js:474745, historical; not relocated in 250 — see cc-250 anchors)). Single-sourced
|
|
182
182
|
* in core: the TOOL-layer check in {@link createLspTool} (the model-visible enforcement, CC-parity) and the
|
|
183
183
|
* defense-in-depth throw in the engine's `defaultLspReadText` must agree on one number. */
|
|
184
184
|
export declare const MAX_LSP_FILE_BYTES: number;
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP client integration. Protocol revision `2025-11-25`, supplied by `@modelcontextprotocol/sdk` 1.
|
|
3
|
-
* (core pins the SDK and does not negotiate the revision itself). Client side only — core never
|
|
4
|
-
* an MCP server.
|
|
2
|
+
* MCP client integration. Protocol revision `2025-11-25`, supplied by `@modelcontextprotocol/sdk` 1.30.0
|
|
3
|
+
* (core pins the SDK EXACTLY and does not negotiate the revision itself). Client side only — core never
|
|
4
|
+
* implements an MCP server.
|
|
5
5
|
*
|
|
6
6
|
* ── DEPRECATION-IMMUNITY REGISTRY (MCP revision `2026-07-28`) ────────────────────────────────────────
|
|
7
7
|
* The `2026-07-28` revision deprecates four surfaces this module has NEVER implemented. That is not an
|
|
@@ -25,6 +25,28 @@
|
|
|
25
25
|
* reopening one is a LATER protocol revision that revives the surface, or a concrete consumer requirement
|
|
26
26
|
* recorded under its own ticket — not the absence of the feature.
|
|
27
27
|
*
|
|
28
|
+
* THE `2026-07-28` DELIVERY FORM, and why it does not narrow the four (design/384 §1, §2-J). That revision
|
|
29
|
+
* does not carry `roots/list`, `sampling/createMessage` or `elicitation/create` as standalone JSON-RPC
|
|
30
|
+
* methods at all. Its request-method table (as a 2026-era client dispatches it — CC 2.1.250's bundled era
|
|
31
|
+
* descriptor) is exactly: `tools/call`, `tools/list`, `prompts/get`, `prompts/list`, `resources/list`,
|
|
32
|
+
* `resources/templates/list`, `resources/read`, `completion/complete`, `server/discover`,
|
|
33
|
+
* `subscriptions/listen`. The three arrive instead through a DIFFERENT door — as embedded `inputRequests`
|
|
34
|
+
* inside an `input_required` result of `tools/call` / `prompts/get` / `resources/read`, answered by
|
|
35
|
+
* RETRYING the original request with `inputResponses`.
|
|
36
|
+
*
|
|
37
|
+
* That is a change of DELIVERY FORM ONLY, and it buys no exemption. Items 1 and 2 are reasoned about what
|
|
38
|
+
* the capability MEANS, not about how it is framed on the wire: an embedded `roots/list` enumerates the
|
|
39
|
+
* very root list item 1 refuses to host, and an embedded `sampling/createMessage` spends the very model
|
|
40
|
+
* channel and budget item 2 refuses to lend. So embedded roots/sampling are banned ON THE SAME TERMS and
|
|
41
|
+
* must clear the SAME reopening bar — a new door is not a reason, and "the registry only named the legacy
|
|
42
|
+
* method" is not a loophole. What this paragraph exists to prevent is the OPPOSITE misreading: items 1-4
|
|
43
|
+
* were written against the `2025-11-25` surface and are not themselves a ruling on the 2026 mechanics.
|
|
44
|
+
* `elicitation/create` — which core DOES implement (opt-in, form mode) and which is NOT one of the four —
|
|
45
|
+
* comes through that same door, so an era connection would have to decide how it is driven; that is
|
|
46
|
+
* design/384 P3's question, not this registry's. Nothing here is reachable today: core speaks `2025-11-25`,
|
|
47
|
+
* where all three are standalone methods, and core's `initialize` declares neither `roots` nor `sampling`
|
|
48
|
+
* (pinned).
|
|
49
|
+
*
|
|
28
50
|
* Two more revision-relevant absences, same reasoning, different cause:
|
|
29
51
|
* - `resources/subscribe` / `resources/unsubscribe` — the revision REMOVES the methods. Tool-list freshness
|
|
30
52
|
* here is served by the explicit `RefreshMcpTools` tool over the existing connection, which is also the
|
|
@@ -47,7 +69,7 @@ import { type ReminderDisclosureCounts } from "./reminder-disclosure.js";
|
|
|
47
69
|
*
|
|
48
70
|
* Trust note (design F): a server's `annotations` are SERVER-controlled and "not guaranteed faithful" (MCP
|
|
49
71
|
* spec), so they may only ever TIGHTEN — `destructiveHint` adds an `ask`/suspend, `openWorldHint` adds an
|
|
50
|
-
* egress tighten — never LOWER. A server's `readOnlyHint` does NOT lower `effect` (that would let
|
|
72
|
+
* egress tighten — never LOWER. A server's `readOnlyHint` does NOT lower `effect` (that would let an adversarial
|
|
51
73
|
* server lie on a mutating tool to escape repeat-safety + path-confinement). The ONLY trusted way to LOWER an
|
|
52
74
|
* MCP tool below the fail-closed `write` default is the caller's {@link McpServerSpec.toolAxes} override
|
|
53
75
|
* (caller = trust root). The synthetic first-party resource tools are the one exception — core sets their
|
|
@@ -278,7 +300,7 @@ export interface McpRefreshResult {
|
|
|
278
300
|
}
|
|
279
301
|
/** design/99 §E9 — projected per-server MCP status (see {@link MaterializedMcp.statuses}). NOTE: `serverInfo`
|
|
280
302
|
* and `error` are SERVER-controlled strings (verbatim from the remote) — UNTRUSTED; a consumer rendering them
|
|
281
|
-
* into a TUI/log must treat them as such (a
|
|
303
|
+
* into a TUI/log must treat them as such (a misbehaving server could embed ANSI/break-out sequences). `toolNames`
|
|
282
304
|
* are namespaced with a core-controlled `mcp__<server>__` prefix (design/108 ①). */
|
|
283
305
|
export interface McpServerStatus {
|
|
284
306
|
name: string;
|
|
@@ -577,7 +599,7 @@ export declare function normalizeMcpToolSchema(schema: unknown): McpSchemaNormal
|
|
|
577
599
|
* and not a technicality: a schema with no object root is unusable everywhere, whereas this charset is
|
|
578
600
|
* ONE provider's rule, and this engine is bring-your-own-model. Dropping here would delete a working
|
|
579
601
|
* tool from a deployment whose provider accepts the name — a Chinese- or Japanese-named parameter is
|
|
580
|
-
* the ordinary case, not
|
|
602
|
+
* the ordinary case, not an adversarial one. Detection was the gap; removal was never the mandate.
|
|
581
603
|
*
|
|
582
604
|
* TOP-LEVEL only, like CC: a nested property name is not what the provider validates.
|
|
583
605
|
*/
|
|
@@ -703,7 +725,7 @@ export declare function walkMcpListPages<T>(fetchPage: (cursor: string | undefin
|
|
|
703
725
|
* `structuredContent` from "a JSON object" to ANY JSON value (the same revision that relaxes
|
|
704
726
|
* `inputSchema`/`outputSchema` to the full JSON Schema 2020-12 keyword set).
|
|
705
727
|
*
|
|
706
|
-
* SDK 1.
|
|
728
|
+
* SDK 1.30.0's `CallToolResultSchema` still types the field as a record, so a spec-legal array / string /
|
|
707
729
|
* number / boolean / null fails validation for the WHOLE result — and the failure takes the `content`
|
|
708
730
|
* blocks down with it: a server returning perfectly usable text plus a scalar `structuredContent`
|
|
709
731
|
* produced nothing but `Invalid tools/call result: expected record, received number`. That is a silent
|
|
@@ -74,6 +74,12 @@ export interface ConsolidationDriverRunRow {
|
|
|
74
74
|
/** design/376-C1 A-5 disclosure — marked candidates withheld from the clean arm's prompts at
|
|
75
75
|
* mint time (present ⇔ the partition engaged over a marked library). */
|
|
76
76
|
withheldFromCleanArm?: number;
|
|
77
|
+
/** design/383 §1.5 ③ — of {@link withheldFromCleanArm}, how many rows were withheld from EVERY
|
|
78
|
+
* call, i.e. never sent to the provider at all (a lone marked row has no group to fold, so no
|
|
79
|
+
* arm renders it). Recorded at mint time rather than derived from the roster later: the roster
|
|
80
|
+
* dies with a discarded cache, and "your data never left this machine" must not become
|
|
81
|
+
* unanswerable because an unrelated guard fired. */
|
|
82
|
+
withheldNeverSent?: number;
|
|
77
83
|
/** The archived mint transcript's file name under {@link CONSOLIDATION_DRIVER_PLANS_DIR}. */
|
|
78
84
|
planArchive?: string;
|
|
79
85
|
/** COMMITTED cycles only (the store moved): a refused/fuse-blocked/zero-delta cycle row does
|
|
@@ -199,6 +205,11 @@ export interface ConsolidationRunReceipt {
|
|
|
199
205
|
* product's silence about some fact key should know the current value may sit in a withheld
|
|
200
206
|
* (marked, handle-faced) row. */
|
|
201
207
|
withheldFromCleanArm?: number;
|
|
208
|
+
/** design/383 §1.5 ③ — of {@link withheldFromCleanArm}, the rows sent to NO call at all (never
|
|
209
|
+
* left this process). "Withheld from the clean arm" and "never sent to the provider" are
|
|
210
|
+
* different facts about a user's data; the receipt reports them separately so a host's wording
|
|
211
|
+
* can too. */
|
|
212
|
+
withheldNeverSent?: number;
|
|
202
213
|
planArchive?: string;
|
|
203
214
|
/** Advisory notices minted for this run (the `_incomplete` disclosure when not converged). */
|
|
204
215
|
notices: EngineNotice[];
|
|
@@ -3,8 +3,8 @@ import { uuidv7 } from "../../internal/harness.js";
|
|
|
3
3
|
import { atomicWriteFileSync, ensureDirExists, lockedStrictUpdate, readStrictSidecar, enqueueMemoryAnnouncement, ControlPlaneCorruptError } from "./layout.js";
|
|
4
4
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
5
5
|
import { CONSOLIDATION_FORCE_THROTTLE_FLOOR_MS, screenConsolidationOptions, supersessionFuseCeiling, } from "./consolidation.js";
|
|
6
|
-
import { memoryConsolidationIncompleteNotice } from "./engine.js";
|
|
7
|
-
import { CONSOLIDATION_DRIVE_ABORT, CONSOLIDATION_SNAPSHOT_PARKED, MEMORY_DISTILLER_CONTRACT_V1, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, normalizedCandidateRev, } from "./distiller.js";
|
|
6
|
+
import { memoryConsolidationIncompleteNotice, memoryConsolidationWithheldNotice } from "./engine.js";
|
|
7
|
+
import { CONSOLIDATION_DRIVE_ABORT, CONSOLIDATION_SNAPSHOT_PARKED, MEMORY_DISTILLER_CONTRACT_V1, detectCleanArmVerbatimLeak, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, normalizedCandidateRev, } from "./distiller.js";
|
|
8
8
|
export const CONSOLIDATION_DRIVER_RUNS_FILE = "distiller-runs.json";
|
|
9
9
|
export const CONSOLIDATION_DRIVER_PLANS_DIR = "distiller-plans";
|
|
10
10
|
function coerceRunsFile(raw) {
|
|
@@ -71,6 +71,13 @@ class DriverMintFailure extends Error {
|
|
|
71
71
|
this.detail = detail;
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
class DriverPurityViolation extends Error {
|
|
75
|
+
detail;
|
|
76
|
+
constructor(detail) {
|
|
77
|
+
super(detail);
|
|
78
|
+
this.detail = detail;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
74
81
|
class MintInvalidated extends Error {
|
|
75
82
|
detail;
|
|
76
83
|
residue;
|
|
@@ -150,6 +157,40 @@ export async function runMemoryConsolidationDriver(engine, scope, opts) {
|
|
|
150
157
|
announceFailures.push(err instanceof Error ? err.message : String(err));
|
|
151
158
|
}
|
|
152
159
|
};
|
|
160
|
+
const egressEndpoint = ((raw) => {
|
|
161
|
+
if (raw === undefined)
|
|
162
|
+
return " (host-supplied chat seat; no endpoint recorded)";
|
|
163
|
+
try {
|
|
164
|
+
const u = new URL(raw);
|
|
165
|
+
return ` at ${inlineUntrusted(u.origin, 120)}`;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return " at an endpoint whose value could not be parsed as a URL (not echoed here — it may carry credentials)";
|
|
169
|
+
}
|
|
170
|
+
})(opts.baseUrl);
|
|
171
|
+
const egressSeat = `${inlineUntrusted(opts.model, 80)}${egressEndpoint}`;
|
|
172
|
+
const announceMintEgress = (calls, offered, plan) => {
|
|
173
|
+
if (calls === 0) {
|
|
174
|
+
announce([
|
|
175
|
+
`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} made NO model call: all ${offered} offered candidate(s) were withheld from every arm, so no memory content left this process.`,
|
|
176
|
+
]);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const armLine = (plan?.minting.arms ?? []).map((a) => `${a.arm} arm ${a.candidateCount} candidate(s) in ${a.calls} call(s)`).join(", ");
|
|
180
|
+
announce([
|
|
181
|
+
`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} SENT memory content to the configured consolidation model: ${egressSeat} — ${armLine || `up to ${offered} candidate(s) over ${calls} call(s) (the mint failed before its per-arm split was recorded; an arm that never ran sent nothing)`}. Each candidate was rendered as its name, description and body.`,
|
|
182
|
+
]);
|
|
183
|
+
const withheld = run.withheldFromCleanArm ?? 0;
|
|
184
|
+
if (withheld > 0) {
|
|
185
|
+
const neverSent = run.withheldNeverSent ?? 0;
|
|
186
|
+
const sent = Math.max(0, withheld - neverSent);
|
|
187
|
+
announce([
|
|
188
|
+
` ${withheld} externally-marked entr${withheld === 1 ? "y was" : "ies were"} withheld from the clean products' prompts.` +
|
|
189
|
+
(sent > 0 ? ` ${sent} of those ${sent === 1 ? "was" : "were"} sent to the model in a separate marked-only call.` : "") +
|
|
190
|
+
(neverSent > 0 ? ` ${neverSent} ${neverSent === 1 ? "was" : "were"} not sent to the model at all.` : ""),
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
153
194
|
const settle = (outcome, drive, stopDetail) => {
|
|
154
195
|
run.state = "settled";
|
|
155
196
|
run.outcome = outcome;
|
|
@@ -164,6 +205,11 @@ export async function runMemoryConsolidationDriver(engine, scope, opts) {
|
|
|
164
205
|
notices.push(notice);
|
|
165
206
|
announce([notice.message]);
|
|
166
207
|
}
|
|
208
|
+
if ((run.withheldFromCleanArm ?? 0) > 0) {
|
|
209
|
+
const withheldNotice = memoryConsolidationWithheldNotice({ scope, runId: run.runId, withheld: run.withheldFromCleanArm, neverSent: run.withheldNeverSent ?? 0 });
|
|
210
|
+
notices.push(withheldNotice);
|
|
211
|
+
announce([withheldNotice.message]);
|
|
212
|
+
}
|
|
167
213
|
return {
|
|
168
214
|
runId: run.runId,
|
|
169
215
|
scope,
|
|
@@ -183,6 +229,7 @@ export async function runMemoryConsolidationDriver(engine, scope, opts) {
|
|
|
183
229
|
writeFailures: [...run.writeFailures],
|
|
184
230
|
residue: run.residue ?? [],
|
|
185
231
|
...(run.withheldFromCleanArm !== undefined ? { withheldFromCleanArm: run.withheldFromCleanArm } : {}),
|
|
232
|
+
...(run.withheldNeverSent !== undefined ? { withheldNeverSent: run.withheldNeverSent } : {}),
|
|
186
233
|
...(run.planArchive !== undefined ? { planArchive: run.planArchive } : {}),
|
|
187
234
|
notices,
|
|
188
235
|
...(announceFailures.length > 0 ? { announceFailures: [...announceFailures] } : {}),
|
|
@@ -210,6 +257,9 @@ export async function runMemoryConsolidationDriver(engine, scope, opts) {
|
|
|
210
257
|
catch (err) {
|
|
211
258
|
if (codeOf(err) === "config.memory_consolidation_driver")
|
|
212
259
|
throw err;
|
|
260
|
+
announce([
|
|
261
|
+
`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} ATTEMPTED to send memory content to the configured consolidation model: ${egressSeat} — the seat threw before any answer was recorded, so whether any content reached the provider is NOT observable from here; up to ${candidates.length} candidate(s) were in flight, each rendered as its name, description and body. Treat this as a possible transmission.`,
|
|
262
|
+
]);
|
|
213
263
|
throw new DriverMintFailure(`the model seat failed during the mint: ${err instanceof Error ? err.message : String(err)}`);
|
|
214
264
|
}
|
|
215
265
|
const transcript = minted.ok ? minted.plan.minting.transcript : minted.calls;
|
|
@@ -219,9 +269,16 @@ export async function runMemoryConsolidationDriver(engine, scope, opts) {
|
|
|
219
269
|
run.usage.completionTokens += c.usage?.completionTokens ?? 0;
|
|
220
270
|
}
|
|
221
271
|
if (!minted.ok) {
|
|
272
|
+
announceMintEgress(transcript.length, candidates.length);
|
|
222
273
|
throw new DriverMintFailure(minted.reason);
|
|
223
274
|
}
|
|
224
275
|
const plan = minted.plan;
|
|
276
|
+
if (plan.withheldFromCleanArm !== undefined) {
|
|
277
|
+
run.withheldFromCleanArm = plan.withheldFromCleanArm;
|
|
278
|
+
const markedServed = (plan.servedCandidates ?? []).filter((r) => r.marked).length;
|
|
279
|
+
run.withheldNeverSent = Math.max(0, plan.withheldFromCleanArm - markedServed);
|
|
280
|
+
}
|
|
281
|
+
announceMintEgress(transcript.length, candidates.length, plan);
|
|
225
282
|
run.usage.retries += plan.minting.parseRepairs.retries;
|
|
226
283
|
run.usage.truncations += plan.minting.parseRepairs.lengthCapped.length;
|
|
227
284
|
run.repairs = {
|
|
@@ -239,6 +296,14 @@ export async function runMemoryConsolidationDriver(engine, scope, opts) {
|
|
|
239
296
|
if (ratio > mintRepairBudget) {
|
|
240
297
|
throw new DriverMintFailure(`the mint needed structural repairs on ${dropped} of ${total} member claim(s) (${(ratio * 100).toFixed(1)}% > mintRepairBudget ${(mintRepairBudget * 100).toFixed(1)}%) — a plan repaired this much does not represent the model's intent, and committing it would fold entries the harness chose, not the model (hallucinated ${plan.minting.repairs.hallucinatedMembers}, duplicate ${plan.minting.repairs.duplicateMembers}, nonInteger ${plan.minting.repairs.nonIntegerMembers}; emptied groups ${plan.minting.repairs.emptyGroups}, oversize ${plan.minting.repairs.oversizeGroups.length})`);
|
|
241
298
|
}
|
|
299
|
+
const purity = detectCleanArmVerbatimLeak(plan);
|
|
300
|
+
if (purity.findings.length > 0) {
|
|
301
|
+
run.planArchive = distillerPlanArchiveName(run.runId, run.attempt);
|
|
302
|
+
writeDriverRun(controlDir, run);
|
|
303
|
+
archiveDistillerPlan(controlDir, run.runId, plan, run.attempt);
|
|
304
|
+
const where = [...new Set(purity.findings.map((f) => `#${f.productIndex}.${f.field}`))].sort();
|
|
305
|
+
throw new DriverPurityViolation(`the exposure partition's clean arm is not clean: ${purity.findings.length} verbatim marked-side byte sequence(s) surfaced in ${new Set(purity.findings.map((f) => f.productIndex)).size} product(s) that would commit UNMARKED, across ${purity.scannedProducts} scanned product(s) against ${purity.markedUniqueShingles} marked-unique shingle(s). Locations (product index into the archived plan . field): ${where.slice(0, 8).join(", ")}${where.length > 8 ? ", …" : ""}. The whole mint is discarded with zero writes; NO model-authored bytes (neither the offending text nor the product keys) are reproduced here — re-run detectCleanArmVerbatimLeak over the archived plan ${run.planArchive} for the byte-level witness.`);
|
|
306
|
+
}
|
|
242
307
|
run.planArchive = distillerPlanArchiveName(run.runId, run.attempt);
|
|
243
308
|
run.planCache = {
|
|
244
309
|
maxInputsPerProduct: plan.maxInputsPerProduct,
|
|
@@ -246,8 +311,6 @@ export async function runMemoryConsolidationDriver(engine, scope, opts) {
|
|
|
246
311
|
...(plan.mintExposure !== undefined ? { mintExposure: plan.mintExposure } : {}),
|
|
247
312
|
...(plan.servedCandidates !== undefined ? { servedCandidates: plan.servedCandidates } : {}),
|
|
248
313
|
};
|
|
249
|
-
if (plan.withheldFromCleanArm !== undefined)
|
|
250
|
-
run.withheldFromCleanArm = plan.withheldFromCleanArm;
|
|
251
314
|
writeDriverRun(controlDir, run);
|
|
252
315
|
archiveDistillerPlan(controlDir, run.runId, plan, run.attempt);
|
|
253
316
|
}
|
|
@@ -382,6 +445,10 @@ export async function runMemoryConsolidationDriver(engine, scope, opts) {
|
|
|
382
445
|
announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} FAILED before any write: ${inlineUntrusted(err.detail, 300)}`]);
|
|
383
446
|
return settle("driver_failed", undefined, err.detail);
|
|
384
447
|
}
|
|
448
|
+
if (err instanceof DriverPurityViolation) {
|
|
449
|
+
announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} REFUSED on the clean-arm purity gate (no writes): ${inlineUntrusted(err.detail, 300)}`]);
|
|
450
|
+
return settle("purity_violation", undefined, err.detail);
|
|
451
|
+
}
|
|
385
452
|
if (err instanceof MintInvalidated) {
|
|
386
453
|
announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} MINT INVALIDATED: ${inlineUntrusted(err.detail, 300)}`]);
|
|
387
454
|
if (run.residue === undefined && err.residue.length > 0)
|