@sema-agent/core 5.48.0 → 5.50.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 +112 -0
- package/dist/agents/agent-transcript-tool.d.ts +1 -1
- package/dist/agents/agent-transcript-tool.js +1 -1
- package/dist/agents/roster-store.js +4 -1
- package/dist/agents/send-message-tool.d.ts +2 -2
- package/dist/agents/send-message-tool.js +2 -2
- package/dist/agents/subagent.d.ts +6 -0
- package/dist/agents/subagent.js +126 -1
- package/dist/agents/teacher.d.ts +25 -1
- package/dist/agents/teacher.js +89 -13
- package/dist/brain/anthropic.js +11 -20
- package/dist/brain/open-responses.js +6 -14
- package/dist/brain/openai.js +6 -18
- package/dist/brain/reasoning.d.ts +100 -8
- package/dist/brain/reasoning.js +39 -15
- package/dist/brain/request-params.d.ts +37 -1
- package/dist/brain/request-params.js +40 -2
- package/dist/core/background-agent-store.d.ts +1 -1
- package/dist/core/background-agent-store.js +5 -4
- package/dist/core/mcp.d.ts +7 -1
- package/dist/core/mcp.js +64 -8
- package/dist/core/memory-engine/delegation-settlement.d.ts +27 -0
- package/dist/core/memory-engine/delegation-settlement.js +31 -4
- package/dist/core/memory-engine/dual-root.js +11 -0
- package/dist/core/memory-engine/engine.d.ts +36 -2
- package/dist/core/memory-engine/engine.js +354 -38
- package/dist/core/memory-engine/layout.d.ts +43 -0
- package/dist/core/memory-engine/layout.js +59 -0
- package/dist/core/memory-engine/memory-backend-contract.js +120 -0
- package/dist/core/memory-engine/origin-clearance.d.ts +19 -0
- package/dist/core/memory-engine/origin-clearance.js +10 -0
- package/dist/core/memory-engine/provenance-wording.d.ts +15 -1
- package/dist/core/memory-engine/provenance-wording.js +1 -0
- package/dist/core/memory-engine/tools.js +6 -4
- package/dist/core/memory-engine/types.d.ts +13 -1
- package/dist/core/runner/prepare-task.js +22 -8
- package/dist/core/runner/runtask.d.ts +26 -1
- package/dist/core/runner/runtask.js +18 -2
- package/dist/core/strategy-store.d.ts +180 -3
- package/dist/core/strategy-store.js +172 -23
- package/dist/core/task-registry-agent.js +6 -0
- package/dist/core/types.d.ts +24 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +12 -0
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-governance.d.ts +27 -0
- package/dist/orchestration/workflow-governance.js +13 -0
- package/dist/orchestration/workflow-primitives.d.ts +8 -1
- package/dist/orchestration/workflow-primitives.js +11 -3
- package/dist/stores/file/file-snapshot-store.js +7 -1
- package/dist/stores/file/index.d.ts +8 -0
- package/dist/stores/file/index.js +12 -0
- package/dist/stores/file/session-policy-store.d.ts +0 -13
- package/dist/stores/file/session-policy-store.js +7 -1
- package/dist/stores/file/session-store.d.ts +4 -1
- package/dist/stores/file/session-store.js +7 -1
- package/dist/stores/file/strategy-store.d.ts +97 -0
- package/dist/stores/file/strategy-store.js +340 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +8 -1
|
@@ -6,8 +6,17 @@
|
|
|
6
6
|
* while a missed strategy just falls back to asking the teacher (baseline). `scope` is a **mandatory
|
|
7
7
|
* isolation boundary** (multi-tenant: one user's strategies must never leak to another).
|
|
8
8
|
*/
|
|
9
|
+
/** How a strategy first entered its scope. `"earned"` (or absent — older data predates the field) =
|
|
10
|
+
* stored by the escalation loop after a verified success; `"seeded"` = pre-installed via
|
|
11
|
+
* {@link seedStrategies}. An audit trail, NOT a security property: the store cannot authenticate
|
|
12
|
+
* its caller, so a direct `save` can claim either value — trust in seed material is the caller's
|
|
13
|
+
* decision, this field only records what the caller declared. */
|
|
14
|
+
export type StrategyOrigin = "earned" | "seeded";
|
|
9
15
|
export interface StoredStrategy {
|
|
10
|
-
/** Stable id.
|
|
16
|
+
/** Stable id. File-backed stores and {@link seedStrategies} require `[A-Za-z0-9_-]{1,64}` (the
|
|
17
|
+
* escalation loop mints UUIDs, which conform). NOTE: ids flow into host telemetry
|
|
18
|
+
* ({@link import("../agents/teacher.js").TeacherRunResult}`.strategiesInjected`) as join keys —
|
|
19
|
+
* do not encode sensitive text into an id. */
|
|
11
20
|
id: string;
|
|
12
21
|
/** The raw task objective that triggered the teacher's help — matched against future objectives. */
|
|
13
22
|
problem: string;
|
|
@@ -23,6 +32,23 @@ export interface StoredStrategy {
|
|
|
23
32
|
teacherModel?: string;
|
|
24
33
|
/** Reserved for a future generalized signature (v3 semantic matching). */
|
|
25
34
|
signature?: string;
|
|
35
|
+
/** Provenance stamp (see {@link StrategyOrigin}). Absent = earned (backward compatible). */
|
|
36
|
+
origin?: StrategyOrigin;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One contained strategy-store incident, as disclosed by a persistent store's `onIncident` seat and by
|
|
40
|
+
* `TeacherConfig.onStrategyStoreIncident`. Phases are split so "the write never happened" (`save`) is
|
|
41
|
+
* distinguishable from "the write landed but housekeeping failed" (`evict`), and a quarantined corrupt
|
|
42
|
+
* entry (`parse`) from a retrieval that degraded (`find`).
|
|
43
|
+
*/
|
|
44
|
+
export interface StrategyStoreIncident {
|
|
45
|
+
op: "find" | "save" | "evict" | "parse";
|
|
46
|
+
/** What was observed and what the operation degraded to. */
|
|
47
|
+
error: string;
|
|
48
|
+
/** The strategy id involved, when one is known. */
|
|
49
|
+
id?: string;
|
|
50
|
+
/** Absolute path of the offending file, when the incident is file-level (e.g. a `parse` quarantine). */
|
|
51
|
+
path?: string;
|
|
26
52
|
}
|
|
27
53
|
export interface StrategyStore {
|
|
28
54
|
/** Persist a strategy (implementations may merge an exact near-duplicate and cap per-scope size). */
|
|
@@ -43,10 +69,113 @@ export interface StrategyStore {
|
|
|
43
69
|
* (ruled 2026-08-04): this is a capacity cap, and a capacity cap can be widened but never turned off.
|
|
44
70
|
*/
|
|
45
71
|
prune?(scope: string, maxSize: number): Promise<void> | void;
|
|
72
|
+
/**
|
|
73
|
+
* Optional: how full a scope is (`used` entries out of `capacity`). {@link seedStrategies} uses it to
|
|
74
|
+
* refuse a batch that would not fit — silently evicting freshly seeded entries would be the most
|
|
75
|
+
* misleading possible "success". A store that cannot report usage simply omits this.
|
|
76
|
+
*/
|
|
77
|
+
scopeUsage?(scope: string): Promise<{
|
|
78
|
+
used: number;
|
|
79
|
+
capacity: number;
|
|
80
|
+
}> | {
|
|
81
|
+
used: number;
|
|
82
|
+
capacity: number;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Optional: is a content-identical entry (same normalized problem+strategy — the save-side merge
|
|
86
|
+
* key) already stored in `scope`? {@link seedStrategies} uses it to count only NET-NEW rows against
|
|
87
|
+
* the remaining capacity, so REPLAYING a batch whose entries already landed is never refused for
|
|
88
|
+
* "not fitting" space it does not need. A store that omits it gets the conservative whole-batch
|
|
89
|
+
* check (sound, but a replay into a full scope is falsely refused).
|
|
90
|
+
*/
|
|
91
|
+
hasStrategy?(scope: string, entry: Pick<StoredStrategy, "problem" | "strategy">): Promise<boolean> | boolean;
|
|
46
92
|
}
|
|
47
93
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
94
|
+
* Validate a retrieval cap fail-loud (ruled 2026-08-04, same posture as `WebFetchConfig.maxBytes` and
|
|
95
|
+
* the workflow resilience knobs). The cap was previously applied as
|
|
96
|
+
* `slice(0, Math.max(0, Number.isFinite(limit) ? limit : 0))`, which mapped NaN, `Infinity` and every
|
|
97
|
+
* negative onto the SAME value — 0 — so the two opposite intentions ("no cap" and "a fumbled unit")
|
|
98
|
+
* both came out as "return nothing", the most misleading possible answer for a high-precision store.
|
|
99
|
+
* `Infinity` is HONORED here (a query cap can legitimately be absent), unlike a capacity cap.
|
|
100
|
+
*/
|
|
101
|
+
export declare function resolveFindLimit(limit: number): number;
|
|
102
|
+
/**
|
|
103
|
+
* Validate a per-scope CAPACITY cap fail-loud (ruled 2026-08-04). Two silent failures lived here: a
|
|
104
|
+
* negative `maxSize` was clamped to 0 and quietly wiped the whole scope, and a non-finite one made the
|
|
105
|
+
* `arr.length > cap` comparison permanently false — eviction disabled, unbounded growth, no signal.
|
|
106
|
+
* `Infinity` is refused by name (the "turn the cap off" misconception) rather than silently behaving
|
|
107
|
+
* like the unbounded store it would create. Shared by the in-memory and file-backed stores.
|
|
108
|
+
*/
|
|
109
|
+
export declare function resolveCapacityCap(name: string, cap: number): number;
|
|
110
|
+
/** Id grammar for persisted/seeded strategies: filename-safe, no traversal, bounded. */
|
|
111
|
+
export declare const STRATEGY_ID_RE: RegExp;
|
|
112
|
+
/** Byte cap on `problem` (the matched objective text). */
|
|
113
|
+
export declare const MAX_STRATEGY_PROBLEM_BYTES = 8192;
|
|
114
|
+
/** Byte cap on `strategy` (the injected guidance — the teacher contract is 1-2 sentences). */
|
|
115
|
+
export declare const MAX_STRATEGY_TEXT_BYTES = 4096;
|
|
116
|
+
/** Byte caps on the remaining serialized string fields, so a legitimate save cannot mint an entry
|
|
117
|
+
* whose on-disk record dwarfs the payload caps above (the file store also bounds what it will READ,
|
|
118
|
+
* and these write-side caps are what make that read bound compatible with every honest record). */
|
|
119
|
+
export declare const MAX_STRATEGY_SCOPE_BYTES = 4096;
|
|
120
|
+
export declare const MAX_STRATEGY_TEACHER_MODEL_BYTES = 512;
|
|
121
|
+
export declare const MAX_STRATEGY_SIGNATURE_BYTES = 4096;
|
|
122
|
+
/** A real ISO-8601 timestamp is ~24-35 bytes; 64 leaves headroom for exotic offsets while keeping a
|
|
123
|
+
* "string that happens to sit in the ts seat" from bloating a serialized record. */
|
|
124
|
+
export declare const MAX_STRATEGY_TS_BYTES = 64;
|
|
125
|
+
/** A stored `ts` may not sit further in the future than this — a far-future timestamp would pin the
|
|
126
|
+
* entry at maximum recency forever (a rank-to-the-top vector). Small clock skew stays legal. */
|
|
127
|
+
export declare const MAX_STRATEGY_TS_FUTURE_MS: number;
|
|
128
|
+
/** Total byte budget across the strategy texts one retrieval hands to a prompt. Enforced by the file
|
|
129
|
+
* store's `find` (close to the tamperable data) AND at the escalation loop's injection site (so the
|
|
130
|
+
* bound holds for ANY backend — the in-memory store deliberately accepts legacy-shaped entries). */
|
|
131
|
+
export declare const MAX_STRATEGY_INJECTION_TOTAL_BYTES = 16384;
|
|
132
|
+
/**
|
|
133
|
+
* Structural validity of a (possibly untrusted, e.g. read-from-disk) value as a {@link StoredStrategy}:
|
|
134
|
+
* strings where strings are promised, id grammar (filename safety), byte caps, confidence domain
|
|
135
|
+
* (finite, 0-3 per the declared contract), scope non-emptiness, origin closed set. Returns a reason
|
|
136
|
+
* string when invalid, `null` when acceptable. Deliberately does NOT judge the timestamp's VALUE —
|
|
137
|
+
* that is a write-side rule only ({@link validateStrategyForWrite}): applying it on read would let a
|
|
138
|
+
* host clock stepping backwards mass-invalidate entries that were legal when written. A MALFORMED
|
|
139
|
+
* (unparseable) `ts` is also allowed through — read-side ranking clamps it to "oldest", so it
|
|
140
|
+
* self-buries instead of pinning.
|
|
141
|
+
*/
|
|
142
|
+
export declare function storedStrategyShapeIssue(v: unknown): string | null;
|
|
143
|
+
/**
|
|
144
|
+
* The write-side door: refuse an entry a persistent store must not accept — the structural checks of
|
|
145
|
+
* {@link storedStrategyShapeIssue} PLUS the future-timestamp bound (a far-future `ts` would pin the
|
|
146
|
+
* entry at maximum recency; refusing it at write keeps the rank honest, while the read side merely
|
|
147
|
+
* clamps so a backwards clock step cannot invalidate history).
|
|
148
|
+
*/
|
|
149
|
+
export declare function validateStrategyForWrite(s: StoredStrategy): void;
|
|
150
|
+
/**
|
|
151
|
+
* Compile a query into the shared high-precision match predicate. Returns `null` when the query has
|
|
152
|
+
* no significant terms (nothing meaningful to match on → don't inject random strategies). Single
|
|
153
|
+
* source for the in-memory and file-backed stores, so retrieval semantics cannot fork between
|
|
154
|
+
* backends.
|
|
155
|
+
*
|
|
156
|
+
* TWO acceptance arms, OR-combined, so the CJK upgrade can only ADD hits, never lose one:
|
|
157
|
+
* - full arm — every significant term (alphanumeric AND CJK) present in the problem;
|
|
158
|
+
* - legacy arm — every significant ALPHANUMERIC term present (and there is ≥1). This is exactly the
|
|
159
|
+
* pre-CJK predicate: it used to DISCARD CJK from the query, so "请帮我 extract tables from
|
|
160
|
+
* report.pdf" matched an English-only stored problem. Requiring the CJK boilerplate too would
|
|
161
|
+
* have silently un-matched that pair — a recall regression hiding inside a recall fix. Keeping
|
|
162
|
+
* the legacy arm costs precision only relative to a strictness nobody ever had.
|
|
163
|
+
*
|
|
164
|
+
* ACCEPTED COST (documented, pinned): via the legacy arm, a mixed query whose alphanumeric terms all
|
|
165
|
+
* match can hit a problem whose CJK terms CONFLICT (提取 X vs 删除 X) — exactly what the pre-CJK
|
|
166
|
+
* predicate always did, retained because losing old hits is the one regression this upgrade must not
|
|
167
|
+
* make. The mitigation is the surrounding depth (negation preamble, verify-before-use instruction,
|
|
168
|
+
* injection audit); tightening precision is a measured-recalibration change, not a retrieval patch.
|
|
169
|
+
*/
|
|
170
|
+
export declare function compileStrategyQuery(query: string): ((problem: string) => boolean) | null;
|
|
171
|
+
export declare function normalizeStrategyText(s: string): string;
|
|
172
|
+
/** Duplicate key for save-side merge and read-side dedup: same normalized problem AND strategy. */
|
|
173
|
+
export declare function strategyDedupKey(s: Pick<StoredStrategy, "problem" | "strategy">): string;
|
|
174
|
+
/** Ranking score: confidence weighted by recency (no `uses` — avoids a reinforcement feedback loop). */
|
|
175
|
+
export declare function scoreStoredStrategy(s: StoredStrategy): number;
|
|
176
|
+
/**
|
|
177
|
+
* Default in-memory store. Retrieval: a stored problem must contain **every significant** query term
|
|
178
|
+
* (and there must be ≥1) — near-exact on the meaningful terms, ignoring stopword noise. High precision
|
|
50
179
|
* by design. Ranked by `confidence × recency`. Per-scope capacity cap with eviction on save.
|
|
51
180
|
*/
|
|
52
181
|
export declare class InMemoryStrategyStore implements StrategyStore {
|
|
@@ -56,6 +185,54 @@ export declare class InMemoryStrategyStore implements StrategyStore {
|
|
|
56
185
|
save(s: StoredStrategy): void;
|
|
57
186
|
find(scope: string, query: string, limit: number): StoredStrategy[];
|
|
58
187
|
prune(scope: string, maxSize: number): void;
|
|
188
|
+
scopeUsage(scope: string): {
|
|
189
|
+
used: number;
|
|
190
|
+
capacity: number;
|
|
191
|
+
};
|
|
192
|
+
hasStrategy(scope: string, entry: Pick<StoredStrategy, "problem" | "strategy">): boolean;
|
|
59
193
|
/** Total stored strategies (across scopes); for tests/observability. */
|
|
60
194
|
get size(): number;
|
|
61
195
|
}
|
|
196
|
+
/** One entry to seed: a {@link StoredStrategy} minus the fields the seeder stamps (`scope` comes from
|
|
197
|
+
* the call, `origin` is forced to `"seeded"`, `ts` defaults to now). */
|
|
198
|
+
export type SeedStrategyEntry = Omit<StoredStrategy, "scope" | "origin" | "ts"> & {
|
|
199
|
+
ts?: string;
|
|
200
|
+
};
|
|
201
|
+
/** Result of {@link seedStrategies}: how many entries landed, and where the write phase stopped if it
|
|
202
|
+
* did. `failedAt` present = a PARTIAL seed (entries before it are in the store); the batch is
|
|
203
|
+
* idempotent by content, so re-running the same call is the supported recovery. */
|
|
204
|
+
export interface SeedStrategiesReport {
|
|
205
|
+
written: number;
|
|
206
|
+
failedAt?: {
|
|
207
|
+
id: string;
|
|
208
|
+
error: string;
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Seed a batch of strategies into `scope`, in two phases:
|
|
213
|
+
*
|
|
214
|
+
* 1. **Validate the whole batch first** — any invalid entry (shape/id grammar/byte caps/timestamp
|
|
215
|
+
* discipline, see {@link validateStrategyForWrite}), a duplicate id OR duplicate normalized
|
|
216
|
+
* content within the batch (two content-identical entries would merge into one, making `written`
|
|
217
|
+
* over-report and one seeded id unfindable), or a batch larger than the scope's remaining
|
|
218
|
+
* capacity (when the store reports usage) refuses the WHOLE batch loudly with ZERO writes.
|
|
219
|
+
* Silently evicting freshly seeded entries would be the most misleading possible "success", so a
|
|
220
|
+
* batch that cannot fully fit does not start.
|
|
221
|
+
* 2. **Write one by one** — a mid-batch failure returns a `{written, failedAt}` report instead of
|
|
222
|
+
* pretending to be a transaction; entries are idempotent (same id + content re-saves merge), so
|
|
223
|
+
* replaying the same batch completes the remainder.
|
|
224
|
+
*
|
|
225
|
+
* HONEST LIMITS (cache-store posture — no locks, no reservations):
|
|
226
|
+
* - the capacity preflight is a snapshot, not a reservation: with a CONCURRENT writer on the same
|
|
227
|
+
* scope, entries can still be evicted between the check and the writes (single-writer deployments
|
|
228
|
+
* — the aggregated backend under its boot lock — get the full guarantee);
|
|
229
|
+
* - an entry content-identical to one ALREADY STORED merges into the existing entry — the STORED id
|
|
230
|
+
* and the STORED `origin` stamp win (content earned before seeding stays `earned`; the requested
|
|
231
|
+
* id is not created). This counts as written: the content IS in the store, and replaying a batch
|
|
232
|
+
* relies on exactly that merge. Injection audit rows always carry the stored id, so joins hold.
|
|
233
|
+
*
|
|
234
|
+
* Every entry is stamped `origin: "seeded"`. That stamp is an AUDIT clue, not a security property —
|
|
235
|
+
* the store cannot authenticate the seeder, and a caller with store access can write anything; trust
|
|
236
|
+
* in the seed material is the caller's decision.
|
|
237
|
+
*/
|
|
238
|
+
export declare function seedStrategies(store: StrategyStore, scope: string, entries: readonly SeedStrategyEntry[]): Promise<SeedStrategiesReport>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { termSet } from "./memory-vector.js";
|
|
1
2
|
function invalidKnob(message, code) {
|
|
2
3
|
const e = new Error(message);
|
|
3
4
|
e.code = code;
|
|
4
5
|
return e;
|
|
5
6
|
}
|
|
6
|
-
function resolveFindLimit(limit) {
|
|
7
|
+
export function resolveFindLimit(limit) {
|
|
7
8
|
if (limit === Number.POSITIVE_INFINITY)
|
|
8
9
|
return limit;
|
|
9
10
|
if (!Number.isInteger(limit) || limit < 0) {
|
|
@@ -11,30 +12,109 @@ function resolveFindLimit(limit) {
|
|
|
11
12
|
}
|
|
12
13
|
return limit;
|
|
13
14
|
}
|
|
14
|
-
function resolveCapacityCap(name, cap) {
|
|
15
|
+
export function resolveCapacityCap(name, cap) {
|
|
15
16
|
if (cap === Number.POSITIVE_INFINITY) {
|
|
16
|
-
throw invalidKnob(`
|
|
17
|
+
throw invalidKnob(`strategy store ${name} cannot be Infinity — a per-scope capacity cap can be widened but not turned off; pass a large finite integer instead`, "config.strategy_max_size_invalid");
|
|
17
18
|
}
|
|
18
19
|
if (!Number.isInteger(cap) || cap < 0) {
|
|
19
|
-
throw invalidKnob(`
|
|
20
|
+
throw invalidKnob(`strategy store ${name} must be a non-negative integer (got ${String(cap)})`, "config.strategy_max_size_invalid");
|
|
20
21
|
}
|
|
21
22
|
return cap;
|
|
22
23
|
}
|
|
24
|
+
export const STRATEGY_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
25
|
+
export const MAX_STRATEGY_PROBLEM_BYTES = 8192;
|
|
26
|
+
export const MAX_STRATEGY_TEXT_BYTES = 4096;
|
|
27
|
+
export const MAX_STRATEGY_SCOPE_BYTES = 4096;
|
|
28
|
+
export const MAX_STRATEGY_TEACHER_MODEL_BYTES = 512;
|
|
29
|
+
export const MAX_STRATEGY_SIGNATURE_BYTES = 4096;
|
|
30
|
+
export const MAX_STRATEGY_TS_BYTES = 64;
|
|
31
|
+
export const MAX_STRATEGY_TS_FUTURE_MS = 24 * 60 * 60 * 1000;
|
|
32
|
+
export const MAX_STRATEGY_INJECTION_TOTAL_BYTES = 16384;
|
|
33
|
+
function invalidEntry(message, id) {
|
|
34
|
+
const e = new Error(message);
|
|
35
|
+
e.code = "strategy.entry_invalid";
|
|
36
|
+
if (id !== undefined)
|
|
37
|
+
e.strategyId = id;
|
|
38
|
+
return e;
|
|
39
|
+
}
|
|
40
|
+
export function storedStrategyShapeIssue(v) {
|
|
41
|
+
if (v === null || typeof v !== "object")
|
|
42
|
+
return "not an object";
|
|
43
|
+
const s = v;
|
|
44
|
+
if (typeof s.id !== "string" || !STRATEGY_ID_RE.test(s.id)) {
|
|
45
|
+
return `id must match [A-Za-z0-9_-]{1,64} (got ${JSON.stringify(String(s.id).slice(0, 80))})`;
|
|
46
|
+
}
|
|
47
|
+
if (typeof s.scope !== "string" || s.scope.length === 0)
|
|
48
|
+
return "scope must be a non-empty string";
|
|
49
|
+
if (Buffer.byteLength(s.scope, "utf8") > MAX_STRATEGY_SCOPE_BYTES)
|
|
50
|
+
return `scope exceeds ${MAX_STRATEGY_SCOPE_BYTES} bytes`;
|
|
51
|
+
if (typeof s.problem !== "string" || typeof s.strategy !== "string")
|
|
52
|
+
return "problem/strategy must be strings";
|
|
53
|
+
if (Buffer.byteLength(s.problem, "utf8") > MAX_STRATEGY_PROBLEM_BYTES) {
|
|
54
|
+
return `problem exceeds ${MAX_STRATEGY_PROBLEM_BYTES} bytes`;
|
|
55
|
+
}
|
|
56
|
+
if (Buffer.byteLength(s.strategy, "utf8") > MAX_STRATEGY_TEXT_BYTES) {
|
|
57
|
+
return `strategy text exceeds ${MAX_STRATEGY_TEXT_BYTES} bytes`;
|
|
58
|
+
}
|
|
59
|
+
if (typeof s.confidence !== "number" || !Number.isFinite(s.confidence) || s.confidence < 0 || s.confidence > 3) {
|
|
60
|
+
return `confidence must be a finite number in [0,3] (got ${String(s.confidence)})`;
|
|
61
|
+
}
|
|
62
|
+
if (typeof s.ts !== "string")
|
|
63
|
+
return "ts must be an ISO timestamp string";
|
|
64
|
+
if (Buffer.byteLength(s.ts, "utf8") > MAX_STRATEGY_TS_BYTES)
|
|
65
|
+
return `ts exceeds ${MAX_STRATEGY_TS_BYTES} bytes`;
|
|
66
|
+
if (s.teacherModel !== undefined && (typeof s.teacherModel !== "string" || Buffer.byteLength(s.teacherModel, "utf8") > MAX_STRATEGY_TEACHER_MODEL_BYTES)) {
|
|
67
|
+
return `teacherModel must be a string of at most ${MAX_STRATEGY_TEACHER_MODEL_BYTES} bytes when present`;
|
|
68
|
+
}
|
|
69
|
+
if (s.signature !== undefined && (typeof s.signature !== "string" || Buffer.byteLength(s.signature, "utf8") > MAX_STRATEGY_SIGNATURE_BYTES)) {
|
|
70
|
+
return `signature must be a string of at most ${MAX_STRATEGY_SIGNATURE_BYTES} bytes when present`;
|
|
71
|
+
}
|
|
72
|
+
if (s.origin !== undefined && s.origin !== "earned" && s.origin !== "seeded") {
|
|
73
|
+
return `origin must be "earned" or "seeded" when present (got ${String(s.origin)})`;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
export function validateStrategyForWrite(s) {
|
|
78
|
+
const issue = storedStrategyShapeIssue(s);
|
|
79
|
+
if (issue !== null) {
|
|
80
|
+
throw invalidEntry(`strategy ${issue}`, typeof s.id === "string" ? s.id : undefined);
|
|
81
|
+
}
|
|
82
|
+
const t = new Date(s.ts).getTime();
|
|
83
|
+
if (!Number.isNaN(t) && t > Date.now() + MAX_STRATEGY_TS_FUTURE_MS) {
|
|
84
|
+
throw invalidEntry(`strategy ts is in the future beyond the allowed skew (${s.ts})`, s.id);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
23
87
|
const STOP = new Set([
|
|
24
88
|
"the", "and", "for", "with", "from", "this", "that", "into", "your", "you", "are", "was", "were", "has",
|
|
25
89
|
"have", "had", "not", "but", "all", "any", "can", "use", "using", "via", "then", "than", "out", "get",
|
|
26
90
|
"got", "its", "it's", "their", "them", "they", "what", "when", "which", "who", "how", "why", "a", "an",
|
|
27
91
|
"of", "to", "in", "on", "is", "it", "or", "as", "at", "by", "be", "do", "if", "so", "no", "up", "we",
|
|
28
92
|
]);
|
|
29
|
-
|
|
30
|
-
|
|
93
|
+
const CJK_LEAD = /^[\u3040-\u30FF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7A3\uF900-\uFAFF]/;
|
|
94
|
+
function terms(s) {
|
|
95
|
+
return termSet(s);
|
|
31
96
|
}
|
|
32
97
|
function significant(s) {
|
|
33
|
-
return
|
|
98
|
+
return [...terms(s)].filter((t) => (CJK_LEAD.test(t) ? true : t.length >= 3 && !STOP.has(t)));
|
|
99
|
+
}
|
|
100
|
+
export function compileStrategyQuery(query) {
|
|
101
|
+
const qSig = significant(query);
|
|
102
|
+
if (qSig.length === 0)
|
|
103
|
+
return null;
|
|
104
|
+
const qAlnum = qSig.filter((t) => !CJK_LEAD.test(t));
|
|
105
|
+
return (problem) => {
|
|
106
|
+
const probTerms = terms(problem);
|
|
107
|
+
if (qSig.every((t) => probTerms.has(t)))
|
|
108
|
+
return true;
|
|
109
|
+
return qAlnum.length > 0 && qAlnum.every((t) => probTerms.has(t));
|
|
110
|
+
};
|
|
34
111
|
}
|
|
35
|
-
function
|
|
112
|
+
export function normalizeStrategyText(s) {
|
|
36
113
|
return s.trim().toLowerCase().replace(/\s+/g, " ");
|
|
37
114
|
}
|
|
115
|
+
export function strategyDedupKey(s) {
|
|
116
|
+
return `${normalizeStrategyText(s.problem)}\u0000${normalizeStrategyText(s.strategy)}`;
|
|
117
|
+
}
|
|
38
118
|
function ageDays(tsISO) {
|
|
39
119
|
const t = new Date(tsISO).getTime();
|
|
40
120
|
if (Number.isNaN(t)) {
|
|
@@ -42,7 +122,7 @@ function ageDays(tsISO) {
|
|
|
42
122
|
}
|
|
43
123
|
return Math.max(0, Date.now() - t) / 86_400_000;
|
|
44
124
|
}
|
|
45
|
-
function
|
|
125
|
+
export function scoreStoredStrategy(s) {
|
|
46
126
|
return (s.confidence + 1) * (1 / (1 + ageDays(s.ts)));
|
|
47
127
|
}
|
|
48
128
|
export class InMemoryStrategyStore {
|
|
@@ -53,19 +133,21 @@ export class InMemoryStrategyStore {
|
|
|
53
133
|
}
|
|
54
134
|
save(s) {
|
|
55
135
|
const arr = this.byScope.get(s.scope) ?? [];
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const dup = arr.find((e) => normalize(e.problem) === np && normalize(e.strategy) === ng);
|
|
136
|
+
const key = strategyDedupKey(s);
|
|
137
|
+
const dup = arr.find((e) => strategyDedupKey(e) === key);
|
|
59
138
|
if (dup) {
|
|
60
|
-
|
|
61
|
-
dup.
|
|
139
|
+
const incoming = Number.isFinite(s.confidence) ? s.confidence : undefined;
|
|
140
|
+
if (incoming !== undefined && incoming >= dup.confidence) {
|
|
141
|
+
dup.confidence = incoming;
|
|
142
|
+
dup.ts = s.ts;
|
|
143
|
+
}
|
|
62
144
|
dup.teacherModel = s.teacherModel ?? dup.teacherModel;
|
|
63
145
|
}
|
|
64
146
|
else {
|
|
65
147
|
arr.push(s);
|
|
66
148
|
}
|
|
67
149
|
if (arr.length > this.maxPerScope) {
|
|
68
|
-
arr.sort((a, b) =>
|
|
150
|
+
arr.sort((a, b) => scoreStoredStrategy(b) - scoreStoredStrategy(a));
|
|
69
151
|
arr.length = this.maxPerScope;
|
|
70
152
|
}
|
|
71
153
|
this.byScope.set(s.scope, arr);
|
|
@@ -76,24 +158,30 @@ export class InMemoryStrategyStore {
|
|
|
76
158
|
if (!arr || arr.length === 0) {
|
|
77
159
|
return [];
|
|
78
160
|
}
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
161
|
+
const matches = compileStrategyQuery(query);
|
|
162
|
+
if (matches === null) {
|
|
81
163
|
return [];
|
|
82
164
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return matches.sort((a, b) => score(b) - score(a)).slice(0, cap);
|
|
165
|
+
return arr
|
|
166
|
+
.filter((s) => matches(s.problem))
|
|
167
|
+
.sort((a, b) => scoreStoredStrategy(b) - scoreStoredStrategy(a))
|
|
168
|
+
.slice(0, cap);
|
|
88
169
|
}
|
|
89
170
|
prune(scope, maxSize) {
|
|
90
171
|
const n = resolveCapacityCap("maxSize", maxSize);
|
|
91
172
|
const arr = this.byScope.get(scope);
|
|
92
173
|
if (arr && arr.length > n) {
|
|
93
|
-
arr.sort((a, b) =>
|
|
174
|
+
arr.sort((a, b) => scoreStoredStrategy(b) - scoreStoredStrategy(a));
|
|
94
175
|
arr.length = n;
|
|
95
176
|
}
|
|
96
177
|
}
|
|
178
|
+
scopeUsage(scope) {
|
|
179
|
+
return { used: this.byScope.get(scope)?.length ?? 0, capacity: this.maxPerScope };
|
|
180
|
+
}
|
|
181
|
+
hasStrategy(scope, entry) {
|
|
182
|
+
const key = strategyDedupKey(entry);
|
|
183
|
+
return (this.byScope.get(scope) ?? []).some((e) => strategyDedupKey(e) === key);
|
|
184
|
+
}
|
|
97
185
|
get size() {
|
|
98
186
|
let n = 0;
|
|
99
187
|
for (const arr of this.byScope.values())
|
|
@@ -101,3 +189,64 @@ export class InMemoryStrategyStore {
|
|
|
101
189
|
return n;
|
|
102
190
|
}
|
|
103
191
|
}
|
|
192
|
+
function seedRefused(message) {
|
|
193
|
+
const e = new Error(message);
|
|
194
|
+
e.code = "strategy.seed_refused";
|
|
195
|
+
return e;
|
|
196
|
+
}
|
|
197
|
+
export async function seedStrategies(store, scope, entries) {
|
|
198
|
+
if (typeof scope !== "string" || scope.length === 0) {
|
|
199
|
+
throw seedRefused("seedStrategies: scope must be a non-empty string");
|
|
200
|
+
}
|
|
201
|
+
const now = new Date().toISOString();
|
|
202
|
+
const full = [];
|
|
203
|
+
const ids = new Set();
|
|
204
|
+
const contentKeys = new Set();
|
|
205
|
+
for (let i = 0; i < entries.length; i++) {
|
|
206
|
+
const e = entries[i];
|
|
207
|
+
const s = { ...e, ts: e.ts ?? now, scope, origin: "seeded" };
|
|
208
|
+
try {
|
|
209
|
+
validateStrategyForWrite(s);
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
throw seedRefused(`seedStrategies: entry ${i} refused, zero entries written — ${err.message}`);
|
|
213
|
+
}
|
|
214
|
+
if (ids.has(s.id)) {
|
|
215
|
+
throw seedRefused(`seedStrategies: duplicate id ${JSON.stringify(s.id)} within the batch, zero entries written`);
|
|
216
|
+
}
|
|
217
|
+
ids.add(s.id);
|
|
218
|
+
const key = strategyDedupKey(s);
|
|
219
|
+
if (contentKeys.has(key)) {
|
|
220
|
+
throw seedRefused(`seedStrategies: entry ${i} (${JSON.stringify(s.id)}) duplicates another batch entry's content, zero entries written`);
|
|
221
|
+
}
|
|
222
|
+
contentKeys.add(key);
|
|
223
|
+
full.push(s);
|
|
224
|
+
}
|
|
225
|
+
if (store.scopeUsage) {
|
|
226
|
+
const usage = await store.scopeUsage(scope);
|
|
227
|
+
let netNew = full.length;
|
|
228
|
+
if (store.hasStrategy) {
|
|
229
|
+
netNew = 0;
|
|
230
|
+
for (const s of full) {
|
|
231
|
+
if (!(await store.hasStrategy(scope, s)))
|
|
232
|
+
netNew++;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const remaining = Math.max(0, usage.capacity - usage.used);
|
|
236
|
+
if (netNew > remaining) {
|
|
237
|
+
throw seedRefused(`seedStrategies: ${netNew} net-new entries exceed the scope's remaining capacity ${remaining} ` +
|
|
238
|
+
`(${usage.used}/${usage.capacity} used), zero entries written — widen the capacity or trim the batch`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
let written = 0;
|
|
242
|
+
for (const s of full) {
|
|
243
|
+
try {
|
|
244
|
+
await store.save(s);
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
return { written, failedAt: { id: s.id, error: String(err.message ?? err) } };
|
|
248
|
+
}
|
|
249
|
+
written++;
|
|
250
|
+
}
|
|
251
|
+
return { written };
|
|
252
|
+
}
|
|
@@ -233,6 +233,12 @@ export async function reapDurableAgentsLane(core, scope, deps, policy) {
|
|
|
233
233
|
continue;
|
|
234
234
|
}
|
|
235
235
|
rowsReaped++;
|
|
236
|
+
{
|
|
237
|
+
const staleInProc = core.handles.get(r.handle);
|
|
238
|
+
if (staleInProc !== undefined && staleInProc.status !== "running" && staleInProc.status !== "pending" && staleInProc.status !== "parked") {
|
|
239
|
+
core.handles.delete(r.handle);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
236
242
|
core.reapedHandles.add(r.handle);
|
|
237
243
|
if (deps.mailbox !== undefined) {
|
|
238
244
|
try {
|
package/dist/core/types.d.ts
CHANGED
|
@@ -4730,6 +4730,15 @@ export interface EngineNotice {
|
|
|
4730
4730
|
* `detail: { total, stripped: [{ key, reason }], omitted? }`, the rendered key list bounded in count
|
|
4731
4731
|
* and length because the names come from the untrusted script. One aggregated notice per governed
|
|
4732
4732
|
* child build, not de-duplicated across builds: each spec is a distinct fact.
|
|
4733
|
+
* - `"mcp.revocation_probe_failed"` (design/338) — the deployment's `mcpRevocations.isRevoked`
|
|
4734
|
+
* probe threw; MCP dispatch FAILS OPEN (revocation is a tightening face) and this announces
|
|
4735
|
+
* once per run. `detail: { message }`. The refusal itself (`mcp.server_revoked`) is a tool
|
|
4736
|
+
* RESULT code, not a notice.
|
|
4737
|
+
* - `"config.models_swapped"` — `Runner.swapModels` replaced the model catalog generation
|
|
4738
|
+
* (zero-restart model switching). `detail: { models, tiers }` — key COUNTS only, never the
|
|
4739
|
+
* catalog itself. In-flight tasks finish on the models they resolved at prepare (natural
|
|
4740
|
+
* snapshot); every later prepare resolves against the new generation. A failed swap (illegal
|
|
4741
|
+
* tier binding) throws atomically and mints nothing.
|
|
4733
4742
|
* - `"config.read_face_deployment_clamped"` (#237) — a deployment-wide `readFace: "open"` is not
|
|
4734
4743
|
* in force beside a read-only (verifier) mount: it clamps to "roots" without throwing
|
|
4735
4744
|
* (stricter-wins; the clamp verdict stands, only its occurrence was undisclosed). Announced
|
|
@@ -4833,7 +4842,8 @@ export interface EngineNotice {
|
|
|
4833
4842
|
* - `"delegation.transcript_integrity"` (subagent transcript persistence) — a durable agent row
|
|
4834
4843
|
* with a BOUND transcript sessionId met a session store that attests `not_found` for it: the
|
|
4835
4844
|
* deployment's declared transcript durability is being contradicted by reality. Announced at
|
|
4836
|
-
* most once per (handle, process)
|
|
4845
|
+
* most once per (scope, handle, process) — `detail: { handle, scope? }`, scope = the resolved
|
|
4846
|
+
* access scope of the read that found the gap — from the continuation read faces (SendMessage preflight /
|
|
4837
4847
|
* AgentTranscript's durable leg); the per-call honest refusals are unchanged, and the declared
|
|
4838
4848
|
* tier is NOT auto-downgraded (declaration-制 — observation reports, it never re-adjudicates);
|
|
4839
4849
|
* `detail: { handle }`.
|
|
@@ -4892,6 +4902,19 @@ export interface RunnerDeps {
|
|
|
4892
4902
|
brain: Brain;
|
|
4893
4903
|
/** Catalog used to resolve string ModelRefs to Model objects. */
|
|
4894
4904
|
models?: Record<string, Model>;
|
|
4905
|
+
/**
|
|
4906
|
+
* design/338 (mid-turn MCP revocation) — the HOST's revocation ledger, probed synchronously at
|
|
4907
|
+
* every MCP dispatch (tool call + the three resource tools) BEFORE the transport. The engine
|
|
4908
|
+
* never caches the answer: the ledger's one authority lives on the host (a cached copy would be
|
|
4909
|
+
* a split-state second authority). A revoked server's calls settle as the coded refusal
|
|
4910
|
+
* `mcp.server_revoked` with known-not-executed wording; in-flight calls a revocation raced are
|
|
4911
|
+
* deliberately not chased (the threat shape is "new calls after removal"). Absent seat = the
|
|
4912
|
+
* pre-338 semantics. A THROWING probe fails open (revocation is a tightening face — a broken
|
|
4913
|
+
* probe must not brick every MCP call) with a once-per-run `mcp.revocation_probe_failed` notice.
|
|
4914
|
+
*/
|
|
4915
|
+
mcpRevocations?: {
|
|
4916
|
+
isRevoked(serverName: string): boolean;
|
|
4917
|
+
};
|
|
4895
4918
|
/**
|
|
4896
4919
|
* design/147 S1c (clay ruling 2026-07-18) — the DURABLE name→agent roster behind explicit-name
|
|
4897
4920
|
* addressing, a storage-tier seam like the checkpoint store: core bundles `MemoryRosterStore`
|
package/dist/index.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export type { RepetitionEvent, RepetitionInspection } from "./brain/repetition.j
|
|
|
40
40
|
export { computeCostMicroUsd, modelCostToPricing, type ModelPricing, type TokenCounts, } from "./core/pricing.js";
|
|
41
41
|
export { cacheFamilyOf, promptTokensOf, uncachedInputTokensOf, type CacheFamily } from "./core/runner/usage-accounting.js";
|
|
42
42
|
export { emitTrace, type ToolDisclosureManifest, type TraceEvent, type TracerHook } from "./core/trace.js";
|
|
43
|
-
export { InMemoryStrategyStore, type StrategyStore, type StoredStrategy } from "./core/strategy-store.js";
|
|
43
|
+
export { InMemoryStrategyStore, seedStrategies, type StrategyStore, type StoredStrategy, type StrategyOrigin, type StrategyStoreIncident, type SeedStrategyEntry, type SeedStrategiesReport, } from "./core/strategy-store.js";
|
|
44
44
|
export { createSqlTool, validateReadOnlySql, type SqlToolOptions } from "./tools/sql.js";
|
|
45
45
|
export { runWithTeacher, parseTeacherAdvice, TEACHER_PROMPT, type TeacherConfig, type TeacherAdvice, type EscalationRecord, type EscalationTrigger, type TeacherRunResult, } from "./agents/teacher.js";
|
|
46
46
|
export { runWithVerification, resumeWithVerification, verifyCompleted, runDeveloperTask, VERIFICATION_PROMPT, STATIC_VERIFICATION_PROMPT, VerdictSchema, type Verdict, type VerifyConfig, type UnverifiedReason, type VerificationOutcome, type VerificationResult, type DeveloperTaskConfig, } from "./agents/verify.js";
|
|
@@ -102,7 +102,7 @@ export { ENV_LIFETIME_SUSPEND_MARGIN_MS, USAGE_WINDOW_REAP_MARGIN_MS } from "./c
|
|
|
102
102
|
export { InMemoryFileSnapshotStore, DEFAULT_SNAPSHOT_BOUNDS } from "./core/file-snapshot-store.js";
|
|
103
103
|
export { captureManifest, applyManifest } from "./core/file-snapshot-store.js";
|
|
104
104
|
export type { FileSnapshotStore, FileSnapshotResult, FileSnapshotError, FileSnapshotBounds } from "./core/file-snapshot-store.js";
|
|
105
|
-
export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemoryStore, FileToolResultStore, FileSessionPolicyStore, FileFileSnapshotStore, FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult, resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock, atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog, type FileStorageBackendOptions, type FileStorageCorruptReadInfo, type FileSessionRepoOptions, type FileFileSnapshotStoreOptions, type FileCheckpointStoreOptions, } from "./stores/file/index.js";
|
|
105
|
+
export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemoryStore, FileToolResultStore, FileSessionPolicyStore, FileFileSnapshotStore, FileStrategyStore, FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult, resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock, atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog, type FileStorageBackendOptions, type FileStorageCorruptReadInfo, type FileStrategyStoreOptions, type FileSessionRepoOptions, type FileFileSnapshotStoreOptions, type FileCheckpointStoreOptions, } from "./stores/file/index.js";
|
|
106
106
|
export { CacheBreakDetector, type CacheBreakFinding, type ToolFingerprintInput } from "./core/cache-break-detector.js";
|
|
107
107
|
export { maybeCompact, type MaybeCompactOptions, type CompactionWindowSafetyInfo } from "./core/auto-compaction.js";
|
|
108
108
|
export { brainToRuntime } from "./core/runtime.js";
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/
|
|
|
28
28
|
export { computeCostMicroUsd, modelCostToPricing, } from "./core/pricing.js";
|
|
29
29
|
export { cacheFamilyOf, promptTokensOf, uncachedInputTokensOf } from "./core/runner/usage-accounting.js";
|
|
30
30
|
export { emitTrace } from "./core/trace.js";
|
|
31
|
-
export { InMemoryStrategyStore } from "./core/strategy-store.js";
|
|
31
|
+
export { InMemoryStrategyStore, seedStrategies, } from "./core/strategy-store.js";
|
|
32
32
|
export { createSqlTool, validateReadOnlySql } from "./tools/sql.js";
|
|
33
33
|
export { runWithTeacher, parseTeacherAdvice, TEACHER_PROMPT, } from "./agents/teacher.js";
|
|
34
34
|
export { runWithVerification, resumeWithVerification, verifyCompleted, runDeveloperTask, VERIFICATION_PROMPT, STATIC_VERIFICATION_PROMPT, VerdictSchema, } from "./agents/verify.js";
|
|
@@ -81,7 +81,7 @@ export { FileUsageWindowStore } from "./stores/file/usage-window-store.js";
|
|
|
81
81
|
export { ENV_LIFETIME_SUSPEND_MARGIN_MS, USAGE_WINDOW_REAP_MARGIN_MS } from "./core/runner/prepare-task.js";
|
|
82
82
|
export { InMemoryFileSnapshotStore, DEFAULT_SNAPSHOT_BOUNDS } from "./core/file-snapshot-store.js";
|
|
83
83
|
export { captureManifest, applyManifest } from "./core/file-snapshot-store.js";
|
|
84
|
-
export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemoryStore, FileToolResultStore, FileSessionPolicyStore, FileFileSnapshotStore, FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult, resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock, atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog, } from "./stores/file/index.js";
|
|
84
|
+
export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemoryStore, FileToolResultStore, FileSessionPolicyStore, FileFileSnapshotStore, FileStrategyStore, FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult, resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock, atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog, } from "./stores/file/index.js";
|
|
85
85
|
export { CacheBreakDetector } from "./core/cache-break-detector.js";
|
|
86
86
|
export { maybeCompact } from "./core/auto-compaction.js";
|
|
87
87
|
export { brainToRuntime } from "./core/runtime.js";
|
|
@@ -234,6 +234,18 @@ export interface RunWorkflowToolDeps {
|
|
|
234
234
|
parentReadFace?: () => import("../core/types.js").TaskSpec["readFace"];
|
|
235
235
|
/** Twin of the above for the deny-set additions (union, not replace). */
|
|
236
236
|
parentReadDenyPatterns?: () => import("../core/types.js").TaskSpec["readDenyPatterns"];
|
|
237
|
+
/** #345 ③ — the HOST task's write-hands clamp, the read-face clamp's sibling on this lane (the
|
|
238
|
+
* subagent delegation lane has carried it as `ToolExecuteContext.handsReadOnly` since its own
|
|
239
|
+
* tighten-only ruling; the workflow lane was the remaining gap — a read-only host's workflow
|
|
240
|
+
* children spawned with full write hands). Filled ONLY when the clamp is ON (`true`), exactly
|
|
241
|
+
* like the ctx seat, so an unclamped deployment's mount gains no key. Known at prepare-time
|
|
242
|
+
* (frozen TaskSpec snapshot — no lazy getter needed, the `parentCheckpointStoreDisabled` shape). */
|
|
243
|
+
parentHandsReadOnly?: true;
|
|
244
|
+
/** #345 ③ — the HOST task's hard-headless clamp (`interactiveTools: false`), same carriage rules
|
|
245
|
+
* as `parentHandsReadOnly` above: only the DISABLING value travels, mirroring the subagent lane's
|
|
246
|
+
* `ToolExecuteContext.interactiveTools` seat — without it a headless-clamped host's workflow
|
|
247
|
+
* children could re-mount the interactive tool the host run disabled. */
|
|
248
|
+
parentInteractiveTools?: false;
|
|
237
249
|
/** [1238](A) — call-time getter for the HOST task's RESOLVED Model object: a spawned agent whose
|
|
238
250
|
* fold chain produced no model inherits the parent's full object (baseUrl/key routing included),
|
|
239
251
|
* mirroring the subagent lane's ctx.model semantics. */
|
|
@@ -468,7 +468,7 @@ export async function createRunWorkflowTool(d) {
|
|
|
468
468
|
}
|
|
469
469
|
: governance;
|
|
470
470
|
const scriptFn = (wfCtx) => {
|
|
471
|
-
const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn, d.parentThinking, principal, ctx.checkpointStoreDisabledForChildren === true || d.parentCheckpointStoreDisabled === true, d.parentReadFace, d.parentReadDenyPatterns);
|
|
471
|
+
const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn, d.parentThinking, principal, ctx.checkpointStoreDisabledForChildren === true || d.parentCheckpointStoreDisabled === true, d.parentReadFace, d.parentReadDenyPatterns, ctx.handsReadOnly === true || d.parentHandsReadOnly === true, ctx.interactiveTools === false || d.parentInteractiveTools === false);
|
|
472
472
|
return d.scriptRunner.run({ scriptSource: script, primitives, scriptArgs: effectiveArgs, signal: wfCtx.signal }).then((r) => r.result);
|
|
473
473
|
};
|
|
474
474
|
if (ctx.signal?.aborted) {
|