@lmzhen/dsh-evolution-core 0.26.26 → 0.27.1

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.
Files changed (107) hide show
  1. package/README.md +16 -1
  2. package/lib/composition/index.js +3 -0
  3. package/lib/composition-D8Y3-ROd.js +604 -0
  4. package/lib/ensure-MEYMj9jn.js +222 -0
  5. package/lib/errors-Br2-uUSK.js +57 -0
  6. package/lib/http-routes-BP-S3KV-.js +149 -0
  7. package/lib/http-routes.js +2 -0
  8. package/lib/index.js +66 -850
  9. package/lib/restrictor.js +37 -0
  10. package/lib/subagent/index.js +186 -0
  11. package/lib/types/citations.js +192 -0
  12. package/lib/types/{preset-composition.d.ts → composition/composer.d.ts} +1 -1
  13. package/lib/types/composition/composer.js +681 -0
  14. package/lib/types/composition/ensure.d.ts +10 -0
  15. package/lib/types/composition/ensure.js +95 -0
  16. package/lib/types/composition/index.d.ts +5 -0
  17. package/lib/types/composition/index.js +7 -0
  18. package/lib/types/composition/preset-ids.d.ts +17 -0
  19. package/lib/types/composition/preset-ids.js +21 -0
  20. package/lib/types/composition/restricted-preset.d.ts +36 -0
  21. package/lib/types/composition/restricted-preset.js +42 -0
  22. package/lib/types/constants.js +223 -0
  23. package/lib/types/cost.js +67 -0
  24. package/lib/types/curator.js +346 -0
  25. package/lib/types/diagnostics.d.ts +26 -0
  26. package/lib/types/diagnostics.js +23 -0
  27. package/lib/types/document-body.js +91 -0
  28. package/lib/types/drift-signals.js +319 -0
  29. package/lib/types/env.js +25 -0
  30. package/lib/types/errors.d.ts +4 -1
  31. package/lib/types/errors.js +59 -0
  32. package/lib/types/events.js +16 -0
  33. package/lib/types/evidence.js +65 -0
  34. package/lib/types/evolution-events.js +479 -0
  35. package/lib/types/fold-untrusted.js +44 -0
  36. package/lib/types/frontmatter.js +623 -0
  37. package/lib/types/fuzzy-match.js +135 -0
  38. package/lib/types/gates.js +51 -0
  39. package/lib/types/guards.js +27 -0
  40. package/lib/types/health-baseline.js +63 -0
  41. package/lib/types/http-routes.d.ts +67 -35
  42. package/lib/types/http-routes.js +135 -0
  43. package/lib/types/index.d.ts +7 -2
  44. package/lib/types/index.js +101 -0
  45. package/lib/types/instance-scope.js +69 -0
  46. package/lib/types/io.js +675 -0
  47. package/lib/types/learn-prompt.js +42 -0
  48. package/lib/types/limits.js +63 -0
  49. package/lib/types/live.js +107 -0
  50. package/lib/types/long-runs.js +96 -0
  51. package/lib/types/memory-store.d.ts +0 -16
  52. package/lib/types/memory-store.js +606 -0
  53. package/lib/types/mutations.js +95 -0
  54. package/lib/types/namespace-migration.js +156 -0
  55. package/lib/types/numeric.js +47 -0
  56. package/lib/types/opt-in.js +101 -0
  57. package/lib/types/package-identity.js +47 -0
  58. package/lib/types/params.js +328 -0
  59. package/lib/types/payload.js +36 -0
  60. package/lib/types/platform-line.generated.d.ts +10 -0
  61. package/lib/types/platform-line.generated.js +12 -0
  62. package/lib/types/platform-services.d.ts +2 -2
  63. package/lib/types/platform-services.js +52 -0
  64. package/lib/types/probe.js +122 -0
  65. package/lib/types/prompts.js +409 -0
  66. package/lib/types/quality.js +308 -0
  67. package/lib/types/reachability.js +122 -0
  68. package/lib/types/redact.js +257 -0
  69. package/lib/types/reference-rewrite.js +144 -0
  70. package/lib/types/reports.js +110 -0
  71. package/lib/types/restrictor.d.ts +11 -0
  72. package/lib/types/restrictor.js +48 -0
  73. package/lib/types/review-channel.js +59 -0
  74. package/lib/types/run-registry.js +345 -0
  75. package/lib/types/scope.js +7 -0
  76. package/lib/types/serial.js +17 -0
  77. package/lib/types/session-fold.d.ts +15 -0
  78. package/lib/types/session-fold.js +28 -0
  79. package/lib/types/session-projection.js +295 -0
  80. package/lib/types/signals.js +209 -0
  81. package/lib/types/skill-discovery.js +66 -0
  82. package/lib/types/skill-health.js +134 -0
  83. package/lib/types/skill-history.js +745 -0
  84. package/lib/types/skill-lint.js +112 -0
  85. package/lib/types/skill-reads.js +67 -0
  86. package/lib/types/skill-row-cells.js +123 -0
  87. package/lib/types/skill-store.js +3528 -0
  88. package/lib/types/state-store.js +60 -0
  89. package/lib/types/store-ledger.js +117 -0
  90. package/lib/types/subagent/channel.d.ts +4 -0
  91. package/lib/types/subagent/channel.js +131 -0
  92. package/lib/types/subagent/index.d.ts +4 -0
  93. package/lib/types/subagent/index.js +5 -0
  94. package/lib/types/subagent/readiness.d.ts +6 -0
  95. package/lib/types/subagent/readiness.js +35 -0
  96. package/lib/types/subagent/types.d.ts +58 -0
  97. package/lib/types/subagent/types.js +2 -0
  98. package/lib/types/threats.js +338 -0
  99. package/lib/types/tool-dispatch.d.ts +5 -0
  100. package/lib/types/tool-dispatch.js +535 -0
  101. package/lib/types/tree-write.js +40 -0
  102. package/lib/types/unit-members.js +121 -0
  103. package/lib/types/usage.js +474 -0
  104. package/lib/types/write-authority.js +23 -0
  105. package/lib/types/write-gates.js +344 -0
  106. package/lib/types/write-inventory.js +158 -0
  107. package/package.json +39 -11
