@sema-agent/core 5.54.0 → 5.55.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 +94 -0
- package/dist/agents/cumulative-stats.d.ts +26 -0
- package/dist/agents/cumulative-stats.js +56 -0
- package/dist/agents/observer.d.ts +11 -7
- package/dist/agents/observer.js +2 -4
- package/dist/agents/verify.d.ts +27 -3
- package/dist/agents/verify.js +7 -2
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.js +1 -1
- package/dist/core/lsp-diagnostics.d.ts +19 -17
- package/dist/core/lsp-diagnostics.js +11 -5
- package/dist/core/mcp.d.ts +46 -0
- package/dist/core/mcp.js +132 -6
- package/dist/core/memory-engine/consolidation.d.ts +378 -0
- package/dist/core/memory-engine/consolidation.js +342 -0
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +237 -4
- package/dist/core/memory-engine/engine.js +1111 -4
- package/dist/core/memory-engine/export-bundle.js +9 -0
- package/dist/core/memory-engine/file-backend.js +27 -1
- package/dist/core/memory-engine/frontmatter.d.ts +20 -1
- package/dist/core/memory-engine/frontmatter.js +111 -0
- package/dist/core/memory-engine/index.d.ts +4 -2
- package/dist/core/memory-engine/index.js +3 -1
- package/dist/core/memory-engine/memory-backend-contract.js +131 -0
- package/dist/core/memory-engine/sync-client.js +26 -0
- package/dist/core/memory-engine/tools.d.ts +9 -0
- package/dist/core/memory-engine/tools.js +57 -13
- package/dist/core/memory-engine/types.d.ts +99 -0
- package/dist/core/memory-recall.js +4 -3
- package/dist/core/memory.d.ts +33 -3
- package/dist/core/memory.js +6 -4
- package/dist/core/permission-rules.d.ts +22 -0
- package/dist/core/permission-rules.js +60 -6
- package/dist/core/reminder-disclosure.d.ts +29 -4
- package/dist/core/reminder-disclosure.js +60 -12
- package/dist/core/runner/prepare-memory.js +7 -2
- package/dist/core/runner/prepare-task.d.ts +31 -1
- package/dist/core/runner/prepare-task.js +31 -14
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +12 -10
- package/dist/core/runner/session-rule-policy.js +5 -3
- package/dist/core/runner/synthetic-tools.js +4 -2
- package/dist/core/runner/turn-attachments.d.ts +16 -6
- package/dist/core/runner/turn-attachments.js +34 -20
- package/dist/core/tool-policy.d.ts +18 -0
- package/dist/core/tool-policy.js +19 -8
- package/dist/core/types.d.ts +89 -6
- package/dist/core/untrusted-egress.js +12 -2
- package/dist/core/untrusted-text.d.ts +189 -3
- package/dist/core/untrusted-text.js +416 -6
- package/dist/engine/loop/types.d.ts +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/prompts/default.d.ts +12 -2
- package/dist/tools/fs/index.d.ts +3 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +28 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,99 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.55.0 — 2026-08-23
|
|
4
|
+
|
|
5
|
+
### BREAKING
|
|
6
|
+
- **MemoryBackend conformance suite tightens 34 → 40 clauses** (design/339 B-1): c35-c40 cover the
|
|
7
|
+
distilled carrier's immutability seat, the supersession read face, and plan storage. A third-party
|
|
8
|
+
backend green at 34 can be red at 40 — **upgrade the backend BEFORE the engine** (the suite is the
|
|
9
|
+
executable contract; run `memoryBackendContract` against your backend first).
|
|
10
|
+
- **New power face** (design/339 B-5): the engine can now restructure memory — but ONLY through
|
|
11
|
+
two-phase intents under a per-scope seat (all-satisfied settlement; conflict parks; delete is a
|
|
12
|
+
terminal-state judgment; `resolveConsolidationPlan(planId, "retry" | "discard")` is the host valve).
|
|
13
|
+
A deployment that assumed the engine never writes distilled products must review its mounts.
|
|
14
|
+
- **memory_search/get face rev 4 → 5** (design/339 §3.4): SUPERSEDED entries leave the default
|
|
15
|
+
result face and the MEMORY.md mechanical index (data-driven, including pre-existing stores whose
|
|
16
|
+
entries acquire edges); they return under `includeSuperseded` and `memory_get` renders a
|
|
17
|
+
supersession banner. `CleanMemorySearchHit.supersededBy` / handle-shape twin are the new seats.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **design/339 consolidation write protocol**: typed distilled carrier (planId/at/carrierRev
|
|
21
|
+
self-anchor outside the rev tuple; inputs with `superseded: true` edges), pure active-set
|
|
22
|
+
derivation (input-rev + carrier-rev anchors, challenge/latch suspension, chain rule, cross-scope
|
|
23
|
+
edges inert), four verbs (snapshot/freeze/apply/reconcile+resolve), gate-store minter accounting,
|
|
24
|
+
construction-time refusals, multiNode bit. New notice codes: `memory.consolidation_recommended` /
|
|
25
|
+
`_committed` / `_conflict` / `_refused` (+ the `memory.consolidation_*` refusal/incident family).
|
|
26
|
+
- **`tool_end.gatedCallId`** (top-level, optional): a park-contamination frame names the toolCallId
|
|
27
|
+
the committed gate holds, minted engine-side (not forgeable by tools); absent when the park holds
|
|
28
|
+
no call (plan review / resource slice). Interrupt/timeout aborts are byte-unchanged. **Wire
|
|
29
|
+
erratum**: an earlier board announcement placed this under `details` — `tool_end` frames carry no
|
|
30
|
+
`details`; the key is top-level.
|
|
31
|
+
- **Authority-envelope anti-forgery** (#391): all five harness-minted envelopes
|
|
32
|
+
(system-reminder / task-notification / new-diagnostics / user_memory / scope) are census-registered
|
|
33
|
+
(single source) and sink-neutralized by source; verbatim lanes stay byte-frozen with a positional
|
|
34
|
+
judgment bit. New disclosure counters `<outlet>.envelope` / `<outlet>.envelope_throttled` in
|
|
35
|
+
`TaskResult.stats.mechanisms.reminderDisclosures`; the trailer grows an envelope-family sentence.
|
|
36
|
+
- **MCP covering rules + resources-only mount** (#387/#386): `mcpRuleNameCovers` (CC's three
|
|
37
|
+
covering shapes) consulted in all five name-keyed lanes; resources-capable tools-less servers
|
|
38
|
+
mount; an out-of-charset top-level parameter name mounts WITH an operator advisory (connect leg).
|
|
39
|
+
- **openclaw benchmark rig** (design/184 ticket 2): HTTP-boundary comparison harness for the v3
|
|
40
|
+
auto-consolidation acceptance obligation (baseline bracket measured; head-to-head numbers pending
|
|
41
|
+
the openclaw build).
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- **Verification loops report the CUMULATIVE spend** (#406, test P0-KPI): runWithVerification /
|
|
45
|
+
resumeWithVerification returned only the last leg's stats (a $1.70 two-round run disclosed $0.70).
|
|
46
|
+
Every leg now folds through `buildCumulativeStats`; early-exit paths return the
|
|
47
|
+
accumulated-so-far account; the RB-368 unpriced axis is preserved.
|
|
48
|
+
- **The persisted-rule seat is time-bounded** (#415, test P0-KPI): `persistedRules.admits` was
|
|
49
|
+
awaited bare — a hanging provider wedged the approval gate forever. The wait now has the org
|
|
50
|
+
sibling's bound (15s + abort); timeout reads as could-not-answer (ask + unavailable), never a
|
|
51
|
+
clean no-rule.
|
|
52
|
+
- **MCP model-facing schemas keep validation-bearing values byte-faithful** (pre-release rescan,
|
|
53
|
+
fixed before publish): the intake neutralizer introduced in this window NFKC-folded enum/const/
|
|
54
|
+
default/examples/pattern values, so the model was shown spellings the server refuses. The schema
|
|
55
|
+
walk now carries a seat: prose (`description`/`title`/`$comment`) and parameter-name keys are
|
|
56
|
+
neutralized; validation-bearing values, unknown/vendor subtrees, and patternProperties KEYS
|
|
57
|
+
(regexes) transit byte-verbatim; `required`/`$ref`/`$defs`/anchor families move in lockstep with
|
|
58
|
+
rewritten names. **Flipped pin**: an advertised ZWSP-bearing enum member is now shown verbatim
|
|
59
|
+
(wire correctness over card-face literal defusing; prose/name lanes still guard spoofing).
|
|
60
|
+
- **Fence truncation is self-reported** (pre-release rescan): `fenceMcpServerInstructions`
|
|
61
|
+
pre-measured with the narrow tag set while the fence clipped the widened form — a server placing
|
|
62
|
+
instructions in the (cap−N, cap] window got a silent cut with no truncation note, on both the
|
|
63
|
+
stable and delta lanes. `delimitUntrustedWithClip` now reports its own clip; the RB-152 egress
|
|
64
|
+
`…[+N chars]` count stays anchored to the true source omission.
|
|
65
|
+
- **The consolidation scope wall holds on the read side** (pre-release rescan): a carrier in scope A
|
|
66
|
+
could suppress a target in scope B on every read face (edge validity never compared scopes; a
|
|
67
|
+
supported cross-scope MOVE keeps the rev byte-identical). Cross-scope edges are now inert —
|
|
68
|
+
existing stores: any cross-scope-suppressed entry RETURNS to the default face (revival direction).
|
|
69
|
+
- **The rewrite terminal carries provenance/trust**: dropping them minted the whitewash patch every
|
|
70
|
+
conforming backend must refuse (the plan parked forever holding the scope seat); a trust-only
|
|
71
|
+
entry was instead silently whitewashed on stores keying the guard on provenance.kind. Both fields
|
|
72
|
+
now carry forward beside origin/distilled (this also corrects plannedPostRev).
|
|
73
|
+
- **The CONFLICT notice stops overstating** (server/cli pins on the old string will red): it names
|
|
74
|
+
all three park causes (concurrent write / store-declined patch / re-judgment) and scopes the
|
|
75
|
+
no-overwrite claim to REFUSED targets (applied siblings stand; discard rolls nothing back);
|
|
76
|
+
retry-synthesized parks now write their own per-target audit rows.
|
|
77
|
+
- **memory_get pages the displayed string**: budgets/cursors previously addressed the raw body while
|
|
78
|
+
the fence displayed a longer neutralized one; the body is now neutralized once into a canonical
|
|
79
|
+
line array every page and cursor reads. Envelope-tag-bearing bodies page differently (fewer lines
|
|
80
|
+
per page, never over budget); marker-free bodies are byte-identical.
|
|
81
|
+
- **The third memory composer joins the widened tag family** (#391 class sweep):
|
|
82
|
+
`composeSelectiveBody` hard-coded `["user_memory"]` in its three sanitize seats while its two
|
|
83
|
+
siblings were widened to `MEMORY_INJECTION_TAGS`.
|
|
84
|
+
- **Contract texts catch up with minted disclosure keys**: `envelope`/`envelope_throttled`/
|
|
85
|
+
`mark_echo` join the `reminderDisclosures` JSDoc and docs/REMINDER-PROVENANCE.md (ratchet-pinned:
|
|
86
|
+
minted set ⊆ both texts; DISCLOSED tag set must be named in both).
|
|
87
|
+
|
|
88
|
+
### Notes
|
|
89
|
+
- Conformance fixture rename (`id-legacy-0001` → `id-plain-0001`); 14 `memory.consolidation_*`
|
|
90
|
+
codes registered non-governance (mechanism receipts, not per-principal verdicts); type-hygiene
|
|
91
|
+
memory-engine checked-cast baseline 14 → 23 (each behind a runtime validator added in the same
|
|
92
|
+
commits).
|
|
93
|
+
- Residuals filed, not shipped silently: #414① (refresh-leg advisory drop), #416 (loosenReasons
|
|
94
|
+
covering-awareness — a published-corpus change, scheduled with the names window), patternProperties
|
|
95
|
+
`$ref` pointer form (disclosed at the seat table).
|
|
96
|
+
|
|
3
97
|
## 5.54.0 — 2026-08-22
|
|
4
98
|
|
|
5
99
|
### Fixed
|
|
@@ -83,3 +83,29 @@ export interface CumulativeStatsAccumulator {
|
|
|
83
83
|
* base: a ratio is not additive, so the last leg's rate is never the operation's rate.
|
|
84
84
|
*/
|
|
85
85
|
export declare function buildCumulativeStats(base: TaskResult["stats"], acc: CumulativeStatsAccumulator): TaskResult["stats"];
|
|
86
|
+
/** A running {@link CumulativeStatsAccumulator} with the per-leg fold built in. */
|
|
87
|
+
export interface CumulativeStatsTracker {
|
|
88
|
+
/** Fold ONE leg's stats into the running totals. Call once per leg, in dispatch order. */
|
|
89
|
+
add(s: TaskResult["stats"]): void;
|
|
90
|
+
/** Build the cumulative stats over `base` — conventionally the FINAL leg's stats (it carries the
|
|
91
|
+
* non-accumulated fields such as `model`). */
|
|
92
|
+
build(base: TaskResult["stats"]): TaskResult["stats"];
|
|
93
|
+
/** How many legs have been folded in. A composition that ran exactly ONE leg can use this to hand that
|
|
94
|
+
* leg's own stats back untouched: the cumulative account over one leg IS that leg, and re-deriving it
|
|
95
|
+
* would rewrite a caller's object (0 → `undefined` normalization, a re-derived `cacheHitRate`) for no
|
|
96
|
+
* gain. */
|
|
97
|
+
readonly legs: number;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Create a {@link CumulativeStatsTracker}.
|
|
101
|
+
*
|
|
102
|
+
* {@link buildCumulativeStats} made the OUTPUT literal single-source, but the INPUT fold stayed copied:
|
|
103
|
+
* cascade / repair-loop / teacher each hand-maintain the same dozen `+=` lines, so a new stats field still
|
|
104
|
+
* has to be remembered in four places — the same duplication one level down from the hole this module
|
|
105
|
+
* exists to close. New call sites take this factory instead. Migrating the three originals onto it is a
|
|
106
|
+
* separate change (their folds are equivalent to this one today, but rewriting three shipped compositions
|
|
107
|
+
* is not part of a cost-accounting bug fix).
|
|
108
|
+
*
|
|
109
|
+
* OWN and NESTED stay DISJOINT here too: `add` never folds a leg's `nested.*` into the own totals.
|
|
110
|
+
*/
|
|
111
|
+
export declare function createCumulativeStatsTracker(): CumulativeStatsTracker;
|
|
@@ -27,3 +27,59 @@ export function buildCumulativeStats(base, acc) {
|
|
|
27
27
|
: undefined,
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
export function createCumulativeStatsTracker() {
|
|
31
|
+
let legs = 0;
|
|
32
|
+
const acc = {
|
|
33
|
+
tokens: 0,
|
|
34
|
+
turns: 0,
|
|
35
|
+
promptTokens: 0,
|
|
36
|
+
totalInputTokens: 0,
|
|
37
|
+
cachedTokens: 0,
|
|
38
|
+
outputTokens: 0,
|
|
39
|
+
cacheWriteTokens: 0,
|
|
40
|
+
cacheWriteTokensLong: 0,
|
|
41
|
+
costMicroUsd: 0,
|
|
42
|
+
anyLegUnpriced: false,
|
|
43
|
+
llmRootMicroUsd: 0,
|
|
44
|
+
compactionMicroUsd: 0,
|
|
45
|
+
nested: { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0 },
|
|
46
|
+
humanReview: { count: 0, totalWaitMs: 0, gates: [] },
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
get legs() {
|
|
50
|
+
return legs;
|
|
51
|
+
},
|
|
52
|
+
add(s) {
|
|
53
|
+
legs += 1;
|
|
54
|
+
acc.tokens += s.tokens;
|
|
55
|
+
acc.turns += s.turns;
|
|
56
|
+
acc.promptTokens += s.promptTokens ?? 0;
|
|
57
|
+
acc.totalInputTokens += s.totalInputTokens ?? 0;
|
|
58
|
+
acc.cachedTokens += s.cachedTokens ?? 0;
|
|
59
|
+
acc.outputTokens += s.outputTokens ?? 0;
|
|
60
|
+
acc.cacheWriteTokens += s.cacheWriteTokens ?? 0;
|
|
61
|
+
acc.cacheWriteTokensLong += s.cacheWriteTokensLong ?? 0;
|
|
62
|
+
acc.costMicroUsd = (acc.costMicroUsd ?? 0) + (s.costMicroUsd ?? 0);
|
|
63
|
+
if (s.costMicroUsd === undefined)
|
|
64
|
+
acc.anyLegUnpriced = true;
|
|
65
|
+
if (s.costBreakdown) {
|
|
66
|
+
acc.llmRootMicroUsd += s.costBreakdown.llmRootMicroUsd;
|
|
67
|
+
acc.compactionMicroUsd += s.costBreakdown.compactionMicroUsd;
|
|
68
|
+
}
|
|
69
|
+
if (s.humanReview) {
|
|
70
|
+
acc.humanReview.count += s.humanReview.count;
|
|
71
|
+
acc.humanReview.totalWaitMs += s.humanReview.totalWaitMs;
|
|
72
|
+
acc.humanReview.gates.push(...s.humanReview.gates);
|
|
73
|
+
}
|
|
74
|
+
if (s.nested) {
|
|
75
|
+
acc.nested.tokens += s.nested.tokens;
|
|
76
|
+
acc.nested.turns += s.nested.turns;
|
|
77
|
+
acc.nested.tasks += s.nested.tasks;
|
|
78
|
+
acc.nested.costMicroUsd += s.nested.costMicroUsd ?? 0;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
build(base) {
|
|
82
|
+
return buildCumulativeStats(base, acc);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { Type } from "typebox";
|
|
18
18
|
import type { AgentDefinition, TaskEvent } from "../core/types.js";
|
|
19
19
|
import { defineTool } from "../core/tools.js";
|
|
20
|
+
import { escapeEnvelopeTag } from "../core/untrusted-text.js";
|
|
20
21
|
/** The four tagged digest event kinds (CC `Tcg`) — `assistant_text` renders untagged. */
|
|
21
22
|
export declare const OBSERVER_EVENT_TAGS: readonly ["tool-call", "user-message", "tool-result", "turn-ended"];
|
|
22
23
|
export type ObserverDigestEvent = {
|
|
@@ -39,14 +40,17 @@ export type ObserverDigestEvent = {
|
|
|
39
40
|
/** CC `S$r` — anti-injection: a caller-declared envelope tag inside observed content is defused in place. */
|
|
40
41
|
export declare function escapeObserverTags(text: string): string;
|
|
41
42
|
/**
|
|
42
|
-
* CC `zZe` @8339092 —
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
43
|
+
* CC `zZe` @8339092 — the named-envelope tag escape. It is the second escape layer CC applies to the
|
|
44
|
+
* DYNAMIC envelope tag (`<{slug}-activity>`) after joining, which the static four-tag
|
|
45
|
+
* {@link escapeObserverTags} (Scg layer) cannot cover.
|
|
46
|
+
*
|
|
47
|
+
* RE-HOMED to `core/untrusted-text.ts` beside the ENGINE_ENVELOPES registry (whose guard column
|
|
48
|
+
* points at it): "escape one named envelope tag" is a containment primitive, not an observer
|
|
49
|
+
* concern, and having it live under `agents/` is what let two later envelopes (`skills`, `scope`)
|
|
50
|
+
* ship with no guard — nothing in the containment module named it. Re-exported here so this
|
|
51
|
+
* module's own call sites and `send-message-tool.ts` are unchanged.
|
|
48
52
|
*/
|
|
49
|
-
export
|
|
53
|
+
export { escapeEnvelopeTag };
|
|
50
54
|
/** CC `Yp` @8338930 — attribute-value escape: `oc` entities (& < >) plus quote/apostrophe. */
|
|
51
55
|
export declare function escapeAttributeValue(value: string): string;
|
|
52
56
|
/** CC `Eqi` = 2000 — the per-payload digest truncation bound (tool inputs/results, user messages). */
|
package/dist/agents/observer.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { defineTool } from "../core/tools.js";
|
|
3
3
|
import { createSafeNotifier } from "../core/safe-notify.js";
|
|
4
|
+
import { escapeEnvelopeTag } from "../core/untrusted-text.js";
|
|
4
5
|
export const OBSERVER_EVENT_TAGS = ["tool-call", "user-message", "tool-result", "turn-ended"];
|
|
5
6
|
const TAG_ESCAPE_RE = new RegExp(`<(?=/?(?:${OBSERVER_EVENT_TAGS.join("|")})(?:[>\\s/]|$))`, "gi");
|
|
6
7
|
export function escapeObserverTags(text) {
|
|
7
8
|
return text.replace(TAG_ESCAPE_RE, "<\\");
|
|
8
9
|
}
|
|
9
|
-
export
|
|
10
|
-
const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11
|
-
return text.replace(new RegExp(`<(?=/?${escapedTag}(?:[>\\s/]|$))`, "gi"), "<\\");
|
|
12
|
-
}
|
|
10
|
+
export { escapeEnvelopeTag };
|
|
13
11
|
export function escapeAttributeValue(value) {
|
|
14
12
|
return value
|
|
15
13
|
.replaceAll("&", "&")
|
package/dist/agents/verify.d.ts
CHANGED
|
@@ -172,9 +172,20 @@ export interface VerificationOutcome {
|
|
|
172
172
|
* Total cost (micro-USD) of the VERIFIER run(s) across all rounds — the verification OVERHEAD, separate from
|
|
173
173
|
* the implementation's own cost (which is the returned `TaskResult.stats`, as the verifier runs in its own
|
|
174
174
|
* session). Mirrors `runWithTeacher`'s `teacherStats` work-vs-overhead split. Σ of each verifier run's
|
|
175
|
-
* cost+nested.
|
|
176
|
-
*
|
|
177
|
-
* (the
|
|
175
|
+
* cost+nested.
|
|
176
|
+
*
|
|
177
|
+
* THE OPERATION TOTAL, exactly (note the middle term — it is easy to miss and this doc used to omit it):
|
|
178
|
+
* `stats.costMicroUsd + (stats.nested?.costMicroUsd ?? 0) + verification.verifierCost`. The axes are
|
|
179
|
+
* DISJOINT, so nothing is counted twice — but they are not symmetric, which is why the nested term is
|
|
180
|
+
* spelled out: THIS field already folds the verifier's own delegated spend into itself, while the
|
|
181
|
+
* implementation side keeps own and nested apart (the family convention — a consumer adds `stats.X +
|
|
182
|
+
* stats.nested.X` for the true total, and nested cost is never folded into `costMicroUsd`).
|
|
183
|
+
*
|
|
184
|
+
* That total is now exact **in the multi-round fix case too**, since the returned `stats` accumulates
|
|
185
|
+
* every impl-side leg (see {@link VerificationResult}). It held only for the single-pass case before: the
|
|
186
|
+
* returned stats were the FINAL impl attempt's alone, so once a fix round ran, the ORIGINAL attempt's
|
|
187
|
+
* cost was absent from the entire return value and any such sum silently undercounted the operation by
|
|
188
|
+
* exactly that much. Omitted
|
|
178
189
|
* (undefined) when no verifier ran (e.g. an impl that suspended/was opted out before verification).
|
|
179
190
|
*/
|
|
180
191
|
verifierCost?: number;
|
|
@@ -201,6 +212,19 @@ export interface VerificationOutcome {
|
|
|
201
212
|
export interface VerificationResult extends TaskResult {
|
|
202
213
|
/** The verification outcome. The task `result`/`status` is the implementation's; consult `verdict` for quality. */
|
|
203
214
|
verification: VerificationOutcome;
|
|
215
|
+
/**
|
|
216
|
+
* ACCOUNTING NOTE for the inherited `stats` (it is the ONE inherited field the gate does not simply pass
|
|
217
|
+
* through). The gate can run the implementation session SEVERAL times — the impl leg, then a continuation
|
|
218
|
+
* per fix round — and each leg's own `stats` cover only that leg. So `stats` here is the CUMULATIVE
|
|
219
|
+
* impl-side account (every impl + fix leg summed, own and nested kept disjoint as everywhere else), not
|
|
220
|
+
* the final leg's; the non-accumulated fields such as `model` come from the final leg. When exactly one
|
|
221
|
+
* leg contributed (the common no-fix path, and every early hand-back before a fix round), it is that
|
|
222
|
+
* leg's own object, untouched.
|
|
223
|
+
*
|
|
224
|
+
* The VERIFIER's spend is NOT in here — it is the separate overhead axis on
|
|
225
|
+
* {@link VerificationOutcome.verifierCost}, which documents the sum that gives the operation total.
|
|
226
|
+
*/
|
|
227
|
+
stats: TaskResult["stats"];
|
|
204
228
|
}
|
|
205
229
|
/**
|
|
206
230
|
* Verify an already-**completed** implementation `result` behind the independent falsification-style verifier,
|
package/dist/agents/verify.js
CHANGED
|
@@ -4,6 +4,7 @@ import { releaseSession } from "./session-util.js";
|
|
|
4
4
|
import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
|
|
5
5
|
import { delimitUntrusted, sanitizeUntrustedText } from "../core/untrusted-text.js";
|
|
6
6
|
import { createSafeNotifier } from "../core/safe-notify.js";
|
|
7
|
+
import { createCumulativeStatsTracker } from "./cumulative-stats.js";
|
|
7
8
|
export const VERIFICATION_PROMPT = `You are a verification specialist. Your job is NOT to confirm the implementation works — it is to try to BREAK it.
|
|
8
9
|
|
|
9
10
|
You have two documented failure patterns. First, verification avoidance: faced with a check, you find reasons not to run it — you read code, narrate what you would test, declare "PASS," and move on. Second, being seduced by the first 80%: a polished result or a passing test suite makes you inclined to pass it, not noticing the edge that crashes, the state that vanishes, the bad input that is unhandled. The first 80% is the easy part. Your entire value is in finding the last 20%.
|
|
@@ -172,6 +173,9 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
172
173
|
}
|
|
173
174
|
};
|
|
174
175
|
let current = result;
|
|
176
|
+
const implAccount = createCumulativeStatsTracker();
|
|
177
|
+
implAccount.add(result.stats);
|
|
178
|
+
const withImplAccount = (r) => implAccount.legs > 1 ? { ...r, stats: implAccount.build(r.stats) } : r;
|
|
175
179
|
let outcome = { verdict: "unverified", rounds: 0, findings: [] };
|
|
176
180
|
const startedAt = Date.now();
|
|
177
181
|
let spend = 0;
|
|
@@ -240,13 +244,14 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
240
244
|
...(foldedReadDeny.length > 0 ? { readDenyPatterns: [...foldedReadDeny] } : {}),
|
|
241
245
|
}, internals);
|
|
242
246
|
spend += (current.stats.costMicroUsd ?? 0) + (current.stats.nested?.costMicroUsd ?? 0);
|
|
247
|
+
implAccount.add(current.stats);
|
|
243
248
|
if (isDurablePause(current.status)) {
|
|
244
|
-
return { ...mapNestedSuspend(carryFrozenPosture(current)), verification: outcome };
|
|
249
|
+
return { ...mapNestedSuspend(carryFrozenPosture(withImplAccount(current))), verification: outcome };
|
|
245
250
|
}
|
|
246
251
|
if (current.status !== "completed")
|
|
247
252
|
break;
|
|
248
253
|
}
|
|
249
|
-
return { ...carryFrozenPosture(current), verification: outcome };
|
|
254
|
+
return { ...carryFrozenPosture(withImplAccount(current)), verification: outcome };
|
|
250
255
|
}
|
|
251
256
|
export async function runWithVerification(runner, implSpec, config = {}, internals) {
|
|
252
257
|
refuseUnhonorableInternals(internals, "door");
|
|
@@ -20,6 +20,20 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
|
20
20
|
"memory.challenge_sweep_failed",
|
|
21
21
|
"memory.lineage_settle_failed",
|
|
22
22
|
"memory.challenge_ledger_corrupt",
|
|
23
|
+
"memory.consolidation_recommended",
|
|
24
|
+
"memory.consolidation_committed",
|
|
25
|
+
"memory.consolidation_conflict",
|
|
26
|
+
"memory.consolidation_refused",
|
|
27
|
+
"memory.consolidation_disabled",
|
|
28
|
+
"memory.consolidation_gated",
|
|
29
|
+
"memory.consolidation_busy",
|
|
30
|
+
"memory.consolidation_stale_snapshot",
|
|
31
|
+
"memory.consolidation_gate_corrupt",
|
|
32
|
+
"memory.consolidation_governance_unreadable",
|
|
33
|
+
"memory.consolidation_lease_unavailable",
|
|
34
|
+
"memory.consolidation_backend_failed",
|
|
35
|
+
"memory.consolidation_gate_failed",
|
|
36
|
+
"memory.active_set_collapse",
|
|
23
37
|
"memory.challenge_ledger_oversize",
|
|
24
38
|
"memory.control_plane_rebuilt",
|
|
25
39
|
"memory.control_plane_not_corrupt",
|
package/dist/core/hooks.js
CHANGED
|
@@ -548,7 +548,7 @@ export async function runToolGate(input) {
|
|
|
548
548
|
catch {
|
|
549
549
|
return { unreadable: true };
|
|
550
550
|
}
|
|
551
|
-
return await pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true }));
|
|
551
|
+
return await settleOrgVerdictWithin(pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true })), { unreadable: true }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
|
|
552
552
|
})();
|
|
553
553
|
const hitEntry = answer.hit;
|
|
554
554
|
const hit = hitEntry?.rule;
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design/121 — LSP diagnostics registry + model-facing formatting (CC 2.1.198 parity).
|
|
3
|
-
*
|
|
4
|
-
* CC's two diagnostics sources (IDE MCP baseline/diff + passive LSP publishDiagnostics) collapse to
|
|
5
|
-
* ONE in sema: the passive registry. The "only NEW diagnostics" semantics CC gets from per-file
|
|
6
|
-
* baselines falls out of the delivered-set here — a diagnostic is injected at most once PER RUN, and a
|
|
7
|
-
* run editing a file clears its delivered set so a persisting problem can resurface (CC `Fjn` same
|
|
8
|
-
* behavior). The registry object itself is deployment-scoped, so "per run" is a keyed fact, not an
|
|
9
|
-
* object lifetime; see {@link LspDiagnosticsRegistry}.
|
|
10
|
-
*
|
|
11
|
-
* Volumes and wire format are CC-exact (198:320661 `Njn=10, nqa=30`; 198:320480-320505 summary +
|
|
12
|
-
* 4000-char cap; severity symbols ✖/⚠/ℹ/★).
|
|
13
|
-
*
|
|
14
|
-
* NOT durable: diagnostics regenerate from the language server on the next edit; a suspend/resume
|
|
15
|
-
* simply starts empty (recorded in design/121 §2).
|
|
16
|
-
*/
|
|
17
1
|
/** One LSP diagnostic, the subset the model/shell needs (LSP `Diagnostic` narrowed). */
|
|
18
2
|
export interface LspDiagnostic {
|
|
19
3
|
message: string;
|
|
@@ -102,7 +86,25 @@ export declare class LspDiagnosticsRegistry {
|
|
|
102
86
|
* {symbol} [Line {line+1}:{col+1}] {message}[ [{code}]][ ({source})]
|
|
103
87
|
* ```
|
|
104
88
|
* capped at 4000 chars with an honest `…[truncated]` tail.
|
|
89
|
+
*
|
|
90
|
+
* ENVELOPE CONTAINMENT (untrusted-text.ts `ENGINE_ENVELOPES`, the `new-diagnostics` row): the
|
|
91
|
+
* per-diagnostic strings (`message`, `code`, `source`) and the file basename come from a LANGUAGE
|
|
92
|
+
* SERVER — a process the engine launches over repository content, i.e. the same trust class as the
|
|
93
|
+
* repository. They land inside the `<new-diagnostics>` envelope, and (since this block is now
|
|
94
|
+
* delivered inside a marked reminder shell) inside an authority-marked reminder too, so a diagnostic
|
|
95
|
+
* message carrying `</new-diagnostics>` closed the envelope and placed server text at the top level
|
|
96
|
+
* of engine-authored authority. CC 223 does not neutralize here either — a deliberate, disclosed
|
|
97
|
+
* hardening divergence: CC ships the same block inside the same reminder shell, so the hole exists on
|
|
98
|
+
* both sides and only the fix is ours. Neutralization runs BEFORE the join, so the 4000-char cap
|
|
99
|
+
* still bounds the FINAL text and can never cut inside a neutralized marker's own bytes.
|
|
105
100
|
*/
|
|
106
101
|
export declare function formatDiagnosticsSummary(files: LspFileDiagnostics[]): string;
|
|
107
|
-
/** CC 198 model-facing injection block (198:320500-320503), verbatim framing.
|
|
102
|
+
/** CC 198 model-facing injection block (198:320500-320503), verbatim framing.
|
|
103
|
+
*
|
|
104
|
+
* DELIVERY (design/319 sibling work): the runner shells this block in a MARKED `<system-reminder>`
|
|
105
|
+
* before steering it — CC 223 `case "diagnostics"` does the same (`ih([Vr({content: …})])` → the
|
|
106
|
+
* reminder wrap), and until that was matched this was the engine's only authority text reaching the
|
|
107
|
+
* model as a bare block with no provenance mark at all. The block's own bytes are unchanged; the
|
|
108
|
+
* shell is applied at the delivery site (runtask), not baked in here, so this exported renderer keeps
|
|
109
|
+
* serving callers that place the block themselves. */
|
|
108
110
|
export declare function formatDiagnosticsBlock(files: LspFileDiagnostics[]): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "./untrusted-text.js";
|
|
1
2
|
const MAX_PER_FILE = 10;
|
|
2
3
|
const MAX_TOTAL = 30;
|
|
3
4
|
const MAX_SUMMARY_CHARS = 4000;
|
|
@@ -79,14 +80,19 @@ function basenameOfUri(uri) {
|
|
|
79
80
|
return idx >= 0 ? path.slice(idx + 1) : path;
|
|
80
81
|
}
|
|
81
82
|
export function formatDiagnosticsSummary(files) {
|
|
83
|
+
const safe = (t) => sanitizeUntrustedText(t, SHELLED_BODY_ENVELOPE_TAGS);
|
|
82
84
|
const lines = [];
|
|
83
85
|
for (const f of files) {
|
|
84
|
-
lines.push(`${basenameOfUri(f.uri)}:`);
|
|
86
|
+
lines.push(`${safe(basenameOfUri(f.uri))}:`);
|
|
85
87
|
for (const d of f.diagnostics) {
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
88
|
+
const startLine = d.range?.start?.line;
|
|
89
|
+
const startChar = d.range?.start?.character;
|
|
90
|
+
const pos = typeof startLine === "number" && Number.isFinite(startLine) && typeof startChar === "number" && Number.isFinite(startChar)
|
|
91
|
+
? `[Line ${Math.trunc(startLine) + 1}:${Math.trunc(startChar) + 1}] `
|
|
92
|
+
: "";
|
|
93
|
+
const code = d.code !== undefined ? ` [${safe(String(d.code))}]` : "";
|
|
94
|
+
const source = d.source ? ` (${safe(String(d.source))})` : "";
|
|
95
|
+
lines.push(` ${severitySymbol(d.severity)} ${pos}${safe(String(d.message))}${code}${source}`);
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
98
|
const summary = lines.join("\n");
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -488,6 +488,30 @@ interface McpContentItem {
|
|
|
488
488
|
};
|
|
489
489
|
}
|
|
490
490
|
export declare function mapContent(content: Array<McpContentItem>, serverName?: string, imageResizer?: McpImageResizer): Promise<Array<TextContent | ImageContent>>;
|
|
491
|
+
/**
|
|
492
|
+
* Connect to each MCP server, list its tools, and wrap them as AgentTools.
|
|
493
|
+
* Tools are namespaced `mcp__<server>__<tool>` (CC parity) to avoid collisions. The model sees each
|
|
494
|
+
* tool's real JSON-Schema (`inputSchema`), which the agent loop validates natively.
|
|
495
|
+
*
|
|
496
|
+
* Fail-open (design/29): a server that fails to connect or list tools is SKIPPED — its error is
|
|
497
|
+
* returned in `warnings`, the healthy servers still materialize. A single misconfigured server
|
|
498
|
+
* (bad stdio command, unreachable URL) never bricks the whole task.
|
|
499
|
+
*
|
|
500
|
+
* Call `dispose()` when the task finishes — connections are task-scoped, never persisted.
|
|
501
|
+
*/
|
|
502
|
+
/**
|
|
503
|
+
* Invisible / format / private-use / unassigned characters stripped out of every model-facing string a
|
|
504
|
+
* server advertises — CC `MZg` :144247. CC spells this as the property class BELOW followed by five
|
|
505
|
+
* explicit ranges (zero-width U+200B–U+200F, bidi embedding U+202A–U+202E, bidi isolates U+2066–U+2069,
|
|
506
|
+
* the BOM, and the BMP private-use area); on this engine's Unicode data every one of those code points
|
|
507
|
+
* is already `Cf` or `Co`, so the class alone is byte-equivalent to CC's expression and the source stays
|
|
508
|
+
* free of literal invisible characters (a file carrying them is a grep blind spot, and this repo has
|
|
509
|
+
* paid for that once already). The equivalence is asserted by a pin, not assumed — a Unicode-data change
|
|
510
|
+
* that moved any of those ranges out of the class would open exactly the hole this strips.
|
|
511
|
+
*
|
|
512
|
+
* `\p{Cc}` is deliberately NOT here: newlines and tabs are legitimate description formatting.
|
|
513
|
+
*/
|
|
514
|
+
export declare const MCP_INVISIBLE_TEXT_RE: RegExp;
|
|
491
515
|
/** One tool's schema-normalization outcome (CC 220 `zyo` @336616-336654's return shape). */
|
|
492
516
|
export type McpSchemaNormalizeResult = {
|
|
493
517
|
outcome: "unchanged";
|
|
@@ -537,6 +561,28 @@ export declare function normalizeMcpToolSchema(schema: unknown): McpSchemaNormal
|
|
|
537
561
|
* to catch, and this gate alone does not catch it (no type key ⇒ passes the root-type check; the
|
|
538
562
|
* combinator's own structure is legal JSON Schema ⇒ passes validateJsonSchemaShape too).
|
|
539
563
|
*/
|
|
564
|
+
/**
|
|
565
|
+
* A schema defect the operator must HEAR about but that must not cost the tool its mount — returns a
|
|
566
|
+
* core-authored sentence, or `undefined` when there is nothing to say.
|
|
567
|
+
*
|
|
568
|
+
* Today that is exactly one thing: a TOP-LEVEL parameter name outside `[a-zA-Z0-9_.-]{1,64}`. A name
|
|
569
|
+
* like `"user name"` is rejected at the Anthropic wire, and because the tool table rides EVERY request
|
|
570
|
+
* it takes the whole request down with it — while the operator previously got nothing at all pointing
|
|
571
|
+
* at the cause (no warning, no drop record, just a task that stopped working). CC checks the identical
|
|
572
|
+
* names against the identical expression (`EX_`/`_xo` :325793/:325824, reported as `check:"propertyKey"`).
|
|
573
|
+
*
|
|
574
|
+
* WHY AN ADVISORY AND NOT A DROP, stated because the sibling structural gate above does drop: CC's own
|
|
575
|
+
* disposition here is keep-and-warn — its drop arm sits behind a rollout flag that ships OFF (`Xyd` /
|
|
576
|
+
* `Yyd` :354641/:354622, an empty remote-config list ⇒ false), so CC's shipped behavior is to keep the
|
|
577
|
+
* tool and warn "requests that include it may fail". The difference from the structural half is real
|
|
578
|
+
* and not a technicality: a schema with no object root is unusable everywhere, whereas this charset is
|
|
579
|
+
* ONE provider's rule, and this engine is bring-your-own-model. Dropping here would delete a working
|
|
580
|
+
* tool from a deployment whose provider accepts the name — a Chinese- or Japanese-named parameter is
|
|
581
|
+
* the ordinary case, not a hostile one. Detection was the gap; removal was never the mandate.
|
|
582
|
+
*
|
|
583
|
+
* TOP-LEVEL only, like CC: a nested property name is not what the provider validates.
|
|
584
|
+
*/
|
|
585
|
+
export declare function mcpToolSchemaAdvisory(schema: unknown): string | undefined;
|
|
540
586
|
export declare function mcpToolSchemaProblem(schema: unknown): string | undefined;
|
|
541
587
|
export declare function materializeMcpTools(specs: McpServerSpec[], principal?: string, onElicit?: OnElicit, imageResizer?: McpImageResizer, // design/116 CONFIRM-1 seam: deployment-injected; default = auto-detected sharp
|
|
542
588
|
reminderDisclosure?: {
|