@sema-agent/core 5.53.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 +150 -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/subagent.d.ts +4 -0
- package/dist/agents/subagent.js +1 -1
- 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 +214 -5
- package/dist/core/mcp.js +315 -30
- 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-rule-model.d.ts +140 -21
- package/dist/core/permission-rule-model.js +76 -17
- package/dist/core/permission-rule-org.d.ts +4 -3
- package/dist/core/permission-rule-org.js +12 -3
- 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 +2 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/workflow-types.d.ts +16 -6
- package/dist/orchestration/workflow-types.js +10 -4
- package/dist/orchestration/workflow.js +32 -6
- package/dist/prompts/default.d.ts +12 -2
- package/dist/stores/file/background-agent-store.js +1 -0
- package/dist/stores/file/checkpoint-store.d.ts +6 -2
- package/dist/stores/file/checkpoint-store.js +1 -0
- package/dist/stores/file/fs-atomic.d.ts +151 -10
- package/dist/stores/file/fs-atomic.js +208 -32
- package/dist/stores/file/index.d.ts +26 -3
- package/dist/stores/file/index.js +25 -2
- package/dist/stores/file/shared-ledger.d.ts +40 -5
- package/dist/stores/file/shared-ledger.js +24 -8
- package/dist/stores/file/workflow-run-store.d.ts +8 -1
- package/dist/stores/file/workflow-run-store.js +1 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +71 -0
- package/dist/tools/fs/bash-readonly-classifier.js +58 -47
- package/dist/tools/fs/index.d.ts +3 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +30 -1
package/dist/core/types.d.ts
CHANGED
|
@@ -3342,12 +3342,27 @@ export interface TaskResult {
|
|
|
3342
3342
|
}>;
|
|
3343
3343
|
/** design/319 (B ticket, G9② observation seat) — reminder-disclosure trigger counts for the
|
|
3344
3344
|
* leg, keyed `<outlet>.<form>`: outlets `read` / `notebook` / `pdf` / `mcp` / `webFetch` /
|
|
3345
|
-
* `webSearch`; forms
|
|
3346
|
-
*
|
|
3347
|
-
* `
|
|
3348
|
-
*
|
|
3349
|
-
*
|
|
3350
|
-
*
|
|
3345
|
+
* `webSearch`; forms
|
|
3346
|
+
* · `bare` (bare-form trailer appended — reminder-shaped text without this session's mark),
|
|
3347
|
+
* · `marked` (marked-form trailer — never throttled),
|
|
3348
|
+
* · `bare_throttled` (a bare trailer suppressed by the 60s per-key window),
|
|
3349
|
+
* · `defused` (an MCP/web segment's exact-mark bytes were rewritten — the lane's one sanctioned
|
|
3350
|
+
* byte change, always paired with a `marked` disclosure),
|
|
3351
|
+
* · `envelope` (text shaped like one of the engine's OTHER authority envelopes — the DISCLOSED
|
|
3352
|
+
* subset is `task-notification` / `new-diagnostics` / `user_memory` / `skills`; `scope` is
|
|
3353
|
+
* fenced but not disclosed, since `<scope>…</scope>` is also an ordinary build-file element.
|
|
3354
|
+
* That family carries no mark, so its sentence is positional rather than byte-testable. It
|
|
3355
|
+
* rides ON the reminder copy when both families hit, so `envelope` can be bumped alongside
|
|
3356
|
+
* `marked`/`bare` on one result),
|
|
3357
|
+
* · `envelope_throttled` (an envelope sentence suppressed by its OWN 60s window — the two
|
|
3358
|
+
* families throttle independently, so one may emit while the other reports suppression),
|
|
3359
|
+
* · `mark_echo` (observation ONLY, never a disclosure: the session's mark VALUE appeared in
|
|
3360
|
+
* external bytes with no reminder-shaped tag around it, so the tag-grammar detector cannot
|
|
3361
|
+
* reach it. Nothing is rewritten and no trailer is appended; it is an upper bound on model
|
|
3362
|
+
* exposure for the lanes that do not defuse).
|
|
3363
|
+
* Present only when ≥1 key is non-zero. The map is OPEN by type — fold by key rather than
|
|
3364
|
+
* switching on a closed set. This is the D-4/D-6 re-ruling data (defuse/trailer widening to
|
|
3365
|
+
* Read/Bash/Grep): a reading, never a gate. */
|
|
3351
3366
|
reminderDisclosures?: Record<string, number>;
|
|
3352
3367
|
};
|
|
3353
3368
|
/**
|
|
@@ -3667,6 +3682,36 @@ export type TaskEvent = ({
|
|
|
3667
3682
|
* carry no string discriminator under either name.
|
|
3668
3683
|
*/
|
|
3669
3684
|
errorCode?: string;
|
|
3685
|
+
/**
|
|
3686
|
+
* WHICH call the gate was holding, when this frame is a park-contamination frame — the tool call
|
|
3687
|
+
* id of the gated call. Three conditions, ALL required: `errorCode === "gate.parked"`, the run is
|
|
3688
|
+
* tearing down behind a committed park, and that park binds a tool call at all.
|
|
3689
|
+
*
|
|
3690
|
+
* **WHERE IT COMES FROM — never lifted from the result**, and deliberately unlike its neighbour
|
|
3691
|
+
* {@link errorCode}: that one IS the result's own `details.code`, i.e. tool-authored, because a
|
|
3692
|
+
* tool classifying its own failure is bounded self-description. This field is an assertion about a
|
|
3693
|
+
* DIFFERENT call, so a tool able to author it could point an approval UI at a call nobody is
|
|
3694
|
+
* holding. It is therefore engine-minted — read from the run's committed park holder and handed to
|
|
3695
|
+
* the frame projection as a parameter, the same posture (and the same reason) as {@link settledBy}.
|
|
3696
|
+
* `errorCode` only participates in selecting WHETHER the id is placed; it never supplies the
|
|
3697
|
+
* value, and on its own it is not enough — the run must also be aborting behind a real park, which
|
|
3698
|
+
* is why a tool declaring `gate.parked` on its own failure cannot conjure this field.
|
|
3699
|
+
*
|
|
3700
|
+
* Why it is on the frame: a durable gate parks ONE call and the loop then short-circuits every
|
|
3701
|
+
* sibling in the batch with an identical "Operation aborted" body, so `gate.parked` alone says
|
|
3702
|
+
* "something parked this batch" without saying WHAT. A consumer wanting to render "waiting on
|
|
3703
|
+
* <the gated call>" beside the collateral frames otherwise has to re-derive the pair from batch
|
|
3704
|
+
* adjacency and timing — an inference that is wrong exactly when it matters (a batch with more
|
|
3705
|
+
* than one ask candidate, a reordered stream).
|
|
3706
|
+
*
|
|
3707
|
+
* ONLY EVER STRUCTURALLY PROVEN, never guessed: the id is read off the committed checkpoint's
|
|
3708
|
+
* pending tool call, so it is the same value the checkpoint parks on and the same value the
|
|
3709
|
+
* decide/resume lane answers about. It is therefore ABSENT — not approximated — for the park
|
|
3710
|
+
* kinds that hold no call (a resource-limit slice, a plan review), and absent for every abort
|
|
3711
|
+
* that is not a gate park at all (user interrupt, timeout, walltime): those frames are
|
|
3712
|
+
* byte-unchanged. Absence means "core cannot prove a causal call", never "there wasn't one".
|
|
3713
|
+
*/
|
|
3714
|
+
gatedCallId?: string;
|
|
3670
3715
|
/**
|
|
3671
3716
|
* WHAT ENDED THE APPROVAL this call was waiting on, when this frame closes a gated call — `"human"`
|
|
3672
3717
|
* / `"timeout"` / `"aborted"`, the vocabulary of
|
|
@@ -4889,6 +4934,27 @@ export interface EngineNotice {
|
|
|
4889
4934
|
* tier is NOT auto-downgraded (declaration-制 — observation reports, it never re-adjudicates);
|
|
4890
4935
|
* `detail: { handle }`.
|
|
4891
4936
|
*
|
|
4937
|
+
* - `"memory.consolidation_recommended"` (design/339 §2.2/§6.2) — the engine-minted per-scope
|
|
4938
|
+
* session count crossed the consolidation thresholds (time gate open ∧ enough distinct
|
|
4939
|
+
* sessions); minted at most once per crossing (a completed run re-arms the edge), NEVER when
|
|
4940
|
+
* the deployment leaves consolidation off. ADVISORY: the host owns the verbs, nothing runs
|
|
4941
|
+
* automatically; `detail: { scope, sessionsSince, sessionId? }`.
|
|
4942
|
+
* - `"memory.consolidation_committed"` (design/339 §6.2) — a consolidation plan reached
|
|
4943
|
+
* `completed`: products landed, superseded targets left the default read face (retained as
|
|
4944
|
+
* evidence), intents settled; `detail: { planId, scope, products, superseded, intents }` —
|
|
4945
|
+
* counts and engine-minted ids only, zero content.
|
|
4946
|
+
* - `"memory.consolidation_conflict"` (design/339 §6.2) — a plan parked CONFLICT: at least one
|
|
4947
|
+
* target did not reach its planned state (a concurrent write, a patch the store declined, or a
|
|
4948
|
+
* target the re-judgment could no longer act on — the plan's audit rows carry the per-target
|
|
4949
|
+
* reason). The REFUSED targets were not overwritten; patches the plan had already applied
|
|
4950
|
+
* stand (discard rolls nothing back — the receipt's applied-vs-conflict counts say how many).
|
|
4951
|
+
* Affected intents stay pending and wait for the host valve
|
|
4952
|
+
* (`resolveConsolidationPlan` retry/discard); `detail: { planId, scope }`.
|
|
4953
|
+
* - `"memory.consolidation_refused"` (design/339 §6.2) — the notice dialect of a consolidation
|
|
4954
|
+
* verb's coded structured refusal (the verb itself throws
|
|
4955
|
+
* {@link import("../core/memory-engine/consolidation.js").ConsolidationRefusedError});
|
|
4956
|
+
* `detail: { refusalCode, scope? }`.
|
|
4957
|
+
*
|
|
4892
4958
|
* Deliberately NOT a notice family: brain retry/reconnect liveness (a rate limit, a 5xx, a
|
|
4893
4959
|
* transient network failure being retried). Those are per-attempt liveness frames with their own
|
|
4894
4960
|
* frequency semantics and ride the wire `status` channel ({@link BrainStatus}), whose sink the
|
|
@@ -5110,6 +5176,23 @@ export interface RunnerDeps {
|
|
|
5110
5176
|
* never truthiness).
|
|
5111
5177
|
*/
|
|
5112
5178
|
memoryProvenance?: "off" | "carry";
|
|
5179
|
+
/**
|
|
5180
|
+
* design/339 §6.1 — the v3 memory-consolidation switch, threaded verbatim to the engine seat
|
|
5181
|
+
* ({@link import("../core/memory-engine/engine.js").MemoryEngineOptions.consolidation} — same
|
|
5182
|
+
* transport as {@link memoryProvenance}). ABSENT = OFF (the shipped default): no gate state is
|
|
5183
|
+
* ever written, no recommendation is ever minted, and the consolidation verbs on an engine the
|
|
5184
|
+
* host constructs over the same store refuse coded. PRESENT = enabled: the engine counts
|
|
5185
|
+
* distinct terminal-harvest sessions per scope and surfaces the advisory
|
|
5186
|
+
* `memory.consolidation_recommended` notice when the configured thresholds cross — EXECUTION
|
|
5187
|
+
* stays host-owned (the four verbs are engine host API; nothing in a task can trigger a run).
|
|
5188
|
+
* `provenance: "off"` beside this refuses loudly at prepare
|
|
5189
|
+
* (`config.memory_consolidation_provenance_off` — the fold law must be able to mint);
|
|
5190
|
+
* `multiNode: true` without a lease refuses (`config.memory_consolidation_lease_required`);
|
|
5191
|
+
* every other bad value refuses under `config.memory_consolidation` (#123). DEPLOYMENT seat
|
|
5192
|
+
* ONLY (no TaskSpec twin, not in the governed workflow whitelist), same law as
|
|
5193
|
+
* {@link memoryProvenance}.
|
|
5194
|
+
*/
|
|
5195
|
+
memoryConsolidation?: import("../core/memory-engine/consolidation.js").MemoryConsolidationOptions;
|
|
5113
5196
|
/**
|
|
5114
5197
|
* design/199 件A — the DEPLOYMENT's read-face declaration
|
|
5115
5198
|
* ({@link import("../tools/fs/read-face.js").ReadFace}; see {@link TaskSpec.readFace} for the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { delimitUntrusted } from "./untrusted-text.js";
|
|
1
|
+
import { delimitUntrusted, neutralizeForFence } from "./untrusted-text.js";
|
|
2
2
|
import { scrubSecrets, SECRET_PASSES, runRedactionPasses } from "./arg-summary.js";
|
|
3
3
|
import { sliceHeadSafe } from "./surrogate-safe-slice.js";
|
|
4
4
|
export { summarizeRedactions } from "./arg-summary.js";
|
|
@@ -98,5 +98,15 @@ export function boundedRedactedSummary(value, max) {
|
|
|
98
98
|
return boundedString(value, max, redactSecrets);
|
|
99
99
|
}
|
|
100
100
|
export function untrustedEgressForHuman(value, opts) {
|
|
101
|
-
|
|
101
|
+
const source = boundedString(value, Number.POSITIVE_INFINITY, redactHostLeaks);
|
|
102
|
+
let budget = opts.max;
|
|
103
|
+
let bounded = boundedString(source, budget, (s) => s);
|
|
104
|
+
for (let i = 0; i < 4 && budget > 0; i++) {
|
|
105
|
+
const overflow = [...neutralizeForFence(bounded)].length - opts.max;
|
|
106
|
+
if (overflow <= 0)
|
|
107
|
+
break;
|
|
108
|
+
budget = Math.max(0, budget - overflow);
|
|
109
|
+
bounded = boundedString(source, budget, (s) => s);
|
|
110
|
+
}
|
|
111
|
+
return delimitUntrusted(opts.label, bounded, opts.max);
|
|
102
112
|
}
|
|
@@ -16,6 +16,144 @@
|
|
|
16
16
|
* own (instructions and data share one channel). The real boundary is decorrelation + reading the objective
|
|
17
17
|
* artifact (the diff / working tree) rather than the worker's self-report (design/53 §3, design/54 §3).
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* One row of {@link ENGINE_ENVELOPES}.
|
|
21
|
+
*
|
|
22
|
+
* WHY A TABLE EXISTS. design/319 spent its whole threat model on a single tag and three independent
|
|
23
|
+
* texts stated the supporting premise as "this harness mints no other tag" — a MODULE-level
|
|
24
|
+
* enumeration (`turn-attachments` really does mint only one) promoted to a HARNESS-level claim. The
|
|
25
|
+
* engine in fact authors several model-facing envelopes, guarded by two different escape families
|
|
26
|
+
* living in two different modules, and the two envelopes added most recently (`skills`, `scope`)
|
|
27
|
+
* reached the model with NO guard at all — not because anyone argued they were safe, but because
|
|
28
|
+
* nothing in the tree listed the set an author was joining. A guard family without a census is a
|
|
29
|
+
* guard family that silently loses members; this table is that census, and the accompanying
|
|
30
|
+
* literal-tag test keeps it honest (a new closing-tag literal in `src/` fails until it is classified
|
|
31
|
+
* here).
|
|
32
|
+
*
|
|
33
|
+
* The same discipline as the `ToolSpec` completeness table: the registry is the declaration, the
|
|
34
|
+
* test is the gate, and a NEW envelope is a two-line edit rather than an invisible omission.
|
|
35
|
+
*/
|
|
36
|
+
export interface EngineEnvelope {
|
|
37
|
+
/** The tag NAME as the engine spells it, without angle brackets. */
|
|
38
|
+
readonly tag: string;
|
|
39
|
+
/**
|
|
40
|
+
* - `authority` — the engine authors it as model-facing HARNESS speech; seeing the tag is what
|
|
41
|
+
* makes the model read the content as system information, so a forgery is an authority claim.
|
|
42
|
+
* - `framing` — the engine wraps UNTRUSTED content in it as a labeled data frame; a forgery
|
|
43
|
+
* escapes the frame (or closes it early) rather than claiming harness authority.
|
|
44
|
+
* - `not-an-envelope` — a tag-shaped literal in `src/` that is not an engine-minted model-facing
|
|
45
|
+
* envelope at all (a MODEL-output grammar the engine parses, a prompt-assembly placeholder,
|
|
46
|
+
* rendering metadata, or copy that merely TALKS about an envelope). Listed so the census test
|
|
47
|
+
* has a home for every literal in the tree and no reader has to re-derive the classification.
|
|
48
|
+
*/
|
|
49
|
+
readonly kind: "authority" | "framing" | "not-an-envelope";
|
|
50
|
+
/** Where the engine authors (or, for `not-an-envelope`, spells) it. */
|
|
51
|
+
readonly mint: string;
|
|
52
|
+
/** How untrusted bytes reaching this envelope's own body/attributes are defused at that site. */
|
|
53
|
+
readonly guard: string;
|
|
54
|
+
/**
|
|
55
|
+
* True ⇒ neutralized inside every FENCED/inline untrusted projection ({@link delimitUntrusted},
|
|
56
|
+
* {@link inlineUntrusted}). Those lanes already rewrite bytes by contract, so a forged authority
|
|
57
|
+
* envelope arriving through them is defused rather than merely fenced.
|
|
58
|
+
*/
|
|
59
|
+
readonly fenced: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* True ⇒ DISCLOSED (never rewritten) when it appears in VERBATIM external data — the design/319
|
|
62
|
+
* trailer pipeline (reminder-disclosure.ts). Verbatim lanes are byte-frozen by the G3 veto (the
|
|
63
|
+
* Read↔Edit `old_string` quote-back loop), so the only available judgment bit is an appended,
|
|
64
|
+
* engine-minted note. Deliberately NOT set for tags whose spelling is common in ordinary data
|
|
65
|
+
* (`scope` is a Maven POM element; `summary` is HTML) — a trailer that fires on every build file
|
|
66
|
+
* teaches the model to ignore trailers.
|
|
67
|
+
*/
|
|
68
|
+
readonly disclosed: boolean;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The census. Rows are grouped by {@link EngineEnvelope.kind}; within a group, by module.
|
|
72
|
+
*
|
|
73
|
+
* KNOWN CEILING (stated, not hidden): the accompanying test enumerates CLOSING-TAG LITERALS in
|
|
74
|
+
* `src/`, so an envelope whose tag name is INTERPOLATED (`<${tag}>`) is invisible to it. Those are
|
|
75
|
+
* enumerated here by hand — `teammate-message`, the observer digest's four event tags, and the
|
|
76
|
+
* dynamic `<{slug}-activity>` wrapper — and each already carries its own escape at its mint site.
|
|
77
|
+
*/
|
|
78
|
+
export declare const ENGINE_ENVELOPES: readonly EngineEnvelope[];
|
|
79
|
+
/** The registry row for a tag name, or `undefined` when the tag is not in the census. */
|
|
80
|
+
export declare function engineEnvelope(tag: string): EngineEnvelope | undefined;
|
|
81
|
+
/** Registry-derived: the envelopes that read as HARNESS AUTHORITY on sight. */
|
|
82
|
+
export declare const ENGINE_AUTHORITY_ENVELOPE_TAGS: readonly string[];
|
|
83
|
+
/**
|
|
84
|
+
* Registry-derived: the tags {@link delimitUntrusted} / {@link inlineUntrusted} neutralize on top of
|
|
85
|
+
* the always-on reminder tag. These lanes carry EXTERNAL/worker bytes into a labeled data frame and
|
|
86
|
+
* already rewrite them by contract, so an authority envelope forged inside them is defused rather
|
|
87
|
+
* than merely fenced — the "forgery can be neutralized, never trusted" direction.
|
|
88
|
+
*
|
|
89
|
+
* Deliberately NOT applied inside {@link sanitizeUntrustedText} itself: that primitive is also used
|
|
90
|
+
* by mint sites over an ALREADY-ASSEMBLED body (the boundary/first-frame reminder wrap runs it over
|
|
91
|
+
* a body that may legitimately contain the engine's own `<skills>` block), so widening the primitive
|
|
92
|
+
* would have the engine defuse its OWN envelopes. Containment belongs to the fence, not the
|
|
93
|
+
* primitive.
|
|
94
|
+
*/
|
|
95
|
+
export declare const FENCED_LANE_ENVELOPE_TAGS: readonly string[];
|
|
96
|
+
/**
|
|
97
|
+
* The same family as {@link FENCED_LANE_ENVELOPE_TAGS}, as a MUTABLE array for every body that will be
|
|
98
|
+
* SHELLED in a marked `<system-reminder>` — the attachment sink, the listing/diagnostics producers,
|
|
99
|
+
* the durable-resume orphan notice, the post-tool-batch hook relay, the git frame.
|
|
100
|
+
*
|
|
101
|
+
* These are not fences. They render deployment/server/model-supplied strings into a body the run loop
|
|
102
|
+
* then wraps in engine authority, so a forged envelope inside one is laundered by the wrapper. The
|
|
103
|
+
* wrapper itself cannot blanket-neutralize the family — it also shells the one body that legitimately
|
|
104
|
+
* IS an envelope (`buildSkillsBlock`'s `<skills>` fence) — so containment is expressed as OWNERSHIP:
|
|
105
|
+
* this full set for every body that owns nothing, minus its own tag for the one that does (see
|
|
106
|
+
* `attachmentEnvelopeTags` in turn-attachments.ts).
|
|
107
|
+
*
|
|
108
|
+
* Allocated ONCE so the sanitizer's memoized break-out regex is keyed by a stable value.
|
|
109
|
+
*/
|
|
110
|
+
export declare const SHELLED_BODY_ENVELOPE_TAGS: string[];
|
|
111
|
+
/**
|
|
112
|
+
* Registry-derived: the tags the design/319 disclosure pipeline reports when they appear in VERBATIM
|
|
113
|
+
* external data (see {@link scanEnvelopeShaped}). `system-reminder` is absent on purpose — the mark
|
|
114
|
+
* pipeline scans it with a stronger (marked vs bare) verdict of its own.
|
|
115
|
+
*/
|
|
116
|
+
export declare const DISCLOSED_ENVELOPE_TAGS: readonly string[];
|
|
117
|
+
/** The result of one envelope-shaped scan of an external projection. */
|
|
118
|
+
export interface EnvelopeShapedScan {
|
|
119
|
+
/** ≥1 disclosed-family envelope tag (open or close) is present. */
|
|
120
|
+
hit: boolean;
|
|
121
|
+
/** The REGISTRY spellings that matched, de-duplicated, in registry order. Registry-owned strings —
|
|
122
|
+
* never the matched bytes — so a caller may render them into trusted copy safely. */
|
|
123
|
+
tags: string[];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The envelope-family sibling of {@link scanReminderShaped}: detect text shaped like one of the
|
|
127
|
+
* engine's OTHER authority envelopes inside external data. Read-only — verbatim lanes are byte
|
|
128
|
+
* frozen (design/319 G3 veto: the Read↔Edit `old_string` quote-back loop), so the disclosure
|
|
129
|
+
* pipeline's only move is to append an engine-minted note saying what was seen.
|
|
130
|
+
*
|
|
131
|
+
* Grammar is deliberately the same shape as the reminder scan (attribute / case / tag-internal
|
|
132
|
+
* whitespace tolerant, open OR close tag), and the tag SET comes from the registry rather than a
|
|
133
|
+
* hand-copied list, so adding an envelope to {@link ENGINE_ENVELOPES} with `disclosed: true` is the
|
|
134
|
+
* whole change. Same accepted residual as the reminder scan: a tag the grammar does not recognize
|
|
135
|
+
* (fragmented, Unicode-lookalike) is not a hit — defense in depth, not a guarantee.
|
|
136
|
+
*/
|
|
137
|
+
export declare function scanEnvelopeShaped(text: string): EnvelopeShapedScan;
|
|
138
|
+
/**
|
|
139
|
+
* Defuse a caller-declared occurrence of ONE specific envelope tag (opening AND closing) inside
|
|
140
|
+
* content, CC `zZe` @8339092 verbatim: ``t.replace(new RegExp(`<(?=/?${e}(?:[>\\s/]|$))`, "gi"), "<\\")``.
|
|
141
|
+
*
|
|
142
|
+
* Homed here (beside {@link ENGINE_ENVELOPES}) rather than in the observer module that first needed
|
|
143
|
+
* it: "escape one named envelope tag" is a containment primitive, and the registry's guard column
|
|
144
|
+
* points at it. Re-exported from `agents/observer.ts` for its existing importers.
|
|
145
|
+
*
|
|
146
|
+
* Deviation from CC (hardening): the tag is regex-escaped before entering the RegExp — CC
|
|
147
|
+
* interpolates raw, safe only because its slugs are `[a-zA-Z0-9_-]`; ours are too, but we don't rely
|
|
148
|
+
* on the caller for that invariant.
|
|
149
|
+
*
|
|
150
|
+
* NOTE the two sanctioned escape SHAPES in this tree, and why they are not merged: this one inserts
|
|
151
|
+
* a backslash (`<` → `<\`) because it is a byte-level CC anchor on the observer/teammate lanes;
|
|
152
|
+
* {@link sanitizeUntrustedText} inserts a zero-width space because its lanes are read by humans as
|
|
153
|
+
* well. Both are idempotent and both break the tag; a merge would move bytes on one of the two
|
|
154
|
+
* families for no security gain.
|
|
155
|
+
*/
|
|
156
|
+
export declare function escapeEnvelopeTag(tag: string, text: string): string;
|
|
19
157
|
/** design/319 (B ticket) — what one reminder-shaped scan of an external projection found. */
|
|
20
158
|
export interface ReminderShapedScan {
|
|
21
159
|
/** ≥1 reminder-shaped tag (open or close; attribute/case/whitespace tolerant) is present. */
|
|
@@ -87,6 +225,14 @@ export declare function defuseExactMarkInSegments(segments: readonly string[], m
|
|
|
87
225
|
* `<discussion>`, so an untrusted member could emit `</statement>` to break out (search [46] BUG2);
|
|
88
226
|
* the caller passes those tag names. Tag names must be literal (alphanumeric/hyphen) — they are code-supplied
|
|
89
227
|
* wrapper names, never untrusted input. Idempotent for prompt assembly (a defused tag no longer matches).
|
|
228
|
+
*
|
|
229
|
+
* WHY THE ALWAYS-ON SET IS ONE TAG AND NOT THE WHOLE {@link ENGINE_ENVELOPES} AUTHORITY FAMILY: this is a
|
|
230
|
+
* PRIMITIVE, and mint sites also run it over an ALREADY-ASSEMBLED body — the boundary/first-frame reminder
|
|
231
|
+
* wrap sanitizes a body that may legitimately BE the engine's own `<skills>` block, and the memory write
|
|
232
|
+
* scan diffs against it to decide what markup a note may contain. Widening the primitive would have the
|
|
233
|
+
* engine defuse its own envelopes and would silently move an unrelated store's admission rule. Family-wide
|
|
234
|
+
* containment therefore lives at the FENCE ({@link delimitUntrusted} / {@link inlineUntrusted}, via
|
|
235
|
+
* {@link FENCED_LANE_ENVELOPE_TAGS}) and at each envelope's own mint site (the registry's guard column).
|
|
90
236
|
*/
|
|
91
237
|
export declare function sanitizeUntrustedText(text: string, extraTags?: string[]): string;
|
|
92
238
|
/**
|
|
@@ -237,8 +383,13 @@ export declare function defuseControlChars(text: string): string;
|
|
|
237
383
|
* `\s` does NOT fully match: the C1 half carries the 8-bit CSI/OSC/ST forms (U+009B/U+009D/U+009C) a
|
|
238
384
|
* C1-honoring terminal treats like their ESC-prefixed spellings, so leaving them through would let a
|
|
239
385
|
* 'sanitized' value repaint the trusted line it is interpolated onto (adversarial round finding) — caps the
|
|
240
|
-
* length, then applies the same tag-neutralization
|
|
241
|
-
*
|
|
386
|
+
* length, then applies the same tag-neutralization + fence-sentinel (`<<<`/`>>>`) defusing the fenced
|
|
387
|
+
* body gets.
|
|
388
|
+
*
|
|
389
|
+
* The neutralized tag set is {@link FENCED_LANE_ENVELOPE_TAGS} — the engine's whole AUTHORITY envelope
|
|
390
|
+
* family, not the reminder tag alone. A value interpolated onto a TRUSTED line is the cheapest place to
|
|
391
|
+
* open a forged envelope, and this lane rewrites bytes by contract already, so the whole family is
|
|
392
|
+
* defused here.
|
|
242
393
|
* Defense-in-depth, NOT a guarantee (same posture as the rest of this module).
|
|
243
394
|
*/
|
|
244
395
|
export declare function inlineUntrusted(text: string, maxLen?: number): string;
|
|
@@ -250,8 +401,43 @@ export declare function inlineUntrusted(text: string, maxLen?: number): string;
|
|
|
250
401
|
export declare const REVIEWER_NOTE_MAX_BODY = 2048;
|
|
251
402
|
/**
|
|
252
403
|
* Wrap untrusted text in a clearly labeled opaque fence. The consuming prompt should instruct the model to
|
|
253
|
-
* treat everything inside as untrusted data — never as instructions. Sanitizes internally (
|
|
404
|
+
* treat everything inside as untrusted data — never as instructions. Sanitizes internally (authority-envelope
|
|
254
405
|
* neutralization + fence-sentinel defusing, on the body AND the label), so callers may pass raw
|
|
255
406
|
* worker/external text — and labels derived from external identifiers (hostnames, resource URIs).
|
|
407
|
+
*
|
|
408
|
+
* The neutralized tag set is {@link FENCED_LANE_ENVELOPE_TAGS} — the engine's whole AUTHORITY envelope
|
|
409
|
+
* family (design/319 sibling work): a fence declares its contents to be data, but a forged
|
|
410
|
+
* `<task-notification>` / `<user_memory>` / `<skills>` opening inside it was previously reproduced byte
|
|
411
|
+
* for byte, so a model that skims past the fence line reads harness-shaped text with no judgment bit.
|
|
412
|
+
* This lane rewrites bytes by contract (it is the CONTAINED half; the verbatim quote-back lanes stay
|
|
413
|
+
* frozen and are served by the disclosure trailer instead), so the whole family is defused here.
|
|
414
|
+
*
|
|
415
|
+
* A caller that must DISCLOSE a `maxBody` truncation calls {@link delimitUntrustedWithClip} instead —
|
|
416
|
+
* never a re-derived length test of its own (see that function and {@link neutralizeForFence}).
|
|
256
417
|
*/
|
|
257
418
|
export declare function delimitUntrusted(label: string, text: string, maxBody?: number): string;
|
|
419
|
+
/**
|
|
420
|
+
* The EXACT body neutralization {@link delimitUntrusted} applies, exported so a caller that has to
|
|
421
|
+
* MEASURE the fenced body (its length in code points, whether the cap will bite) measures the string
|
|
422
|
+
* the fence really carries instead of re-deriving the transform from its own copy of the tag list.
|
|
423
|
+
*
|
|
424
|
+
* Two copies of that list is precisely what drifted once the fenced lane widened past the reminder tag:
|
|
425
|
+
* a caller pre-counting with the default (reminder-only) set under-measured the wide-set result by one
|
|
426
|
+
* ZWSP per widened-family tag occurrence, so a body got clipped while the truncation disclosure that
|
|
427
|
+
* decision drove was withheld — an attacker-selectable window just under the cap. Idempotent (both
|
|
428
|
+
* halves are), so a caller may hand the neutralized string straight back to the fence.
|
|
429
|
+
*/
|
|
430
|
+
export declare function neutralizeForFence(text: string): string;
|
|
431
|
+
/**
|
|
432
|
+
* {@link delimitUntrusted} plus the clip verdict, for the callers that must DISCLOSE a truncation on a
|
|
433
|
+
* TRUSTED line outside the fence (§25 honesty: a bare ellipsis reads to the model as the source's own
|
|
434
|
+
* text, so a silent clip is a lie about what the source said).
|
|
435
|
+
*
|
|
436
|
+
* `clipped` is reported by the same measurement that performed the cut. A caller re-deriving "would this
|
|
437
|
+
* clip?" from its own transform + its own tag list is the shape that silently broke when the fence's tag
|
|
438
|
+
* set widened; there is no second condition here to keep in sync.
|
|
439
|
+
*/
|
|
440
|
+
export declare function delimitUntrustedWithClip(label: string, text: string, maxBody?: number): {
|
|
441
|
+
text: string;
|
|
442
|
+
clipped: boolean;
|
|
443
|
+
};
|