@@ -0,0 +1,345 @@
1
+ /**
2
+ * 0.19.0 (S2) / 0.19.3 (restructure): the family's run-lifecycle owner.
3
+ *
4
+ * A "run" is work that can outlive one interaction — today the maintenance scan.
5
+ * Before this module the only record of such work was the requesting invocation:
6
+ * `lastMaintainAt` / `lastMaintainRunId` / `maintainInFlightSince` were
7
+ * module-scope globals in evolution-commands, and the plan text existed ONLY in
8
+ * the command's return string. An interaction that ended (measured: the desktop
9
+ * shell hangs up at ~305 s) therefore erased every trace of a still-running scan.
10
+ *
11
+ * This registry is the single owner of (a) what is in flight, (b) how a run
12
+ * ended, and (c) WHERE its result lives (`resultRef` is a path, never the result
13
+ * itself — the report file is the one result home).
14
+ *
15
+ * Three disciplines are structural, not stylistic:
16
+ *
17
+ * - **One writer per row** (0.19.3). A run's row is authoritative in the memory of
18
+ * the process that STARTED it (`owned`) and in the shared index for everyone else
19
+ * (`foreign`, rebuilt from the file on every `load()`). The only write a non-owner
20
+ * may make is the orphan verdict for a row whose owner is gone, judged inside the
21
+ * very transaction that read the row. The two cross-plane bugs this family shipped
22
+ * were projections of that one missing rule: 0.19.0 froze a foreign run as
23
+ * `running` forever (a "known id" was never re-read), and 0.19.1 wrote a settled
24
+ * foreign row back as `running` (memory overwrote every row it knew).
25
+ * - **No timer.** A run whose process died is converged at READ time: the live rows
26
+ * are in memory, so a `running` row in the file whose owner is not alive is an
27
+ * orphan — the same "converge when read" posture the approval window uses.
28
+ * - **One terminal state, and it is monotone.** `settle()` refuses to overwrite a row
29
+ * that is already terminal (so a cancel that raced a failure cannot flip the answer
30
+ * the operator already read), and a row that reached a terminal state never reads as
31
+ * `running` again: only the owner that started it could make it running and
32
+ * `settle()` is terminal-once, so a `running` claim for a settled row can only be a
33
+ * stale writer's — refused on read, repaired on the next write.
34
+ *
35
+ * The store is PER INSTANCE (a factory, not module scope): one registry per
36
+ * mounted row, disposed with its fiber. There is no module-scope mutable state
37
+ * here to register under N12.
38
+ * @module
39
+ */
40
+ import { randomUUID } from 'node:crypto';
41
+ import { join } from 'node:path';
42
+ import { clampedNumber } from "./numeric.js";
43
+ import { isProcessAlive, transactIo } from "./io.js";
44
+ /** Current index schema. Bump only for a structural change. */
45
+ export const RUNS_SCHEMA_VERSION = 1;
46
+ /** Retained run records, newest first. */
47
+ export const DEFAULT_RUN_RECORDS = 50;
48
+ /** Index path for a home. */
49
+ export function runsFile(home) {
50
+ return join(home, 'runs.json');
51
+ }
52
+ const TERMINAL = new Set(['succeeded', 'failed', 'cancelled']);
53
+ /**
54
+ * Parse an index body into records.
55
+ *
56
+ * `ok: false` means the body could not be read as an index at all — the caller
57
+ * decides what to say (`load()` reports it; `persist()` merges with what it can).
58
+ * @param raw - the file's text, or null when it does not exist.
59
+ * @returns the parsed records and whether the body was an index.
60
+ */
61
+ function parseIndex(raw) {
62
+ if (raw === null)
63
+ return { ok: true, records: [], foreign: [] };
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(raw);
67
+ }
68
+ catch {
69
+ return { ok: false, records: [], foreign: [] };
70
+ }
71
+ const rows = parsed.runs;
72
+ if (!Array.isArray(rows))
73
+ return { ok: false, records: [], foreign: [] };
74
+ const notes = [];
75
+ const version = parsed.schemaVersion;
76
+ if (typeof version === 'number' && version !== RUNS_SCHEMA_VERSION) {
77
+ notes.push(`the index declares schemaVersion ${version} while this build writes ${RUNS_SCHEMA_VERSION}`);
78
+ }
79
+ const records = [];
80
+ const foreign = [];
81
+ for (const row of rows) {
82
+ const record = row;
83
+ if (typeof record.id !== 'string' || typeof record.startedAt !== 'number') {
84
+ foreign.push(row);
85
+ continue;
86
+ }
87
+ if (typeof record.kind !== 'string' || typeof record.state !== 'string') {
88
+ foreign.push(row);
89
+ continue;
90
+ }
91
+ if (record.state !== 'running' && !TERMINAL.has(record.state)) {
92
+ // A state this build does not know (a newer writer, or a hand-edited file):
93
+ // it is KEPT as it is — dropping it would silently erase another writer's record.
94
+ foreign.push(row);
95
+ continue;
96
+ }
97
+ records.push({
98
+ id: record.id,
99
+ kind: record.kind,
100
+ state: record.state,
101
+ startedAt: record.startedAt,
102
+ ...(typeof record.pid === 'number' ? { pid: record.pid } : {}),
103
+ ...(typeof record.endedAt === 'number' ? { endedAt: record.endedAt } : {}),
104
+ ...(typeof record.resultRef === 'string' ? { resultRef: record.resultRef } : {}),
105
+ ...(typeof record.failure === 'string' ? { failure: record.failure } : {}),
106
+ });
107
+ }
108
+ return { ok: true, records, foreign, ...(notes.length > 0 ? { note: notes.join('; ') } : {}) };
109
+ }
110
+ /**
111
+ * Create the run registry for one mounted row.
112
+ * @param options - io seam, evolution home, warn channel, clock and record cap.
113
+ * @returns the registry; call {@link RunRegistry.load} once before answering reads.
114
+ */
115
+ export function newRunRegistry(options) {
116
+ const now = options.now ?? (() => Date.now());
117
+ const maxRecords = clampedNumber(options.maxRecords ?? DEFAULT_RUN_RECORDS, DEFAULT_RUN_RECORDS, { min: 1 });
118
+ const warn = options.warn ?? (() => { });
119
+ const path = runsFile(options.home);
120
+ /** Rows THIS process started: its memory is the only truth for them. */
121
+ const owned = new Map();
122
+ /** Rows read from the shared index: the file is the only truth for them. */
123
+ const foreign = new Map();
124
+ /** Cancellation handles — "can this still be stopped", NOT ownership (`settle` drops one). */
125
+ const controllers = new Map();
126
+ /** The ONE wording of the orphan verdict. */
127
+ const ORPHAN_FAILURE = 'no live owner process — the run was interrupted before it settled (orphan)';
128
+ /** A row whose owner cannot be reached: `pid` absent (an older build) or not alive. */
129
+ const ownerIsGone = (row) => row.pid === undefined || !isProcessAlive(row.pid);
130
+ /** The verdict is materialized ONCE, so its `endedAt` (and the duration a reader sees) is stable. */
131
+ const orphanOf = (row) => ({ ...row, state: 'failed', endedAt: now(), failure: ORPHAN_FAILURE });
132
+ /** Every row this process knows, newest first. ALL reads go through here. */
133
+ const view = () => [...owned.values(), ...foreign.values()].sort((a, b) => b.startedAt - a.startedAt);
134
+ /** Retained history: every running row plus the newest terminal ones (one cap, both planes). */
135
+ const capped = (rows) => {
136
+ const live = rows.filter(row => row.state === 'running');
137
+ const done = rows
138
+ .filter(row => TERMINAL.has(row.state))
139
+ .sort((a, b) => (b.endedAt ?? b.startedAt) - (a.endedAt ?? a.startedAt))
140
+ .slice(0, maxRecords);
141
+ return [...live, ...done].sort((a, b) => b.startedAt - a.startedAt);
142
+ };
143
+ /**
144
+ * Update a surviving foreign row in place (identity stable), or add it.
145
+ *
146
+ * Monotone states (0.19.3): a row already known TERMINAL is never shown as `running`
147
+ * again. Only the owner that started a run could make it running, and `settle()` is
148
+ * terminal-once — so a `running` claim for a settled row can only come from a stale
149
+ * writer (an older plane holding an outdated view), and adopting it would propagate the
150
+ * resurrection to every other plane.
151
+ * @returns true when the row was refused as exactly that regression.
152
+ */
153
+ const adopt = (row) => {
154
+ const existing = foreign.get(row.id);
155
+ if (existing === undefined) {
156
+ foreign.set(row.id, row);
157
+ return false;
158
+ }
159
+ if (TERMINAL.has(existing.state) && !TERMINAL.has(row.state))
160
+ return true;
161
+ // Clear what the new row dropped, so nothing is inherited from the previous view.
162
+ delete existing.endedAt;
163
+ delete existing.resultRef;
164
+ delete existing.failure;
165
+ Object.assign(existing, row);
166
+ return false;
167
+ };
168
+ const persist = async () => {
169
+ try {
170
+ // Read-modify-write under the io backend's cross-process lock. The file is the
171
+ // BASE and only THIS process's rows are laid over it ("one writer per row"); the
172
+ // single write a non-owner may make is the orphan verdict for a row whose owner is
173
+ // gone. The row read inside this transaction IS the compare-and-swap: if its owner
174
+ // settled it in between, it no longer reads as `running` and is left alone.
175
+ await transactIo(options.io, path, (current) => {
176
+ const onDisk = parseIndex(current);
177
+ const merged = new Map();
178
+ for (const row of onDisk.records)
179
+ merged.set(row.id, row);
180
+ for (const [id, row] of owned)
181
+ merged.set(id, row);
182
+ // Repair a stale writer's regression: this process READ the owner's terminal
183
+ // verdict, so re-asserting it is not authoring state — it refuses to propagate a
184
+ // stale overwrite. Without this, a fresh plane would see the resurrected `running`
185
+ // row and hold its in-flight guard (the 0.19.0 symptom, one plane later).
186
+ for (const [id, row] of foreign) {
187
+ const onDiskRow = merged.get(id);
188
+ if (TERMINAL.has(row.state) && onDiskRow !== undefined && !TERMINAL.has(onDiskRow.state))
189
+ merged.set(id, row);
190
+ }
191
+ for (const [id, row] of merged) {
192
+ if (row.state === 'running' && !owned.has(id) && ownerIsGone(row))
193
+ merged.set(id, orphanOf(row));
194
+ }
195
+ const kept = capped([...merged.values()]);
196
+ const live = kept.filter(row => row.state === 'running');
197
+ const done = kept.filter(row => TERMINAL.has(row.state));
198
+ // Rows this build does not understand ride along untouched (P2-5): it cannot
199
+ // judge them, and dropping them would erase another writer's record.
200
+ return JSON.stringify({ schemaVersion: RUNS_SCHEMA_VERSION, runs: [...live, ...done, ...onDisk.foreign] }, null, 2);
201
+ });
202
+ }
203
+ catch (error) {
204
+ warn(`evolution-core: could not persist the run index (${error instanceof Error ? error.message : String(error)})`);
205
+ }
206
+ };
207
+ return {
208
+ async load() {
209
+ let raw;
210
+ try {
211
+ raw = await options.io.readText(path);
212
+ }
213
+ catch (error) {
214
+ // A read FAILURE is not "no runs": say so instead of presenting an empty
215
+ // registry as a clean history (the same three-state rule the probe files use).
216
+ return { ok: false, note: `run index unreadable: ${error instanceof Error ? error.message : String(error)}` };
217
+ }
218
+ const parsed = parseIndex(raw);
219
+ if (!parsed.ok) {
220
+ return { ok: false, note: '运行索引读不出来(不是 JSON,或没有 runs 数组)—— 上一次的运行历史未知(报告不受影响)' };
221
+ }
222
+ // The foreign view is REBUILT wholesale: a row this process does not own is whatever
223
+ // the index says it is, every time. (0.19.0 kept "known" ids frozen, which is how a
224
+ // settled foreign run answered `running` until the observing process restarted.)
225
+ // Surviving rows are updated in place so a reader that held one across the load
226
+ // keeps a live view; rows that vanished from the index are dropped.
227
+ const wasRunning = new Set([...foreign.values()].filter(row => row.state === 'running').map(row => row.id));
228
+ const seen = new Set();
229
+ let converged = 0;
230
+ let liveElsewhere = 0;
231
+ let followed = 0;
232
+ let regressed = 0;
233
+ for (const row of parsed.records) {
234
+ if (owned.has(row.id))
235
+ continue;
236
+ seen.add(row.id);
237
+ if (row.state !== 'running') {
238
+ if (wasRunning.has(row.id))
239
+ followed++;
240
+ if (adopt(row))
241
+ regressed++;
242
+ continue;
243
+ }
244
+ // A running row whose OWNER is alive belongs to another process sharing this home:
245
+ // genuinely in flight, never rewritten here (review P1-1). A row that cannot name
246
+ // its owner is treated as gone — the conservative side, and what a row written by
247
+ // an older build looks like.
248
+ if (ownerIsGone(row)) {
249
+ converged++;
250
+ adopt(orphanOf(row));
251
+ continue;
252
+ }
253
+ liveElsewhere++;
254
+ if (adopt(row))
255
+ regressed++;
256
+ }
257
+ for (const id of [...foreign.keys()]) {
258
+ if (!seen.has(id))
259
+ foreign.delete(id);
260
+ }
261
+ if (converged > 0 || regressed > 0)
262
+ await persist();
263
+ const notes = [
264
+ parsed.note,
265
+ converged > 0 ? `${converged} run(s) with no live owner were recorded as failed(orphan)` : undefined,
266
+ liveElsewhere > 0 ? `${liveElsewhere} run(s) are in flight in another process sharing this home` : undefined,
267
+ followed > 0 ? `${followed} run(s) settled by their owner were re-read from the index` : undefined,
268
+ regressed > 0 ? `${regressed} settled run(s) stayed settled although the index claimed they were running` : undefined,
269
+ ].filter((note) => note !== undefined);
270
+ return { ok: true, ...(notes.length > 0 ? { note: notes.join('; ') } : {}) };
271
+ },
272
+ async begin(kind) {
273
+ const id = randomUUID();
274
+ const startedAt = now();
275
+ const controller = new AbortController();
276
+ controllers.set(id, controller);
277
+ // The pid is what lets ANOTHER process tell this live run from an orphan.
278
+ owned.set(id, { id, kind, state: 'running', startedAt, pid: process.pid });
279
+ // Write-through (0.19.1, review P2): answering "started" promises a run another
280
+ // plane can read, so the row reaches the index before this resolves. `persist()`
281
+ // warns instead of throwing, so an unwritable home cannot withhold the handle.
282
+ await persist();
283
+ return { id, kind, signal: controller.signal, startedAt };
284
+ },
285
+ async settle(id, outcome) {
286
+ const row = owned.get(id);
287
+ // Terminal-once: a cancel that raced the run's own failure must not flip the
288
+ // answer the operator already read.
289
+ if (row === undefined || TERMINAL.has(row.state))
290
+ return;
291
+ row.state = outcome.state;
292
+ row.endedAt = now();
293
+ if (outcome.resultRef !== undefined)
294
+ row.resultRef = outcome.resultRef;
295
+ if (outcome.failure !== undefined)
296
+ row.failure = outcome.failure;
297
+ controllers.delete(id);
298
+ await persist();
299
+ },
300
+ // The registry tracks ONE kind today (`RunKind` = 'maintain'), so these reads take no
301
+ // kind filter — a comparison the type system already knows is true. Re-add the
302
+ // parameter (and the filter) together with the second `RunKind` member.
303
+ inFlight() {
304
+ return view().find(row => row.state === 'running');
305
+ },
306
+ lastSettledAt() {
307
+ return view().find(row => TERMINAL.has(row.state))?.endedAt;
308
+ },
309
+ runs() {
310
+ return capped(view());
311
+ },
312
+ find(id) {
313
+ return owned.get(id) ?? foreign.get(id);
314
+ },
315
+ cancel(id) {
316
+ // Only work THIS process started can be stopped. A running row loaded from the
317
+ // index belongs to another process sharing this home (review P1-1), and marking it
318
+ // cancelled here would fabricate a terminal state for work that is still running
319
+ // there — so an unowned id is refused, not "cancelled". (`controllers` is the
320
+ // right test here precisely because it is about stopping, not about authority.)
321
+ const controller = controllers.get(id);
322
+ if (controller === undefined)
323
+ return false;
324
+ const row = owned.get(id);
325
+ if (row === undefined || row.state !== 'running')
326
+ return false;
327
+ controller.abort();
328
+ controllers.delete(id);
329
+ row.state = 'cancelled';
330
+ row.endedAt = now();
331
+ row.failure = 'cancelled by operator';
332
+ void persist();
333
+ return true;
334
+ },
335
+ cancelAll() {
336
+ let count = 0;
337
+ for (const id of [...controllers.keys()]) {
338
+ if (this.cancel(id))
339
+ count++;
340
+ }
341
+ return count;
342
+ },
343
+ };
344
+ }
345
+ //# sourceMappingURL=run-registry.js.map
@@ -0,0 +1,7 @@
1
+ import { scopeOf } from '@deepseek-ai/dsh-scope';
2
+ export function callingScope(ctx, held) {
3
+ if (held !== undefined)
4
+ return held;
5
+ return scopeOf(ctx);
6
+ }
7
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A process-local serial task queue: each task starts only after the previous
3
+ * one settles (success or failure), so read-modify-write sequences that share
4
+ * one file never interleave inside this process. The durable cross-process
5
+ * serialization layer is the IO backend's transact lock; this chain is the
6
+ * second layer (0.3.17 S2.8, T-1: the shape was duplicated in state-json and
7
+ * memory-files — one factory now).
8
+ */
9
+ export function makeSerialQueue() {
10
+ let chain = Promise.resolve();
11
+ return (task) => {
12
+ const run = chain.then(task, task);
13
+ chain = run.then(() => undefined, () => undefined);
14
+ return run;
15
+ };
16
+ }
17
+ //# sourceMappingURL=serial.js.map
@@ -0,0 +1,15 @@
1
+ export type SessionEventClass = 'model' | 'plugin-record' | 'unknown';
2
+ /** Frames the model authored/saw (the only frames evidence, reads and usage accounting may consume). */
3
+ import { MODEL_SESSION_EVENT_TYPES } from './tool-dispatch.ts';
4
+ export { MODEL_SESSION_EVENT_TYPES as MODEL_SESSION_EVENTS };
5
+ /** Classify one session event WITHOUT interpreting it (pure). */
6
+ export declare function classifySessionEvent(event: {
7
+ readonly type?: unknown;
8
+ readonly ignorable?: unknown;
9
+ }): SessionEventClass;
10
+ /** True when the event must be skipped by evidence/usage readers (tolerant, never fatal). */
11
+ export declare function isIgnorableEvent(event: {
12
+ readonly type?: unknown;
13
+ readonly ignorable?: unknown;
14
+ }): boolean;
15
+ //# sourceMappingURL=session-fold.d.ts.map
@@ -0,0 +1,28 @@
1
+ // Session-event classification for EVERY family reader (G6-S1; C-03/C-05).
2
+ //
3
+ // Platform contract: native session events are the model-visible content frames; a plugin record is
4
+ // an `ignorable: true` event whose type is `plugin:<name>` (session/src/index.ts pluginRecordOf).
5
+ // Family readers must classify BEFORE they count or index: unknown frames are tolerated (never
6
+ // interpreted, never written), plugin records are tolerated read-only. The family never appends
7
+ // plugin records.
8
+ // The platform rule is `pluginRecordOf(event) = ignorable && type startsWith('plugin:')`
9
+ // (session/src/index.ts). Spelled structurally here: the preflight's machine-state resolution can
10
+ // point at an older session closure than the platform line this build targets, so importing the
11
+ // helper would make the contract depend on the bed rather than on the rule.
12
+ /** Frames the model authored/saw (the only frames evidence, reads and usage accounting may consume). */
13
+ import { MODEL_SESSION_EVENT_TYPES } from "./tool-dispatch.js";
14
+ export { MODEL_SESSION_EVENT_TYPES as MODEL_SESSION_EVENTS };
15
+ const MODEL_EVENT_TYPES = new Set(MODEL_SESSION_EVENT_TYPES);
16
+ /** Classify one session event WITHOUT interpreting it (pure). */
17
+ export function classifySessionEvent(event) {
18
+ const type = typeof event.type === 'string' ? event.type : '';
19
+ if (type.startsWith('plugin:')) {
20
+ return event.ignorable === true ? 'plugin-record' : 'unknown';
21
+ }
22
+ return MODEL_EVENT_TYPES.has(type) ? 'model' : 'unknown';
23
+ }
24
+ /** True when the event must be skipped by evidence/usage readers (tolerant, never fatal). */
25
+ export function isIgnorableEvent(event) {
26
+ return classifySessionEvent(event) !== 'model';
27
+ }
28
+ //# sourceMappingURL=session-fold.js.map