@lmzhen/dsh-evolution-skill-discovery 0.30.43 → 0.31.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.
@@ -0,0 +1,2104 @@
1
+ import { _ as skillKeyOf, d as SkillIndex, m as projectionDigestOf, p as policyDigestOf, v as threeFieldHashOf } from "./tool-CvUTwKvW.js";
2
+ import { createHash } from "node:crypto";
3
+ import { isRecord, parseFrontmatter } from "@lmzhen/dsh-evolution-core";
4
+ import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
5
+ import { escapeText } from "@deepseek-ai/dsh-skill";
6
+ //#region lib/types/domain-generation.js
7
+ /** @returns stable id derived from title, not the title itself. */
8
+ function mintDomainId(title) {
9
+ return "domain-" + createHash("sha256").update(title).digest("hex").slice(0, 12);
10
+ }
11
+ /** @returns direct for <=100 skills; prechunk-eligible above 100. */
12
+ function domainGenerationMode(skillCount) {
13
+ if (!Number.isSafeInteger(skillCount) || skillCount < 0) throw new RangeError("skillCount must be a non-negative safe integer");
14
+ return skillCount <= 100 ? "direct" : "prechunk-eligible";
15
+ }
16
+ function nonEmpty(value) {
17
+ return typeof value === "string" && value.trim().length > 0;
18
+ }
19
+ function nonEmptyList(value) {
20
+ return Array.isArray(value) && value.length > 0 && value.every(nonEmpty);
21
+ }
22
+ function forbiddenKeys(value) {
23
+ return [
24
+ "primaryParent",
25
+ "primaryParentId",
26
+ "parent",
27
+ "embedding",
28
+ "embeddingVector",
29
+ "taxonomyMethod"
30
+ ].filter((key) => key in value);
31
+ }
32
+ /**
33
+ * Validate an initial generation proposal and mint ids. Does not call an LLM
34
+ * and does not read skill bodies; embedding taxonomy is rejected.
35
+ * @param input - skills the proposal was generated from.
36
+ * @param proposal - LLM output.
37
+ * @returns normalized domains or stable errors.
38
+ */
39
+ function validateDomainGeneration(input, proposal) {
40
+ const errors = [];
41
+ if (proposal.domains.length < 8 || proposal.domains.length > 12) errors.push("domain count must be 8-12");
42
+ const known = new Set(input.skills.map((skill) => skill.name));
43
+ const ids = /* @__PURE__ */ new Set();
44
+ const titles = /* @__PURE__ */ new Set();
45
+ const domains = [];
46
+ for (const candidate of proposal.domains) {
47
+ if (candidate === null || typeof candidate !== "object") {
48
+ errors.push("domain is not an object");
49
+ continue;
50
+ }
51
+ const bad = forbiddenKeys(candidate);
52
+ if (bad.length > 0) errors.push("forbidden fields: " + bad.join(","));
53
+ if (!nonEmpty(candidate.title) || !nonEmpty(candidate.purpose)) {
54
+ errors.push("domain title/purpose is empty");
55
+ continue;
56
+ }
57
+ if (!nonEmptyList(candidate.inScope) || !nonEmptyList(candidate.outOfScope) || !nonEmptyList(candidate.triggers)) errors.push("domain scope/triggers are empty");
58
+ if (!Array.isArray(candidate.evidenceSkillKeys) || candidate.evidenceSkillKeys.length === 0) errors.push("domain evidence is empty");
59
+ if (candidate.evidenceSkillKeys?.some((key) => !known.has(key)) === true) errors.push("domain evidence references unknown skill");
60
+ const id = mintDomainId(candidate.title);
61
+ if (ids.has(id)) errors.push("duplicate minted domain id");
62
+ if (titles.has(candidate.title)) errors.push("duplicate domain title");
63
+ ids.add(id);
64
+ titles.add(candidate.title);
65
+ domains.push({
66
+ ...candidate,
67
+ id
68
+ });
69
+ }
70
+ if ("embedding" in proposal || "taxonomyMethod" in proposal) errors.push("embedding taxonomy is not allowed");
71
+ return errors.length === 0 ? {
72
+ ok: true,
73
+ domains
74
+ } : {
75
+ ok: false,
76
+ errors
77
+ };
78
+ }
79
+ //#endregion
80
+ //#region lib/types/domain-bootstrap.js
81
+ /**
82
+ * Write an initial LLM proposal using live skill facts. Code owns IDs and
83
+ * validation; no synthetic description/hash is written.
84
+ * @param store - map owner.
85
+ * @param proposal - 8–12 domain proposal.
86
+ * @param facts - real rows with stable keys and three-field hashes.
87
+ * @param expectedMapVersion - CAS precondition.
88
+ * @param provenance - prompt/run identity for audit.
89
+ * @returns committed snapshot.
90
+ */
91
+ async function bootstrapDomainMapWithFacts(store, proposal, facts, expectedMapVersion, provenance) {
92
+ const checked = validateDomainGeneration({ skills: facts.map(({ name, description, whenToUse }) => ({
93
+ name,
94
+ description,
95
+ ...whenToUse === void 0 ? {} : { whenToUse }
96
+ })) }, proposal);
97
+ if (!checked.ok) throw new Error("invalid domain proposal: " + checked.errors.join("; "));
98
+ const byName = new Map(facts.map((fact) => [fact.name, fact]));
99
+ const now = Date.now();
100
+ const qualityOf = (domain) => {
101
+ const triggerCount = domain.triggers.filter((value) => value.trim() !== "").length;
102
+ const scopeCount = domain.inScope.filter((value) => value.trim() !== "").length;
103
+ const duplicate = checked.domains.some((other) => other.id !== domain.id && other.purpose.trim().toLowerCase() === domain.purpose.trim().toLowerCase());
104
+ const fields = [
105
+ domain.purpose.trim() !== "",
106
+ triggerCount > 0,
107
+ scopeCount > 0,
108
+ domain.outOfScope.some((value) => value.trim() !== ""),
109
+ !duplicate
110
+ ];
111
+ return {
112
+ score: fields.filter(Boolean).length / fields.length,
113
+ triggerCount,
114
+ scopeCount
115
+ };
116
+ };
117
+ const domains = checked.domains.map((domain) => ({
118
+ id: domain.id,
119
+ name: domain.title,
120
+ purpose: domain.purpose,
121
+ description: domain.purpose,
122
+ inScope: domain.inScope,
123
+ outOfScope: domain.outOfScope,
124
+ triggers: domain.triggers,
125
+ parentIds: [],
126
+ status: "provisional",
127
+ version: 1,
128
+ createdAt: now,
129
+ updatedAt: now,
130
+ descriptionHash: "bootstrap",
131
+ descriptionVersion: 1,
132
+ descriptionStatus: "fresh",
133
+ provenance: {
134
+ source: "llm",
135
+ promptVersion: provenance.promptVersion,
136
+ sourceDigest: provenance.sourceDigest,
137
+ model: provenance.model,
138
+ runId: provenance.runId
139
+ },
140
+ quality: qualityOf(domain)
141
+ }));
142
+ const primary = /* @__PURE__ */ new Set();
143
+ const edges = [];
144
+ for (const [domainIndex, domain] of checked.domains.entries()) for (const name of domain.evidenceSkillKeys) {
145
+ const fact = byName.get(name);
146
+ if (fact === void 0) continue;
147
+ const role = primary.has(fact.skillKey) ? "secondary" : "primary";
148
+ if (role === "primary") primary.add(fact.skillKey);
149
+ edges.push({
150
+ skillKey: fact.skillKey,
151
+ domainId: checked.domains[domainIndex].id,
152
+ role,
153
+ confidence: 1,
154
+ source: "create",
155
+ skillHash: fact.threeFieldHash,
156
+ mapVersion: expectedMapVersion,
157
+ status: "active",
158
+ assignedAt: now
159
+ });
160
+ }
161
+ return store.commit(expectedMapVersion, {
162
+ mapVersion: String(Number(expectedMapVersion) + 1),
163
+ domains,
164
+ edges
165
+ });
166
+ }
167
+ /**
168
+ * Legacy fallback for callers that only have a name -> key mapping.
169
+ * Production must use bootstrapDomainMapWithFacts.
170
+ */
171
+ async function bootstrapDomainMap(store, proposal, skillKeys, expectedMapVersion) {
172
+ return bootstrapDomainMapWithFacts(store, proposal, Object.entries(skillKeys).map(([name, skillKey]) => ({
173
+ name,
174
+ description: name,
175
+ skillKey,
176
+ threeFieldHash: ""
177
+ })), expectedMapVersion, {
178
+ promptVersion: "legacy",
179
+ sourceDigest: "legacy",
180
+ model: "legacy",
181
+ runId: "legacy"
182
+ });
183
+ }
184
+ //#endregion
185
+ //#region lib/types/domain-generation-coordinator.js
186
+ /** Single-flight initial map generation; mapVersion CAS is the final arbiter. */
187
+ var DomainGenerationCoordinator = class {
188
+ store;
189
+ port;
190
+ inflight = /* @__PURE__ */ new Map();
191
+ epoch = 0;
192
+ constructor(store, port) {
193
+ this.store = store;
194
+ this.port = port;
195
+ }
196
+ /** @returns current map version; increment makes older requests stale. */
197
+ get currentEpoch() {
198
+ return this.epoch;
199
+ }
200
+ /**
201
+ * Generate an initial map once per scope; concurrent calls share one job.
202
+ * @param request - live skills, stable keys, prompt, and CAS precondition.
203
+ * @returns committed or existing snapshot.
204
+ */
205
+ async ensure(request) {
206
+ const current = await this.store.read();
207
+ if (current.domains.length > 0) return current;
208
+ const existing = this.inflight.get(request.mapScope);
209
+ if (existing !== void 0) return existing;
210
+ this.epoch += 1;
211
+ const epoch = this.epoch;
212
+ const job = this.run(request, epoch).finally(() => {
213
+ this.inflight.delete(request.mapScope);
214
+ });
215
+ this.inflight.set(request.mapScope, job);
216
+ return job;
217
+ }
218
+ async run(request, epoch) {
219
+ const current = await this.store.read();
220
+ if (current.domains.length > 0) return current;
221
+ const proposal = await this.port.generate({
222
+ skills: request.facts.map(({ name, description, whenToUse }) => ({
223
+ name,
224
+ description,
225
+ ...whenToUse === void 0 ? {} : { whenToUse }
226
+ })),
227
+ prompt: request.prompt,
228
+ ...request.signal === void 0 ? {} : { signal: request.signal }
229
+ });
230
+ if (epoch !== this.epoch) {
231
+ const latest = await this.store.read();
232
+ if (latest.domains.length > 0) return latest;
233
+ throw new Error("domain generation superseded by a newer source snapshot");
234
+ }
235
+ return bootstrapDomainMapWithFacts(this.store, proposal, request.facts, request.expectedMapVersion, {
236
+ promptVersion: request.promptVersion,
237
+ sourceDigest: request.sourceDigest,
238
+ model: request.model,
239
+ runId: request.runId
240
+ });
241
+ }
242
+ };
243
+ //#endregion
244
+ //#region lib/types/domain-policy.js
245
+ /** Invalid policy layer (e.g. attempts to edit taxonomy). */
246
+ var DomainPolicyValidationError = class extends Error {
247
+ code = "domain-policy-invalid";
248
+ constructor(message) {
249
+ super(message);
250
+ this.name = "DomainPolicyValidationError";
251
+ }
252
+ };
253
+ const ALLOWED = new Set([
254
+ "mode",
255
+ "pinDomains",
256
+ "disableDomains",
257
+ "revision"
258
+ ]);
259
+ const MODES = new Set([
260
+ "off",
261
+ "auto",
262
+ "on"
263
+ ]);
264
+ function checkLayer(layer) {
265
+ for (const key of Object.keys(layer)) if (!ALLOWED.has(key)) throw new DomainPolicyValidationError("unsupported policy field " + key);
266
+ if (layer.mode !== void 0 && !MODES.has(layer.mode)) throw new DomainPolicyValidationError("unsupported policy mode");
267
+ }
268
+ function knownOnly(values, known, degraded) {
269
+ const result = /* @__PURE__ */ new Set();
270
+ for (const value of values ?? []) {
271
+ if (known !== void 0 && !known.has(value)) {
272
+ degraded.add(value);
273
+ continue;
274
+ }
275
+ result.add(value);
276
+ }
277
+ return result;
278
+ }
279
+ /**
280
+ * Merge global -> workspace -> scope policy. Disable wins over pin; unknown
281
+ * domains are ignored and reported as degraded.
282
+ * @param global - mandatory global layer.
283
+ * @param workspace - optional workspace overlay.
284
+ * @param scope - optional scope overlay.
285
+ * @param knownDomainIds - optional known ids for validation.
286
+ * @returns immutable effective policy.
287
+ */
288
+ function mergeDomainPolicy(global, workspace, scope, knownDomainIds) {
289
+ checkLayer(global);
290
+ if (workspace) checkLayer(workspace);
291
+ if (scope) checkLayer(scope);
292
+ const degraded = /* @__PURE__ */ new Set();
293
+ const blocked = new Set([
294
+ ...knownOnly(global.disableDomains, knownDomainIds, degraded),
295
+ ...knownOnly(workspace?.disableDomains, knownDomainIds, degraded),
296
+ ...knownOnly(scope?.disableDomains, knownDomainIds, degraded)
297
+ ]);
298
+ const pinned = new Set([
299
+ ...knownOnly(global.pinDomains, knownDomainIds, degraded),
300
+ ...knownOnly(workspace?.pinDomains, knownDomainIds, degraded),
301
+ ...knownOnly(scope?.pinDomains, knownDomainIds, degraded)
302
+ ]);
303
+ for (const id of blocked) pinned.delete(id);
304
+ return {
305
+ mode: scope?.mode ?? workspace?.mode ?? global.mode ?? "off",
306
+ pinned,
307
+ disabled: blocked,
308
+ degraded: [...degraded].sort(),
309
+ globalRevision: global.revision,
310
+ workspaceRevision: workspace?.revision,
311
+ scopeRevision: scope?.revision
312
+ };
313
+ }
314
+ //#endregion
315
+ //#region lib/types/domain-policy-store.js
316
+ /** Resolves global/workspace/scope policy without touching DomainMap or mapVersion. */
317
+ var DomainPolicyStore = class {
318
+ input;
319
+ constructor(input) {
320
+ this.input = input;
321
+ }
322
+ /**
323
+ * @param knownDomainIds - optional live id set; overrides the construction-time set.
324
+ * @returns effective policy with unknown ids reported as degraded.
325
+ */
326
+ resolve(knownDomainIds = this.input.knownDomainIds) {
327
+ return mergeDomainPolicy(this.input.global, this.input.workspace, this.input.scope, knownDomainIds);
328
+ }
329
+ /**
330
+ * @param knownDomainIds - optional live id set used for the effective policy.
331
+ * @returns stable digest of effective policy and all layer revisions.
332
+ */
333
+ digest(knownDomainIds = this.input.knownDomainIds) {
334
+ const policy = this.resolve(knownDomainIds);
335
+ return policyDigestOf({
336
+ globalPolicyRevision: policy.globalRevision ?? "0",
337
+ ...policy.workspaceRevision === void 0 ? {} : { workspaceOverlayRevision: policy.workspaceRevision },
338
+ ...policy.scopeRevision === void 0 ? {} : { scopeOverlayRevision: policy.scopeRevision },
339
+ effectivePolicy: policy
340
+ });
341
+ }
342
+ };
343
+ //#endregion
344
+ //#region lib/types/domain-map-store.js
345
+ /** File schema version supported by this store. */
346
+ const DOMAIN_MAP_SCHEMA_VERSION = 1;
347
+ /** Typed failure for an invalid persisted file. */
348
+ var DomainMapCorruptError = class extends Error {
349
+ code = "domain-map-corrupt";
350
+ constructor(message) {
351
+ super(message);
352
+ this.name = "DomainMapCorruptError";
353
+ }
354
+ };
355
+ /** Typed failure for a CAS mismatch. */
356
+ var DomainMapVersionConflict = class extends Error {
357
+ code = "domain-map-version-conflict";
358
+ expected;
359
+ actual;
360
+ constructor(expected, actual) {
361
+ super("domain map version conflict: expected " + expected + ", found " + actual);
362
+ this.expected = expected;
363
+ this.actual = actual;
364
+ this.name = "DomainMapVersionConflict";
365
+ }
366
+ };
367
+ /** Typed failure for an invalid write. */
368
+ var DomainMapValidationError = class extends Error {
369
+ code = "domain-map-invalid";
370
+ constructor(message) {
371
+ super(message);
372
+ this.name = "DomainMapValidationError";
373
+ }
374
+ };
375
+ function emptySnapshot() {
376
+ return {
377
+ mapVersion: "0",
378
+ domains: [],
379
+ edges: []
380
+ };
381
+ }
382
+ function assertNode(node) {
383
+ if (!isRecord(node) || typeof node.id !== "string" || node.id.length === 0) throw new DomainMapCorruptError("domain node id is missing");
384
+ if (typeof node.name !== "string" || typeof node.purpose !== "string" || typeof node.description !== "string") throw new DomainMapCorruptError("domain node text fields are missing");
385
+ if (!Array.isArray(node.inScope) || !Array.isArray(node.outOfScope) || !Array.isArray(node.triggers) || !Array.isArray(node.parentIds)) throw new DomainMapCorruptError("domain node arrays are invalid");
386
+ if (node.status !== "active" && node.status !== "provisional" && node.status !== "deprecated") throw new DomainMapCorruptError("domain node status is invalid");
387
+ }
388
+ function assertEdge(edge) {
389
+ if (!isRecord(edge) || typeof edge.skillKey !== "string" || typeof edge.domainId !== "string") throw new DomainMapCorruptError("skill edge identity is missing");
390
+ if (edge.role !== "primary" && edge.role !== "secondary") throw new DomainMapCorruptError("skill edge role is invalid");
391
+ if (edge.status !== "active" && edge.status !== "stale" && edge.status !== "pending") throw new DomainMapCorruptError("skill edge status is invalid");
392
+ if (typeof edge.confidence !== "number" || edge.confidence < 0 || edge.confidence > 1) throw new DomainMapCorruptError("skill edge confidence is invalid");
393
+ }
394
+ function validateWrite(next) {
395
+ if (next.mapVersion.length === 0) throw new DomainMapValidationError("mapVersion must be non-empty");
396
+ const ids = /* @__PURE__ */ new Set();
397
+ for (const node of next.domains) {
398
+ assertNode(node);
399
+ if (ids.has(node.id)) throw new DomainMapValidationError("duplicate domain id " + node.id);
400
+ ids.add(node.id);
401
+ }
402
+ for (const node of next.domains) for (const parent of node.parentIds) {
403
+ if (parent === node.id) throw new DomainMapValidationError("domain self-parent " + node.id);
404
+ if (!ids.has(parent)) throw new DomainMapValidationError("unknown parent domain " + parent);
405
+ }
406
+ const edgeKeys = /* @__PURE__ */ new Set();
407
+ const primaries = /* @__PURE__ */ new Map();
408
+ for (const edge of next.edges) {
409
+ assertEdge(edge);
410
+ if (!ids.has(edge.domainId)) throw new DomainMapValidationError("unknown edge domain " + edge.domainId);
411
+ const key = edge.skillKey + "\0" + edge.domainId;
412
+ if (edgeKeys.has(key)) throw new DomainMapValidationError("duplicate skill/domain edge");
413
+ edgeKeys.add(key);
414
+ if (edge.role === "primary") {
415
+ const count = (primaries.get(edge.skillKey) ?? 0) + 1;
416
+ if (count > 1) throw new DomainMapValidationError("more than one primary domain for " + edge.skillKey);
417
+ primaries.set(edge.skillKey, count);
418
+ }
419
+ }
420
+ }
421
+ /**
422
+ * JSON-backed DomainMap owner. One file, schemaVersion, CAS mapVersion, and a
423
+ * best-effort .bak for rollback; no evolution-state tables or graph DB.
424
+ */
425
+ var DomainMapFileStore = class {
426
+ queue = Promise.resolve();
427
+ io;
428
+ path;
429
+ now;
430
+ constructor(io, path, now = Date.now) {
431
+ this.io = io;
432
+ this.path = path;
433
+ this.now = now;
434
+ }
435
+ get backupPath() {
436
+ return this.path + ".bak";
437
+ }
438
+ parse(content) {
439
+ if (content === null) return emptySnapshot();
440
+ let value;
441
+ try {
442
+ value = JSON.parse(content);
443
+ } catch {
444
+ throw new DomainMapCorruptError("domain map is not valid JSON");
445
+ }
446
+ if (!isRecord(value) || value.schemaVersion !== 1 || typeof value.mapVersion !== "string" || !Array.isArray(value.domains) || !Array.isArray(value.edges)) throw new DomainMapCorruptError("domain map schema is unsupported");
447
+ for (const node of value.domains) assertNode(node);
448
+ for (const edge of value.edges) assertEdge(edge);
449
+ return {
450
+ mapVersion: value.mapVersion,
451
+ domains: value.domains,
452
+ edges: value.edges
453
+ };
454
+ }
455
+ async read() {
456
+ return this.parse(await this.io.readText(this.path));
457
+ }
458
+ async commitNow(current, expected, next) {
459
+ const snapshot = this.parse(current);
460
+ if (snapshot.mapVersion !== expected) throw new DomainMapVersionConflict(expected, snapshot.mapVersion);
461
+ if (next.mapVersion === expected) throw new DomainMapValidationError("mapVersion must change on commit");
462
+ validateWrite(next);
463
+ if (current !== null) await this.io.writeText(this.backupPath, current);
464
+ const file = {
465
+ schemaVersion: 1,
466
+ mapVersion: next.mapVersion,
467
+ updatedAt: this.now(),
468
+ domains: next.domains,
469
+ edges: next.edges
470
+ };
471
+ return JSON.stringify(file);
472
+ }
473
+ async commit(expectedMapVersion, next) {
474
+ const run = async () => {
475
+ if (this.io.transact !== void 0) await this.io.transact(this.path, (current) => this.commitNow(current, expectedMapVersion, next));
476
+ else {
477
+ const current = await this.io.readText(this.path);
478
+ await this.io.writeText(this.path, await this.commitNow(current, expectedMapVersion, next));
479
+ }
480
+ };
481
+ const pending = this.queue.then(run, run);
482
+ this.queue = pending.catch(() => {});
483
+ await pending;
484
+ return this.read();
485
+ }
486
+ async rollback() {
487
+ const backup = await this.io.readText(this.backupPath);
488
+ if (backup === null) throw new DomainMapCorruptError("domain map backup is missing");
489
+ const snapshot = this.parse(backup);
490
+ await this.io.writeText(this.path, backup);
491
+ return snapshot;
492
+ }
493
+ };
494
+ //#endregion
495
+ //#region lib/types/projection-journal.js
496
+ function keyOf(record) {
497
+ return record.sessionId + "|" + record.viewKey + "|" + record.projectionDigest + "|" + record.codecVersion;
498
+ }
499
+ /** Durable projection journal; no base means restore must fail closed. */
500
+ var ProjectionJournal = class {
501
+ io;
502
+ path;
503
+ maxRecords;
504
+ constructor(io, path, options = {}) {
505
+ this.io = io;
506
+ this.path = path;
507
+ this.maxRecords = options.maxRecords ?? 512;
508
+ }
509
+ async read() {
510
+ const content = await this.io.readText(this.path);
511
+ if (content === null) return {
512
+ schemaVersion: 1,
513
+ records: {}
514
+ };
515
+ try {
516
+ const parsed = JSON.parse(content);
517
+ return parsed.schemaVersion === 1 && typeof parsed.records === "object" && parsed.records !== null ? {
518
+ schemaVersion: 1,
519
+ records: parsed.records
520
+ } : {
521
+ schemaVersion: 1,
522
+ records: {}
523
+ };
524
+ } catch {
525
+ return {
526
+ schemaVersion: 1,
527
+ records: {}
528
+ };
529
+ }
530
+ }
531
+ /** Persist one projection record; later records with the same key win. */
532
+ async put(record) {
533
+ const records = {
534
+ ...(await this.read()).records,
535
+ [keyOf(record)]: record
536
+ };
537
+ const ordered = Object.entries(records).sort((left, right) => right[1].createdAt - left[1].createdAt).slice(0, this.maxRecords);
538
+ await this.io.writeText(this.path, JSON.stringify({
539
+ schemaVersion: 1,
540
+ records: Object.fromEntries(ordered)
541
+ }));
542
+ }
543
+ /** @returns the exact projection record for one view/digest/codec, if present. */
544
+ async get(sessionId, viewKey, projectionDigest, codecVersion) {
545
+ return (await this.read()).records[keyOf({
546
+ sessionId,
547
+ viewKey,
548
+ projectionDigest,
549
+ codecVersion
550
+ })];
551
+ }
552
+ /** @returns newest record for a view, used when leaving ready without a known digest. */
553
+ async latest(sessionId, viewKey, codecVersion) {
554
+ return Object.values((await this.read()).records).filter((record) => record.sessionId === sessionId && record.viewKey === viewKey && record.codecVersion === codecVersion).sort((left, right) => right.createdAt - left.createdAt)[0];
555
+ }
556
+ /** Delete one record after a successful restore. */
557
+ async remove(sessionId, viewKey, projectionDigest, codecVersion) {
558
+ const records = { ...(await this.read()).records };
559
+ delete records[keyOf({
560
+ sessionId,
561
+ viewKey,
562
+ projectionDigest,
563
+ codecVersion
564
+ })];
565
+ await this.io.writeText(this.path, JSON.stringify({
566
+ schemaVersion: 1,
567
+ records
568
+ }));
569
+ }
570
+ };
571
+ //#endregion
572
+ //#region lib/types/domain-quality.js
573
+ function tokens(values) {
574
+ return new Set(values.join(" ").toLowerCase().split(/[^\p{Letter}\p{Number}]+/u).filter((token) => token.length > 0));
575
+ }
576
+ function jaccard(left, right) {
577
+ if (left.size === 0 && right.size === 0) return 1;
578
+ let overlap = 0;
579
+ for (const token of left) if (right.has(token)) overlap += 1;
580
+ return overlap / (left.size + right.size - overlap);
581
+ }
582
+ /**
583
+ * Evaluate whether a domain definition is specific, triggered, and distinct.
584
+ * @param domain - candidate domain.
585
+ * @param peers - other visible domains.
586
+ * @returns auditable issues; does not mutate the candidate.
587
+ */
588
+ function evaluateDomainQuality(domain, peers) {
589
+ const issues = [];
590
+ if (domain.purpose.trim().length === 0) issues.push("empty-purpose");
591
+ else if (domain.purpose.trim().length < 8) issues.push("vague-purpose");
592
+ if (domain.inScope.length === 0) issues.push("empty-in-scope");
593
+ if (domain.outOfScope.length === 0) issues.push("empty-out-of-scope");
594
+ if (domain.triggers.length === 0) issues.push("empty-triggers");
595
+ const own = tokens([...domain.inScope, ...domain.triggers]);
596
+ for (const peer of peers) {
597
+ if (domain.name.trim().toLowerCase() === peer.name.trim().toLowerCase() || domain.purpose.trim().toLowerCase() === peer.purpose.trim().toLowerCase()) {
598
+ issues.push("indistinguishable");
599
+ break;
600
+ }
601
+ if (jaccard(own, tokens([...peer.inScope, ...peer.triggers])) > .8) {
602
+ issues.push("overlaps-peer");
603
+ break;
604
+ }
605
+ }
606
+ const unique = [...new Set(issues)];
607
+ return {
608
+ passed: unique.length === 0,
609
+ issues: unique,
610
+ score: Math.max(0, 1 - unique.length * .2)
611
+ };
612
+ }
613
+ //#endregion
614
+ //#region lib/types/security.js
615
+ /**
616
+ * Treat description text as data: strip control characters, escape HTML
617
+ * delimiters, and cap length. It never turns data into instructions.
618
+ * @param value - untrusted domain/skill text.
619
+ * @param limit - maximum characters retained.
620
+ * @returns escaped text plus truncation flag.
621
+ */
622
+ function sanitizeUntrustedText(value, limit = 2e3) {
623
+ const escaped = value.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
624
+ return escaped.length <= limit ? {
625
+ text: escaped,
626
+ truncated: false
627
+ } : {
628
+ text: escaped.slice(0, limit),
629
+ truncated: true
630
+ };
631
+ }
632
+ //#endregion
633
+ //#region lib/types/projection.js
634
+ function escapeData(value) {
635
+ return sanitizeUntrustedText(value, 2e3).text;
636
+ }
637
+ function renderCards(cards) {
638
+ if (cards.length === 0) return "";
639
+ return [
640
+ "<system-reminder>",
641
+ "Domain cards summarize where skills cluster; they support awareness, not retrieval. skill_search matches task text to skill summaries, skill(name) loads an exact skill, and the full catalog remains the fallback.",
642
+ "<skill_domains>",
643
+ ...cards.map((card) => [
644
+ "- ",
645
+ escapeData(card.id),
646
+ " ",
647
+ escapeData(card.title),
648
+ ": ",
649
+ escapeData(card.purpose),
650
+ " triggers=",
651
+ card.triggers.map(escapeData).join(","),
652
+ " | managed=",
653
+ String(card.managedModelInvocableCount),
654
+ " external=",
655
+ String(card.externalCatalogCount),
656
+ " pending=",
657
+ String(card.pendingCount),
658
+ " unclassified=",
659
+ String(card.unclassifiedCount ?? 0),
660
+ " state=",
661
+ card.state,
662
+ " descriptionStatus=",
663
+ card.descriptionStatus,
664
+ " asOf=",
665
+ escapeData(card.asOf),
666
+ " mapVersion=",
667
+ escapeData(card.mapVersion),
668
+ " mapStale=",
669
+ String(card.mapStale)
670
+ ].join("")),
671
+ "</skill_domains>",
672
+ "</system-reminder>"
673
+ ].join("\n");
674
+ }
675
+ /**
676
+ * Pure catalog projection: hide only active managed entries, preserve every
677
+ * other entry, and render L1 cards. Incomplete input fails open.
678
+ * @param input - view rows, active managed keys, cards, and identity inputs.
679
+ * @returns projected entries/context/hidden names and digest.
680
+ */
681
+ function projectCatalog(input) {
682
+ if (!input.complete) return {
683
+ catalogEntries: input.entries,
684
+ context: "",
685
+ hiddenNames: [],
686
+ scope: input.scope,
687
+ digest: projectionDigestOf({
688
+ viewKey: input.viewKey,
689
+ mapVersion: input.mapVersion,
690
+ policyDigest: input.policyDigest,
691
+ viewCatalogDigest: input.viewCatalogDigest,
692
+ adapterVersion: input.adapterVersion,
693
+ projectionSchemaVersion: input.projectionSchemaVersion,
694
+ discoveryMode: input.discoveryMode
695
+ }),
696
+ complete: false
697
+ };
698
+ const activeByName = /* @__PURE__ */ new Map();
699
+ for (const row of input.rows) if (row.managed && row.modelInvocable && !row.protected && input.activeManagedSkillKeys.has(row.skillKey)) activeByName.set(row.name, row);
700
+ const hiddenNames = input.entries.filter((entry) => activeByName.has(entry.name)).map((entry) => entry.name).sort();
701
+ const hidden = new Set(hiddenNames);
702
+ return {
703
+ catalogEntries: input.entries.filter((entry) => !hidden.has(entry.name)),
704
+ context: renderCards(input.cards),
705
+ hiddenNames,
706
+ scope: input.scope,
707
+ digest: projectionDigestOf({
708
+ viewKey: input.viewKey,
709
+ mapVersion: input.mapVersion,
710
+ policyDigest: input.policyDigest,
711
+ viewCatalogDigest: input.viewCatalogDigest,
712
+ adapterVersion: input.adapterVersion,
713
+ projectionSchemaVersion: input.projectionSchemaVersion,
714
+ discoveryMode: input.discoveryMode
715
+ }),
716
+ complete: true
717
+ };
718
+ }
719
+ //#endregion
720
+ //#region lib/types/domain-projection.js
721
+ function qualityPasses(node, domains) {
722
+ return evaluateDomainQuality(node, domains.filter((peer) => peer.id !== node.id)).passed;
723
+ }
724
+ /**
725
+ * Build L1 cards and the hidden set for one view, then delegate the pure
726
+ * catalog rewrite. Structural staleness fails open; description staleness does not.
727
+ * @param input - rows, domain facts, policy, and identity inputs.
728
+ * @returns projection result with cards and hidden names.
729
+ */
730
+ function hiddenRecoverable(hiddenNames, searchResultNames) {
731
+ return hiddenNames.every((name) => searchResultNames.has(name));
732
+ }
733
+ function buildDomainProjection(input) {
734
+ const mode = input.mode ?? "on";
735
+ if (mode === "off") return projectCatalog({
736
+ entries: input.entries,
737
+ rows: input.rows,
738
+ activeManagedSkillKeys: /* @__PURE__ */ new Set(),
739
+ cards: [],
740
+ viewKey: input.viewKey,
741
+ scope: input.scope,
742
+ mapVersion: input.mapVersion,
743
+ policyDigest: input.policyDigest,
744
+ viewCatalogDigest: input.viewCatalogDigest,
745
+ adapterVersion: input.adapterVersion,
746
+ projectionSchemaVersion: input.projectionSchemaVersion,
747
+ discoveryMode: input.discoveryMode,
748
+ complete: false
749
+ });
750
+ const staleByAge = input.staleAfterHours !== void 0 && input.snapshot.domains.some((node) => input.now !== void 0 && input.now - node.updatedAt > input.staleAfterHours * 36e5);
751
+ const mapStale = input.mapStale === true || staleByAge;
752
+ if (!input.complete) return projectCatalog({
753
+ entries: input.entries,
754
+ rows: input.rows,
755
+ activeManagedSkillKeys: /* @__PURE__ */ new Set(),
756
+ cards: [],
757
+ viewKey: input.viewKey,
758
+ scope: input.scope,
759
+ mapVersion: input.mapVersion,
760
+ policyDigest: input.policyDigest,
761
+ viewCatalogDigest: input.viewCatalogDigest,
762
+ adapterVersion: input.adapterVersion,
763
+ projectionSchemaVersion: input.projectionSchemaVersion,
764
+ discoveryMode: input.discoveryMode,
765
+ complete: false
766
+ });
767
+ const rowByKey = new Map(input.rows.map((row) => [row.skillKey, row]));
768
+ const staleEdgeDomains = new Set(input.snapshot.edges.filter((edge) => edge.status === "active" && edge.role === "primary" && rowByKey.get(edge.skillKey) !== void 0 && edge.skillHash !== rowByKey.get(edge.skillKey).threeFieldHash).map((edge) => edge.domainId));
769
+ const linkedKeys = new Set(input.snapshot.edges.filter((edge) => edge.status === "active" || edge.status === "pending").map((edge) => edge.skillKey));
770
+ const unclassifiedCount = input.rows.filter((row) => row.modelInvocable && !linkedKeys.has(row.skillKey)).length;
771
+ const domains = input.snapshot.domains.filter((node) => node.status === "active" || node.status === "provisional");
772
+ const cardDomains = domains.filter((node) => node.parentIds.length <= (input.maxVisibleDepth ?? 3) - 1 && node.parentIds.length === 0).filter((node) => !input.policy.disabled.has(node.id)).filter((node) => input.policy.pinned.has(node.id) || qualityPasses(node, domains)).sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0).slice(0, Math.max(1, input.maxCards ?? 12));
773
+ const domainById = new Map(domains.map((node) => [node.id, node]));
774
+ const cards = cardDomains.map((node) => {
775
+ let managedModelInvocableCount = 0;
776
+ let externalCatalogCount = 0;
777
+ let pendingCount = 0;
778
+ for (const edge of input.snapshot.edges) {
779
+ if (edge.domainId !== node.id) continue;
780
+ const row = rowByKey.get(edge.skillKey);
781
+ if (row === void 0) continue;
782
+ if (edge.status === "pending") {
783
+ pendingCount += 1;
784
+ continue;
785
+ }
786
+ if (edge.status !== "active") continue;
787
+ if (row.managed && row.modelInvocable && !row.protected && !row.pinned) managedModelInvocableCount += 1;
788
+ else if (!row.managed) externalCatalogCount += 1;
789
+ }
790
+ return {
791
+ id: node.id,
792
+ title: node.name,
793
+ purpose: node.purpose.slice(0, 80),
794
+ descriptionStatus: node.descriptionStatus === "fresh" && input.descriptionStaleAfterHours !== void 0 && input.now !== void 0 && input.now - node.updatedAt > input.descriptionStaleAfterHours * 36e5 ? "stale" : node.descriptionStatus,
795
+ triggers: node.triggers.slice(0, 3),
796
+ managedModelInvocableCount,
797
+ externalCatalogCount,
798
+ managedCount: managedModelInvocableCount,
799
+ externalCount: externalCatalogCount,
800
+ pendingCount,
801
+ unclassifiedCount,
802
+ state: node.status === "provisional" ? "provisional" : "active",
803
+ asOf: new Date(node.updatedAt).toISOString(),
804
+ mapVersion: input.mapVersion,
805
+ mapStale: mapStale || staleEdgeDomains.has(node.id)
806
+ };
807
+ });
808
+ const budget = input.tokenBudget;
809
+ if (budget !== void 0 && budget > 0) while (cards.length > 1 && Math.ceil(JSON.stringify(cards).length / 4) > budget) cards.pop();
810
+ const cardIds = new Set(cards.map((card) => card.id));
811
+ const wouldHide = /* @__PURE__ */ new Set();
812
+ for (const edge of input.snapshot.edges) {
813
+ if (edge.status !== "active" || edge.role !== "primary") continue;
814
+ const node = domainById.get(edge.domainId);
815
+ const row = rowByKey.get(edge.skillKey);
816
+ if (node === void 0 || row === void 0) continue;
817
+ if (node.status !== "active" || !cardIds.has(node.id) || !row.managed || !row.modelInvocable || row.protected || row.pinned) continue;
818
+ if (edge.skillHash !== row.threeFieldHash) continue;
819
+ if (input.policy.disabled.has(node.id)) continue;
820
+ wouldHide.add(edge.skillKey);
821
+ }
822
+ const activeManaged = mode === "on" && !mapStale && input.searchAvailable !== false && input.policy.degraded.length === 0 ? wouldHide : /* @__PURE__ */ new Set();
823
+ const projected = projectCatalog({
824
+ entries: input.entries,
825
+ rows: input.rows,
826
+ activeManagedSkillKeys: activeManaged,
827
+ cards,
828
+ viewKey: input.viewKey,
829
+ scope: input.scope,
830
+ mapVersion: input.mapVersion,
831
+ policyDigest: input.policyDigest,
832
+ viewCatalogDigest: input.viewCatalogDigest,
833
+ adapterVersion: input.adapterVersion,
834
+ projectionSchemaVersion: input.projectionSchemaVersion,
835
+ discoveryMode: input.discoveryMode,
836
+ complete: true
837
+ });
838
+ return mode === "on" ? projected : projected;
839
+ }
840
+ //#endregion
841
+ //#region lib/types/catalog-codec.js
842
+ function textOf(message) {
843
+ return message.content.map((block) => block.type === "text" ? block.text : "").join("\n");
844
+ }
845
+ function sourceOf(message) {
846
+ if (message.source.kind !== "skill-catalog") return void 0;
847
+ return message.source;
848
+ }
849
+ /** @returns captured catalog when text has the known available_skills block. */
850
+ function captureCatalog(message) {
851
+ const source = sourceOf(message);
852
+ if (source === void 0 || source.form !== "catalog") return void 0;
853
+ const text = textOf(message);
854
+ if (!text.includes("<available_skills>") || !text.includes("</available_skills>")) return void 0;
855
+ return {
856
+ text,
857
+ source,
858
+ entries: source.entries,
859
+ update: source.update === true
860
+ };
861
+ }
862
+ function renderEntries(entries) {
863
+ return entries.map((entry) => "- `" + entry.name + "`: " + escapeText(entry.description)).join("\n");
864
+ }
865
+ /**
866
+ * Replace only the available_skills block; preamble, footer, and update marker stay byte-identical.
867
+ * @param capture - platform message captured before projection.
868
+ * @param entries - projected entries.
869
+ * @returns a platform-sourced message with the same framing.
870
+ */
871
+ function renderCatalog(capture, entries) {
872
+ const start = capture.text.indexOf("<available_skills>");
873
+ const end = capture.text.indexOf("</available_skills>", start);
874
+ return createUserMessage({
875
+ content: [{
876
+ type: "text",
877
+ text: capture.text.slice(0, start + 18) + "\n" + renderEntries(entries) + "\n" + capture.text.slice(end)
878
+ }],
879
+ source: {
880
+ ...capture.source,
881
+ entries: capture.source.entries
882
+ }
883
+ });
884
+ }
885
+ /**
886
+ * Build a replacement catalog when no current catalog message exists.
887
+ * @param sourceEntries - full platform entries; keeps the platform digest stable.
888
+ * @param entries - projected entries.
889
+ * @returns a skill-catalog message with update=true.
890
+ */
891
+ function renderCatalogUpdate(sourceEntries, entries) {
892
+ return createUserMessage({
893
+ content: [{
894
+ type: "text",
895
+ text: [
896
+ "<system-reminder>",
897
+ "The available skill catalog changed. This complete catalog replaces every earlier available-skills list in this session:",
898
+ "",
899
+ "<available_skills>",
900
+ ...entries.map((entry) => "- `" + entry.name + "`: " + escapeText(entry.description)),
901
+ "</available_skills>",
902
+ "",
903
+ "Use only names in this replacement catalog. If the user names a listed skill, or the task clearly matches its description, call the `skill` tool with the exact name before acting.",
904
+ "A user may also invoke a skill directly; its <skill_content> block then appears in this conversation. Follow it, and do not call the `skill` tool again for that skill.",
905
+ "</system-reminder>"
906
+ ].join("\n")
907
+ }],
908
+ source: {
909
+ kind: "skill-catalog",
910
+ form: "catalog",
911
+ update: true,
912
+ entries: sourceEntries
913
+ }
914
+ });
915
+ }
916
+ /** @returns the exact platform message captured before projection. */
917
+ function restoreCatalog(capture) {
918
+ return createUserMessage({
919
+ content: [{
920
+ type: "text",
921
+ text: capture.text
922
+ }],
923
+ source: capture.source
924
+ });
925
+ }
926
+ //#endregion
927
+ //#region lib/types/projection-adapter.js
928
+ /** @returns the first platform catalog message in the current decision. */
929
+ function findCatalogMessage(decision) {
930
+ if (decision.kind !== "enter") return void 0;
931
+ return decision.messages.find((message) => message.source.kind === "skill-catalog");
932
+ }
933
+ /**
934
+ * Replace the current catalog message with projected entries, preserving its framing.
935
+ * @param decision - current pre-step decision.
936
+ * @param capture - platform catalog captured from the same decision.
937
+ * @param entries - projected entries.
938
+ * @returns a new decision; unchanged when no catalog message exists.
939
+ */
940
+ function applyCapturedProjection(decision, capture, entries) {
941
+ if (decision.kind !== "enter") return decision;
942
+ const index = decision.messages.findIndex((message) => message.source.kind === "skill-catalog");
943
+ if (index < 0) return decision;
944
+ const messages = [...decision.messages];
945
+ messages[index] = renderCatalog(capture, entries);
946
+ return {
947
+ ...decision,
948
+ messages
949
+ };
950
+ }
951
+ /**
952
+ * Restore the exact captured platform catalog in the current decision.
953
+ * @param decision - current pre-step decision.
954
+ * @param capture - durable platform catalog capture.
955
+ * @returns a new decision; unchanged when no catalog message exists.
956
+ */
957
+ function restoreCapturedProjection(decision, capture) {
958
+ if (decision.kind !== "enter") return decision;
959
+ const withoutContext = decision.messages.filter((message) => message.source.kind !== "evolution-skill-projection");
960
+ const index = withoutContext.findIndex((message) => message.source.kind === "skill-catalog");
961
+ if (index < 0) return decision;
962
+ const messages = [...withoutContext];
963
+ messages[index] = restoreCatalog(capture);
964
+ return {
965
+ ...decision,
966
+ messages
967
+ };
968
+ }
969
+ /**
970
+ * Append a replacement catalog when the current decision has no catalog message.
971
+ * @param decision - current pre-step decision.
972
+ * @param sourceEntries - full platform entries for the stable digest.
973
+ * @param result - projection result.
974
+ * @returns decision with replacement catalog plus optional context.
975
+ */
976
+ function applyReplacementDecision(decision, sourceEntries, result) {
977
+ if (decision.kind !== "enter") return decision;
978
+ const update = renderCatalogUpdate(sourceEntries, result.catalogEntries);
979
+ const context = renderProjectionContext(result);
980
+ const messages = context === void 0 ? [...decision.messages, update] : [
981
+ ...decision.messages,
982
+ update,
983
+ context
984
+ ];
985
+ return {
986
+ ...decision,
987
+ messages
988
+ };
989
+ }
990
+ /**
991
+ * Append a full replacement catalog without a current catalog message.
992
+ * @param decision - current pre-step decision.
993
+ * @param capture - durable platform capture.
994
+ * @returns decision with a full replacement catalog.
995
+ */
996
+ function applyRestoreUpdate(decision, capture) {
997
+ if (decision.kind !== "enter") return decision;
998
+ return {
999
+ ...decision,
1000
+ messages: [...decision.messages, renderCatalogUpdate(capture.source.entries, capture.entries)]
1001
+ };
1002
+ }
1003
+ /** @returns captured current catalog or undefined when the shape is unknown. */
1004
+ function captureDecisionCatalog(decision) {
1005
+ const message = findCatalogMessage(decision);
1006
+ return message === void 0 ? void 0 : captureCatalog(message);
1007
+ }
1008
+ /** @returns a model-visible projection context message, or undefined when empty. */
1009
+ function renderProjectionContext(result) {
1010
+ if (result.context === "") return void 0;
1011
+ return createUserMessage({
1012
+ content: [{
1013
+ type: "text",
1014
+ text: result.context
1015
+ }],
1016
+ source: {
1017
+ kind: "evolution-skill-projection",
1018
+ entries: result.catalogEntries,
1019
+ hiddenNames: result.hiddenNames,
1020
+ digest: result.digest
1021
+ }
1022
+ });
1023
+ }
1024
+ /**
1025
+ * Apply the complete projection: replace the current catalog and append one context message.
1026
+ * @param decision - current pre-step decision.
1027
+ * @param capture - platform catalog captured from the same decision.
1028
+ * @param result - complete projection.
1029
+ * @returns new decision; unchanged when no catalog exists.
1030
+ */
1031
+ function applyProjectedDecision(decision, capture, result) {
1032
+ const withCatalog = applyCapturedProjection(decision, capture, result.catalogEntries);
1033
+ const context = renderProjectionContext(result);
1034
+ if (context === void 0 || withCatalog.kind !== "enter") return withCatalog;
1035
+ return withCatalog.messages.some((message) => message.source.kind === "evolution-skill-projection" && message.source.digest === result.digest) ? withCatalog : {
1036
+ ...withCatalog,
1037
+ messages: [...withCatalog.messages, context]
1038
+ };
1039
+ }
1040
+ //#endregion
1041
+ //#region lib/types/view-state.js
1042
+ /**
1043
+ * Derive one view's state without consulting another view.
1044
+ * @param input - per-view health and identity.
1045
+ * @returns state with reasons; hidden safety is evaluated by canHide.
1046
+ */
1047
+ function deriveViewState(input) {
1048
+ const reasons = [...input.reasons ?? []];
1049
+ if (input.mode === "off") return {
1050
+ ...input,
1051
+ phase: "off",
1052
+ reasons
1053
+ };
1054
+ if (!input.catalogComplete) reasons.push("catalog-incomplete");
1055
+ if (input.map === "empty" || input.map === "corrupt") {
1056
+ reasons.push(input.map === "corrupt" ? "map-corrupt" : "map-empty");
1057
+ return {
1058
+ ...input,
1059
+ phase: "unavailable",
1060
+ reasons
1061
+ };
1062
+ }
1063
+ if (input.map === "stale") reasons.push("map-stale");
1064
+ if (input.search === "degraded") reasons.push("search-degraded");
1065
+ if (input.policy === "degraded") reasons.push("policy-degraded");
1066
+ if (!input.catalogComplete || input.search === "degraded" || input.policy === "degraded") return {
1067
+ ...input,
1068
+ phase: "degraded",
1069
+ reasons
1070
+ };
1071
+ return {
1072
+ ...input,
1073
+ phase: "ready",
1074
+ reasons
1075
+ };
1076
+ }
1077
+ /** @returns true only when this view may hide catalog entries. */
1078
+ function canHide(state) {
1079
+ return state.phase === "ready" && state.map === "ready" && state.search === "ready" && state.policy === "ready";
1080
+ }
1081
+ /** @returns true when this view may render cards, even if hiding is blocked. */
1082
+ function canProject(state) {
1083
+ return (state.phase === "ready" || state.phase === "degraded") && (state.map === "ready" || state.map === "stale");
1084
+ }
1085
+ /** Bounded view-state cache keyed by view identity and codec version. */
1086
+ var ViewStateRegistry = class {
1087
+ entries = /* @__PURE__ */ new Map();
1088
+ limit;
1089
+ ttlMs;
1090
+ now;
1091
+ constructor(options = {}) {
1092
+ this.limit = options.limit ?? 64;
1093
+ this.ttlMs = options.ttlMs ?? 10 * 6e4;
1094
+ this.now = options.now ?? Date.now;
1095
+ }
1096
+ /** @returns cache key including all identity components that can change a view. */
1097
+ key(state, codecVersion) {
1098
+ return state.viewKey + "|" + state.mapVersion + "|" + state.policyDigest + "|" + codecVersion;
1099
+ }
1100
+ set(state, codecVersion) {
1101
+ const key = this.key(state, codecVersion);
1102
+ this.entries.delete(key);
1103
+ this.entries.set(key, {
1104
+ state,
1105
+ at: this.now()
1106
+ });
1107
+ this.prune();
1108
+ }
1109
+ get(viewKey, mapVersion, policyDigest, codecVersion) {
1110
+ const key = viewKey + "|" + mapVersion + "|" + policyDigest + "|" + codecVersion;
1111
+ const entry = this.entries.get(key);
1112
+ if (entry === void 0) return void 0;
1113
+ if (this.now() - entry.at > this.ttlMs) {
1114
+ this.entries.delete(key);
1115
+ return;
1116
+ }
1117
+ return entry.state;
1118
+ }
1119
+ deleteView(viewKey) {
1120
+ for (const [key, entry] of this.entries) if (entry.state.viewKey === viewKey) this.entries.delete(key);
1121
+ }
1122
+ prune() {
1123
+ const now = this.now();
1124
+ for (const [key, entry] of this.entries) if (now - entry.at > this.ttlMs) this.entries.delete(key);
1125
+ while (this.entries.size > this.limit) {
1126
+ const oldest = this.entries.keys().next().value;
1127
+ if (oldest === void 0) break;
1128
+ this.entries.delete(oldest);
1129
+ }
1130
+ }
1131
+ };
1132
+ //#endregion
1133
+ //#region lib/types/projection-runtime.js
1134
+ function mapStateOf(snapshot, staleAfterHours, now) {
1135
+ if (snapshot === void 0) return "corrupt";
1136
+ if (snapshot.domains.length === 0) return "empty";
1137
+ if (staleAfterHours !== void 0 && snapshot.domains.some((node) => now - node.updatedAt > staleAfterHours * 36e5)) return "stale";
1138
+ return "ready";
1139
+ }
1140
+ function effectiveMode(mode) {
1141
+ return mode === "off" ? "off" : mode === "auto" ? "shadow" : "on";
1142
+ }
1143
+ function catalogText(message) {
1144
+ return message.content.map((block) => block.type === "text" ? block.text ?? "" : "").join("");
1145
+ }
1146
+ function fullCatalogText(capture) {
1147
+ return catalogText(renderCatalog(capture, capture.entries));
1148
+ }
1149
+ function sessionCatalogCapture(agent) {
1150
+ const readEvents = agent.session.readEvents;
1151
+ if (readEvents === void 0) return void 0;
1152
+ let projected;
1153
+ for (const event of readEvents()) {
1154
+ if (event.type !== "user/message" || typeof event.data !== "object" || event.data === null) continue;
1155
+ const message = event.data;
1156
+ if (message.source.kind !== "skill-catalog") continue;
1157
+ const capture = captureCatalog(message);
1158
+ if (capture === void 0) continue;
1159
+ projected = capture;
1160
+ if (catalogText(message) === fullCatalogText(capture)) return capture;
1161
+ }
1162
+ return projected;
1163
+ }
1164
+ /** Per-view projection runtime; no global readiness and no history edits. */
1165
+ var ProjectionRuntime = class {
1166
+ ports;
1167
+ states = new ViewStateRegistry();
1168
+ last = {
1169
+ action: "none",
1170
+ digest: "",
1171
+ at: 0
1172
+ };
1173
+ lastByAgent = /* @__PURE__ */ new Map();
1174
+ constructor(ports) {
1175
+ this.ports = ports;
1176
+ }
1177
+ /** @returns last action for one agent, or the latest process-wide action. */
1178
+ report(agent) {
1179
+ return agent === void 0 ? this.last : this.lastByAgent.get(String(agent.id)) ?? this.last;
1180
+ }
1181
+ mark(agent, action, digest, content = "") {
1182
+ const report = {
1183
+ action,
1184
+ digest,
1185
+ content,
1186
+ at: Date.now()
1187
+ };
1188
+ this.last = report;
1189
+ this.lastByAgent.set(String(agent.id), report);
1190
+ }
1191
+ /** @returns current state for diagnostics, if this process has seen the view. */
1192
+ stateFor(viewKey, mapVersion, policyDigest) {
1193
+ return this.states.get(viewKey, mapVersion, policyDigest, this.ports.codecVersion);
1194
+ }
1195
+ async restore(agent, viewKey, decision, currentCapture) {
1196
+ const record = await this.ports.journal.latest(String(agent.id), viewKey, this.ports.codecVersion);
1197
+ const sessionBase = sessionCatalogCapture(agent);
1198
+ const fullSession = sessionBase !== void 0 && sessionBase.text === fullCatalogText(sessionBase) ? sessionBase : void 0;
1199
+ const durable = currentCapture !== void 0 && currentCapture.text !== fullCatalogText(currentCapture) ? fullSession ?? record?.base : currentCapture ?? fullSession ?? record?.base;
1200
+ if (durable === void 0) {
1201
+ this.mark(agent, "blocked", "");
1202
+ return decision;
1203
+ }
1204
+ const fullText = fullCatalogText(durable);
1205
+ const previous = this.report(agent);
1206
+ if (previous.action === "restored" && previous.content === fullText) return decision;
1207
+ const restored = currentCapture === void 0 ? applyRestoreUpdate(decision, durable) : restoreCapturedProjection(decision, durable);
1208
+ if (record !== void 0) await this.ports.journal.remove(record.sessionId, record.viewKey, record.projectionDigest, record.codecVersion);
1209
+ this.mark(agent, "restored", record?.projectionDigest ?? "", fullText);
1210
+ return restored;
1211
+ }
1212
+ async readState(agent, signal) {
1213
+ const cwd = await this.ports.workingDirectory.ensure(agent, signal);
1214
+ const view = await new SkillIndex(this.ports.skillIndexSource).read({
1215
+ cwd,
1216
+ scopeKey: String(agent.id),
1217
+ scope: agent,
1218
+ signal
1219
+ });
1220
+ let snapshot;
1221
+ try {
1222
+ snapshot = await this.ports.map.read();
1223
+ } catch {
1224
+ snapshot = void 0;
1225
+ }
1226
+ const knownDomainIds = snapshot === void 0 ? void 0 : new Set(snapshot.domains.map((node) => node.id));
1227
+ const store = this.ports.policyFor === void 0 ? this.ports.policy : await this.ports.policyFor(cwd);
1228
+ const policy = store.resolve(knownDomainIds);
1229
+ const policyDigest = store.digest(knownDomainIds);
1230
+ const search = this.ports.searchAvailable?.() === false ? "degraded" : "ready";
1231
+ const state = deriveViewState({
1232
+ viewKey: view.viewKey,
1233
+ mode: effectiveMode(policy.mode),
1234
+ map: mapStateOf(snapshot, this.ports.staleAfterHours, Date.now()),
1235
+ search,
1236
+ policy: policy.degraded.length === 0 ? "ready" : "degraded",
1237
+ catalogComplete: view.complete,
1238
+ mapVersion: snapshot?.mapVersion ?? "0",
1239
+ policyDigest,
1240
+ viewCatalogDigest: view.viewCatalogDigest
1241
+ });
1242
+ this.states.set(state, this.ports.codecVersion);
1243
+ return {
1244
+ view,
1245
+ snapshot,
1246
+ policy,
1247
+ policyDigest,
1248
+ state
1249
+ };
1250
+ }
1251
+ /** @returns current per-view state without projecting; used by diagnostic commands. */
1252
+ async inspect(agent, signal) {
1253
+ if (this.ports.mode === "off") return {
1254
+ viewKey: "off",
1255
+ phase: "off",
1256
+ map: "empty",
1257
+ search: "ready",
1258
+ policy: "ready",
1259
+ reasons: [],
1260
+ mapVersion: "0",
1261
+ policyDigest: "",
1262
+ viewCatalogDigest: ""
1263
+ };
1264
+ return (await this.readState(agent, signal)).state;
1265
+ }
1266
+ /**
1267
+ * Project or restore one current pre-step decision.
1268
+ * @param agent - session owner; identity comes from workingDirectory.
1269
+ * @param decision - current waterfall decision.
1270
+ * @param signal - request cancellation.
1271
+ * @returns projected, restored, or unchanged decision.
1272
+ */
1273
+ async project(agent, decision, signal) {
1274
+ if (this.ports.mode === "off") return decision;
1275
+ if (this.ports.isFrozen !== void 0 && await this.ports.isFrozen(agent)) return decision;
1276
+ const { view, snapshot, policy, policyDigest, state } = await this.readState(agent, signal);
1277
+ const currentCapture = captureDecisionCatalog(decision);
1278
+ let capture = currentCapture;
1279
+ if (capture === void 0) capture = (await this.ports.journal.latest(String(agent.id), view.viewKey, this.ports.codecVersion))?.base;
1280
+ if (capture === void 0) capture = sessionCatalogCapture(agent);
1281
+ if (!canProject(state) || snapshot === void 0) return this.restore(agent, view.viewKey, decision, currentCapture);
1282
+ if (capture === void 0) {
1283
+ this.mark(agent, "blocked", "");
1284
+ return decision;
1285
+ }
1286
+ const projection = buildDomainProjection({
1287
+ entries: capture.entries,
1288
+ rows: view.rows,
1289
+ complete: view.complete,
1290
+ snapshot,
1291
+ policy,
1292
+ viewKey: view.viewKey,
1293
+ scope: String(agent.id),
1294
+ mapVersion: snapshot.mapVersion,
1295
+ policyDigest,
1296
+ viewCatalogDigest: view.viewCatalogDigest,
1297
+ adapterVersion: this.ports.adapterVersion,
1298
+ projectionSchemaVersion: this.ports.projectionSchemaVersion,
1299
+ discoveryMode: this.ports.mode,
1300
+ ...this.ports.maxCards === void 0 ? {} : { maxCards: this.ports.maxCards },
1301
+ ...this.ports.tokenBudget === void 0 ? {} : { tokenBudget: this.ports.tokenBudget },
1302
+ searchAvailable: state.search === "ready",
1303
+ mode: effectiveMode(policy.mode),
1304
+ mapStale: state.map === "stale",
1305
+ now: Date.now(),
1306
+ ...this.ports.staleAfterHours === void 0 ? {} : { staleAfterHours: this.ports.staleAfterHours },
1307
+ ...this.ports.descriptionStaleAfterHours === void 0 ? {} : { descriptionStaleAfterHours: this.ports.descriptionStaleAfterHours }
1308
+ });
1309
+ if (!projection.complete) return this.restore(agent, view.viewKey, decision, currentCapture);
1310
+ const expectedText = renderCatalog(capture, projection.catalogEntries).content.map((block) => block.type === "text" ? block.text : "").join("");
1311
+ if (currentCapture !== void 0 && currentCapture.text === expectedText) return decision;
1312
+ const previous = this.report(agent);
1313
+ if (currentCapture === void 0 && previous.action === "applied" && previous.digest === projection.digest && previous.content === expectedText) return decision;
1314
+ await this.ports.journal.put({
1315
+ sessionId: String(agent.id),
1316
+ viewKey: view.viewKey,
1317
+ projectionDigest: projection.digest,
1318
+ codecVersion: this.ports.codecVersion,
1319
+ mapVersion: snapshot.mapVersion,
1320
+ policyDigest,
1321
+ base: capture,
1322
+ createdAt: Date.now()
1323
+ });
1324
+ this.mark(agent, "applied", projection.digest, expectedText);
1325
+ return currentCapture === void 0 ? applyReplacementDecision(decision, capture.source.entries, projection) : applyProjectedDecision(decision, capture, projection);
1326
+ }
1327
+ };
1328
+ //#endregion
1329
+ //#region lib/types/domain-generation-prompt.js
1330
+ /** Frozen initial domain-generation prompt; caller owns schema/IDs. */
1331
+ const DOMAIN_GENERATION_PROMPT_VERSION = "v2";
1332
+ const DOMAIN_GENERATION_PROMPT_V1 = `Group the provided skills into first-level domains.
1333
+ Use only name, description, and whenToUse; do not infer from skill bodies.
1334
+ Produce 8–12 domains that are coherent, distinct, task-recognizable, and useful as an awareness layer. A single skill is not a domain. Each domain has a name, one-sentence purpose, in-scope, out-of-scope, triggers, and evidence skill names that actually support it.
1335
+ Prefer clear boundaries over exhaustive coverage. If the evidence is weak, leave the skill unassigned rather than forcing it into a domain. Together the domains should cover the library where evidence supports it. Domain IDs are assigned by the caller.
1336
+ Return only the structured result.`;
1337
+ /** @returns a parameterized initial-domain prompt; default 8–12. */
1338
+ function buildDomainGenerationPrompt(options = {}) {
1339
+ const requestedMin = options.minDomains ?? 8;
1340
+ const requestedMax = options.maxDomains ?? 12;
1341
+ const valid = Number.isSafeInteger(requestedMin) && Number.isSafeInteger(requestedMax) && requestedMin >= 2 && requestedMax >= requestedMin && requestedMax <= 20;
1342
+ return `You are designing a first-level domain map for a skill library.
1343
+ Input is limited to each skill's name, description, and whenToUse; do not infer from skill bodies.
1344
+ Choose ${valid ? requestedMin : 8}–${valid ? requestedMax : 12} domains that help a model decide where to look. Domains are an awareness layer, not a complete taxonomy.
1345
+ Each domain needs a name, one-sentence purpose, in-scope, out-of-scope, triggers, and evidence skill names copied from the input.
1346
+ Keep domains coherent, distinguishable, and recognizable from a task description; a single skill is not a domain. Do not invent skills or domains.
1347
+ Prefer clear boundaries over exhaustive coverage. Leave a skill unassigned when evidence is weak rather than forcing it. The caller assigns IDs.
1348
+ Return only a JSON object with a "domains" array. Each domain must have title, purpose, inScope, outOfScope, triggers, and evidenceSkillKeys. No markdown fences and no prose.`;
1349
+ }
1350
+ const DOMAIN_GENERATION_PROMPT_V2 = buildDomainGenerationPrompt();
1351
+ const DOMAIN_GENERATION_PROMPT_HASH = createHash("sha256").update(DOMAIN_GENERATION_PROMPT_V2, "utf8").digest("hex");
1352
+ /** Active prompt; v1 remains frozen for traceability. */
1353
+ const DOMAIN_GENERATION_PROMPT = DOMAIN_GENERATION_PROMPT_V2;
1354
+ //#endregion
1355
+ //#region lib/types/domain-generation-llm.js
1356
+ /** Typed failure from LLM transport/parse/validation. */
1357
+ var DomainGenerationError = class extends Error {
1358
+ code;
1359
+ constructor(code, message) {
1360
+ super(message);
1361
+ this.code = code;
1362
+ this.name = "DomainGenerationError";
1363
+ }
1364
+ };
1365
+ /** @returns parsed proposal with the caller's skill set; throws on malformed/empty output. */
1366
+ function parseDomainGenerationProposal(text, skills) {
1367
+ const cleaned = text.trim().replace(/^\`\`\`(?:json)?\s*/i, "").replace(/\`\`\`$/, "").trim();
1368
+ if (cleaned.length === 0) throw new DomainGenerationError("invalid-output", "domain generation returned empty output");
1369
+ let parsed;
1370
+ try {
1371
+ parsed = JSON.parse(cleaned);
1372
+ } catch (error) {
1373
+ throw new DomainGenerationError("invalid-output", "domain generation returned non-JSON output: " + (error instanceof Error ? error.message : String(error)));
1374
+ }
1375
+ const candidate = isRecord(parsed) && Array.isArray(parsed.domains) ? { domains: parsed.domains } : Array.isArray(parsed) ? { domains: parsed } : void 0;
1376
+ if (candidate === void 0) throw new DomainGenerationError("invalid-output", "domain generation output must be an object with domains[]");
1377
+ const checked = validateDomainGeneration({ skills }, candidate);
1378
+ if (!checked.ok) throw new DomainGenerationError("invalid-proposal", "invalid domain proposal: " + checked.errors.join("; "));
1379
+ return candidate;
1380
+ }
1381
+ /** Production LLM adapter; validates before returning, never writes the map. */
1382
+ var LlmDomainGenerationPort = class {
1383
+ llm;
1384
+ options;
1385
+ constructor(llm, options) {
1386
+ this.llm = llm;
1387
+ this.options = {
1388
+ provider: options.provider,
1389
+ model: options.model,
1390
+ maxTokens: options.maxTokens ?? 4096,
1391
+ timeoutMs: options.timeoutMs ?? 12e4,
1392
+ attempts: options.attempts ?? 3
1393
+ };
1394
+ }
1395
+ /**
1396
+ * Generate one validated domain proposal.
1397
+ * @param input - live facts, prompt, and cancellation.
1398
+ * @returns validated proposal with 8–12 domains and known evidence.
1399
+ */
1400
+ async generate(input) {
1401
+ const timeout = AbortSignal.timeout(this.options.timeoutMs);
1402
+ const signal = input.signal === void 0 ? timeout : AbortSignal.any([input.signal, timeout]);
1403
+ const skillContext = "\n\nAvailable skills (copy evidenceSkillKeys only from these names):\n" + input.skills.map((skill) => "- " + skill.name + ": " + skill.description + (skill.whenToUse === void 0 ? "" : " | when: " + skill.whenToUse)).join("\n");
1404
+ const basePrompt = input.prompt + skillContext;
1405
+ let feedback = "";
1406
+ let last;
1407
+ for (let attempt = 1; attempt <= this.options.attempts; attempt += 1) try {
1408
+ const prompt = feedback === "" ? basePrompt : basePrompt + "\nPrevious output was rejected: " + feedback + "\nReturn only the structured result with 8-12 domains.";
1409
+ const assembler = new BlockAssembler();
1410
+ for await (const chunk of this.llm.stream({
1411
+ provider: this.options.provider,
1412
+ model: this.options.model,
1413
+ messages: [createUserMessage({
1414
+ content: [{
1415
+ type: "text",
1416
+ text: prompt
1417
+ }],
1418
+ source: {
1419
+ kind: "evolution-skill-generation",
1420
+ form: "notice",
1421
+ summary: "initial domain generation"
1422
+ }
1423
+ })],
1424
+ maxTokens: this.options.maxTokens,
1425
+ signal
1426
+ })) assembler.push(chunk);
1427
+ return parseDomainGenerationProposal(assembler.blocks().filter((block) => block.type === "text").map((block) => block.text).join("\n"), input.skills);
1428
+ } catch (error) {
1429
+ if (!(error instanceof DomainGenerationError) || error.code !== "invalid-output" && error.code !== "invalid-proposal") {
1430
+ if (timeout.aborted) throw new DomainGenerationError("timeout", "domain generation timed out after " + this.options.timeoutMs + "ms");
1431
+ throw new DomainGenerationError("llm-unavailable", "domain generation llm failed: " + (error instanceof Error ? error.message : String(error)));
1432
+ }
1433
+ last = error;
1434
+ feedback = error.message;
1435
+ }
1436
+ throw last ?? new DomainGenerationError("invalid-output", "domain generation failed");
1437
+ }
1438
+ };
1439
+ //#endregion
1440
+ //#region lib/types/generation-input.js
1441
+ /**
1442
+ * Convert live model-invocable rows into the initial-map writer's real facts.
1443
+ * @param rows - current view rows.
1444
+ * @returns stable keys and three-field hashes, never synthetic values.
1445
+ */
1446
+ function bootstrapFactsFromRows(rows) {
1447
+ return rows.filter((row) => row.modelInvocable).map((row) => ({
1448
+ name: row.name,
1449
+ description: row.description,
1450
+ ...row.whenToUse === void 0 ? {} : { whenToUse: row.whenToUse },
1451
+ skillKey: row.skillKey,
1452
+ threeFieldHash: row.threeFieldHash
1453
+ }));
1454
+ }
1455
+ //#endregion
1456
+ //#region lib/types/generation-composition.js
1457
+ /**
1458
+ * Resolve the production port; mode:on without an llm service fails loud.
1459
+ * @param input - mode and concrete llm service.
1460
+ * @returns port, or undefined for mode:off.
1461
+ */
1462
+ function resolveGenerationPort(input) {
1463
+ if (input.mode === "off") return void 0;
1464
+ if (input.llm === void 0) throw new DomainGenerationError("llm-unavailable", "evolution-skill-discovery: mode=" + input.mode + " requires an llm service for initial domain generation");
1465
+ return new LlmDomainGenerationPort(input.llm, {
1466
+ provider: input.provider,
1467
+ model: input.model,
1468
+ ...input.maxTokens === void 0 ? {} : { maxTokens: input.maxTokens },
1469
+ ...input.timeoutMs === void 0 ? {} : { timeoutMs: input.timeoutMs },
1470
+ ...input.attempts === void 0 ? {} : { attempts: input.attempts }
1471
+ });
1472
+ }
1473
+ /**
1474
+ * Build a global-map generation request from live rows.
1475
+ * @param input - map scope, source identity, rows, and prompt.
1476
+ * @returns request with real facts; never reads config arrays.
1477
+ */
1478
+ function buildGenerationRequest(input) {
1479
+ return {
1480
+ mapScope: input.mapScope,
1481
+ sourceDigest: input.sourceDigest,
1482
+ expectedMapVersion: input.mapVersion,
1483
+ facts: bootstrapFactsFromRows(input.rows),
1484
+ prompt: input.prompt,
1485
+ promptVersion: input.promptVersion,
1486
+ model: input.model,
1487
+ runId: input.runId
1488
+ };
1489
+ }
1490
+ //#endregion
1491
+ //#region lib/types/view-snapshot-store.js
1492
+ /** Durable per-session view snapshot; old epochs stay frozen. */
1493
+ var ViewSnapshotStore = class {
1494
+ io;
1495
+ path;
1496
+ constructor(io, path) {
1497
+ this.io = io;
1498
+ this.path = path;
1499
+ }
1500
+ async read() {
1501
+ const content = await this.io.readText(this.path);
1502
+ if (content === null) return {
1503
+ schemaVersion: 1,
1504
+ sessions: {}
1505
+ };
1506
+ try {
1507
+ const parsed = JSON.parse(content);
1508
+ return parsed.schemaVersion === 1 && typeof parsed.sessions === "object" && parsed.sessions !== null ? {
1509
+ schemaVersion: 1,
1510
+ sessions: parsed.sessions
1511
+ } : {
1512
+ schemaVersion: 1,
1513
+ sessions: {}
1514
+ };
1515
+ } catch {
1516
+ return {
1517
+ schemaVersion: 1,
1518
+ sessions: {}
1519
+ };
1520
+ }
1521
+ }
1522
+ /** Persist one session snapshot; first writer wins unless overwrite is explicit. */
1523
+ async put(snapshot, options = {}) {
1524
+ const current = await this.read();
1525
+ if (current.sessions[snapshot.sessionId] !== void 0 && options.overwrite !== true) return;
1526
+ const sessions = {
1527
+ ...current.sessions,
1528
+ [snapshot.sessionId]: {
1529
+ viewKey: snapshot.viewKey,
1530
+ cwd: snapshot.cwd,
1531
+ viewCatalogDigest: snapshot.viewCatalogDigest,
1532
+ mapVersion: snapshot.mapVersion,
1533
+ configDigest: snapshot.configDigest,
1534
+ featureEpoch: snapshot.featureEpoch,
1535
+ createdAt: snapshot.createdAt
1536
+ }
1537
+ };
1538
+ await this.io.writeText(this.path, JSON.stringify({
1539
+ schemaVersion: 1,
1540
+ sessions
1541
+ }));
1542
+ }
1543
+ /** @returns frozen snapshot for one session, if recorded. */
1544
+ async get(sessionId) {
1545
+ const entry = (await this.read()).sessions[sessionId];
1546
+ return entry === void 0 ? void 0 : {
1547
+ sessionId,
1548
+ ...entry
1549
+ };
1550
+ }
1551
+ /**
1552
+ * @returns true for legacy sessions without a snapshot and for older feature epochs.
1553
+ */
1554
+ async isFrozen(sessionId, currentFeatureEpoch) {
1555
+ const snapshot = await this.get(sessionId);
1556
+ return snapshot === void 0 || snapshot.featureEpoch !== currentFeatureEpoch;
1557
+ }
1558
+ };
1559
+ //#endregion
1560
+ //#region lib/types/assignment.js
1561
+ /** In-memory idempotent queue; production adapter can persist by key. */
1562
+ var MemoryAssignmentQueue = class {
1563
+ entries = /* @__PURE__ */ new Map();
1564
+ async enqueue(input) {
1565
+ const existing = this.entries.get(input.key);
1566
+ if (existing !== void 0) return {
1567
+ entry: existing,
1568
+ duplicate: true
1569
+ };
1570
+ const entry = {
1571
+ ...input,
1572
+ status: "queued"
1573
+ };
1574
+ this.entries.set(input.key, entry);
1575
+ return {
1576
+ entry,
1577
+ duplicate: false
1578
+ };
1579
+ }
1580
+ async update(key, status, pendingReason) {
1581
+ const entry = this.entries.get(key);
1582
+ if (entry !== void 0) {
1583
+ entry.status = status;
1584
+ entry.pendingReason = pendingReason;
1585
+ }
1586
+ }
1587
+ list() {
1588
+ return [...this.entries.values()];
1589
+ }
1590
+ };
1591
+ function digest(value) {
1592
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
1593
+ }
1594
+ /** @returns idempotency key for one proposal observation. */
1595
+ function assignmentKey(input) {
1596
+ return digest([
1597
+ input.skillKey,
1598
+ input.threeFieldHash,
1599
+ input.proposalDigest,
1600
+ input.mapVersion
1601
+ ]);
1602
+ }
1603
+ /** @returns stable proposal digest; undefined proposal is digest of null. */
1604
+ function proposalDigest(proposal) {
1605
+ return digest(proposal ?? null);
1606
+ }
1607
+ /** @returns a closed-set pending reason or undefined when the proposal can enqueue. */
1608
+ function validateProposal(proposal) {
1609
+ if (proposal.mode !== "existing" && proposal.mode !== "new") return "schema-invalid";
1610
+ if (typeof proposal.confidence !== "number" || proposal.confidence < 0 || proposal.confidence > 1 || typeof proposal.reason !== "string" || typeof proposal.mapVersion !== "string") return "schema-invalid";
1611
+ if (proposal.mode === "existing" && (typeof proposal.id !== "string" || proposal.id.length === 0)) return "invalid-id";
1612
+ if (proposal.mode === "new") {
1613
+ const proposed = proposal.proposed;
1614
+ if (proposed === void 0 || proposed.title.trim().length === 0 || proposed.purpose.trim().length === 0 || proposed.inScope.length === 0 || proposed.outOfScope.length === 0 || proposed.triggers.length === 0) return "new-domain-conflict";
1615
+ }
1616
+ }
1617
+ /** @returns true only when whenToUse is covered by in/out/triggers; names alone do not count. */
1618
+ function coveredByDomain(whenToUse, domain) {
1619
+ const text = whenToUse.toLowerCase();
1620
+ if (text.length === 0) return false;
1621
+ if (domain.outOfScope.some((value) => value.length > 0 && text.includes(value.toLowerCase()))) return false;
1622
+ return domain.triggers.some((value) => value.length > 0 && text.includes(value.toLowerCase())) || domain.inScope.some((value) => value.length > 0 && text.includes(value.toLowerCase()));
1623
+ }
1624
+ /** One assignment sidecar service; the only map write path for create. */
1625
+ var DomainAssignmentService = class {
1626
+ queue;
1627
+ store;
1628
+ options;
1629
+ constructor(queue, store, options) {
1630
+ this.queue = queue;
1631
+ this.store = store;
1632
+ this.options = options;
1633
+ }
1634
+ /** Stage a create proposal without queueing it. */
1635
+ stage(submission) {
1636
+ return {
1637
+ id: digest([
1638
+ "staged",
1639
+ submission.skillKey,
1640
+ submission.threeFieldHash
1641
+ ]),
1642
+ submission
1643
+ };
1644
+ }
1645
+ /** Approve a staged create; only this call enqueues. */
1646
+ async approve(staged) {
1647
+ return this.submit(staged.submission);
1648
+ }
1649
+ /**
1650
+ * Submit a create proposal after SkillLibrary.create succeeded.
1651
+ * @param submission - skill identity, three-field hash, permission, proposal.
1652
+ * @returns queue/pending/unclassified; never rolls back create.
1653
+ */
1654
+ async submit(submission) {
1655
+ if (!this.options.createClassificationEnabled) return {
1656
+ kind: "unclassified",
1657
+ reason: "feature-off"
1658
+ };
1659
+ if (!submission.modelInvocable) return {
1660
+ kind: "unclassified",
1661
+ reason: "not-model-invocable"
1662
+ };
1663
+ if (submission.proposal === void 0) {
1664
+ if (this.options.createCardsAvailable === true) {
1665
+ const reason = "schema-invalid";
1666
+ return {
1667
+ kind: "pending",
1668
+ entry: {
1669
+ key: digest([
1670
+ "missing-proposal",
1671
+ submission.skillKey,
1672
+ submission.threeFieldHash
1673
+ ]),
1674
+ submission,
1675
+ proposalDigest: proposalDigest(void 0),
1676
+ createdAt: this.now(),
1677
+ status: "pending",
1678
+ pendingReason: reason
1679
+ },
1680
+ reason
1681
+ };
1682
+ }
1683
+ return {
1684
+ kind: "unclassified",
1685
+ reason: "no-proposal"
1686
+ };
1687
+ }
1688
+ const invalid = validateProposal(submission.proposal);
1689
+ const pdigest = proposalDigest(submission.proposal);
1690
+ const key = assignmentKey({
1691
+ skillKey: submission.skillKey,
1692
+ threeFieldHash: submission.threeFieldHash,
1693
+ proposalDigest: pdigest,
1694
+ mapVersion: submission.proposal.mapVersion
1695
+ });
1696
+ let queued;
1697
+ try {
1698
+ queued = await this.queue.enqueue({
1699
+ key,
1700
+ submission,
1701
+ proposalDigest: pdigest,
1702
+ createdAt: submission.now ?? this.now()
1703
+ });
1704
+ } catch {
1705
+ return {
1706
+ kind: "pending",
1707
+ entry: {
1708
+ key,
1709
+ submission,
1710
+ proposalDigest: pdigest,
1711
+ createdAt: submission.now ?? this.now(),
1712
+ status: "pending",
1713
+ pendingReason: "queue-unavailable"
1714
+ },
1715
+ reason: "queue-unavailable"
1716
+ };
1717
+ }
1718
+ if (invalid !== void 0) {
1719
+ await this.queue.update(key, "pending", invalid);
1720
+ return {
1721
+ kind: "pending",
1722
+ entry: {
1723
+ ...queued.entry,
1724
+ status: "pending",
1725
+ pendingReason: invalid
1726
+ },
1727
+ reason: invalid
1728
+ };
1729
+ }
1730
+ return {
1731
+ kind: "queued",
1732
+ entry: queued.entry,
1733
+ duplicate: queued.duplicate
1734
+ };
1735
+ }
1736
+ /** Process one queued entry with the current map; CAS failure becomes pending. */
1737
+ async process(entry) {
1738
+ const proposal = entry.submission.proposal;
1739
+ if (proposal === void 0) return {
1740
+ kind: "pending",
1741
+ entry,
1742
+ reason: "schema-invalid"
1743
+ };
1744
+ const current = await this.store.read();
1745
+ if (proposal.mapVersion !== current.mapVersion) {
1746
+ await this.queue.update(entry.key, "pending", "map-version-stale");
1747
+ return {
1748
+ kind: "pending",
1749
+ entry: {
1750
+ ...entry,
1751
+ status: "pending",
1752
+ pendingReason: "map-version-stale"
1753
+ },
1754
+ reason: "map-version-stale"
1755
+ };
1756
+ }
1757
+ try {
1758
+ const next = buildNext(current, entry, proposal, this.options.provisionalThreshold ?? 3);
1759
+ const committed = await this.store.commit(current.mapVersion, {
1760
+ mapVersion: next.mapVersion,
1761
+ domains: next.domains,
1762
+ edges: next.edges
1763
+ });
1764
+ const status = next.provisional ? "provisional" : "applied";
1765
+ await this.queue.update(entry.key, status);
1766
+ return {
1767
+ kind: status,
1768
+ entry: {
1769
+ ...entry,
1770
+ status
1771
+ },
1772
+ mapVersion: committed.mapVersion
1773
+ };
1774
+ } catch (error) {
1775
+ if (error instanceof DomainMapVersionConflict) {
1776
+ await this.queue.update(entry.key, "pending", "map-version-stale");
1777
+ return {
1778
+ kind: "pending",
1779
+ entry: {
1780
+ ...entry,
1781
+ status: "pending",
1782
+ pendingReason: "map-version-stale"
1783
+ },
1784
+ reason: "map-version-stale"
1785
+ };
1786
+ }
1787
+ const reason = error instanceof Error && (error.message === "unknown-domain" || error.message === "new-domain-conflict" || error.message === "schema-invalid") ? error.message : "queue-unavailable";
1788
+ await this.queue.update(entry.key, "pending", reason);
1789
+ return {
1790
+ kind: "pending",
1791
+ entry: {
1792
+ ...entry,
1793
+ status: "pending",
1794
+ pendingReason: reason
1795
+ },
1796
+ reason
1797
+ };
1798
+ }
1799
+ }
1800
+ /** Manually confirm one provisional domain; no semantic reclassification. */
1801
+ async confirm(domainId) {
1802
+ const current = await this.store.read();
1803
+ const domain = current.domains.find((node) => node.id === domainId);
1804
+ if (domain === void 0 || domain.status !== "provisional") return false;
1805
+ const domains = current.domains.map((node) => node.id === domainId ? {
1806
+ ...node,
1807
+ status: "active",
1808
+ updatedAt: this.now()
1809
+ } : node);
1810
+ await this.store.commit(current.mapVersion, {
1811
+ mapVersion: nextMapVersion(current.mapVersion),
1812
+ domains,
1813
+ edges: current.edges
1814
+ });
1815
+ return true;
1816
+ }
1817
+ now() {
1818
+ return this.options.now?.() ?? Date.now();
1819
+ }
1820
+ };
1821
+ function nextMapVersion(version) {
1822
+ const numeric = Number(version);
1823
+ return Number.isSafeInteger(numeric) && numeric >= 0 ? String(numeric + 1) : version + "+1";
1824
+ }
1825
+ function buildNext(current, entry, proposal, threshold) {
1826
+ const edge = {
1827
+ skillKey: entry.submission.skillKey,
1828
+ domainId: "",
1829
+ role: "primary",
1830
+ confidence: proposal.confidence,
1831
+ source: "create",
1832
+ skillHash: entry.submission.threeFieldHash,
1833
+ mapVersion: nextMapVersion(current.mapVersion),
1834
+ status: "active",
1835
+ assignedAt: entry.createdAt
1836
+ };
1837
+ const secondaries = (proposal.secondaryDomainIds ?? []).map((id) => {
1838
+ const secondary = current.domains.find((node) => node.id === id);
1839
+ if (secondary === void 0) throw new Error("unknown-domain");
1840
+ return {
1841
+ ...edge,
1842
+ domainId: secondary.id,
1843
+ role: "secondary"
1844
+ };
1845
+ });
1846
+ if (proposal.mode === "existing") {
1847
+ const domain = current.domains.find((node) => node.id === proposal.id);
1848
+ if (domain === void 0) throw new Error("unknown-domain");
1849
+ if (!coveredByDomain(entry.submission.whenToUse ?? "", domain)) throw new Error("new-domain-conflict");
1850
+ const members = new Set(current.edges.filter((item) => item.domainId === domain.id).map((item) => item.skillKey));
1851
+ members.add(entry.submission.skillKey);
1852
+ const promote = domain.status === "provisional" && members.size >= threshold;
1853
+ const domains = promote ? current.domains.map((node) => node.id === domain.id ? {
1854
+ ...node,
1855
+ status: "active",
1856
+ updatedAt: entry.createdAt
1857
+ } : node) : current.domains;
1858
+ return {
1859
+ mapVersion: nextMapVersion(current.mapVersion),
1860
+ domains,
1861
+ edges: [
1862
+ ...current.edges,
1863
+ {
1864
+ ...edge,
1865
+ domainId: domain.id
1866
+ },
1867
+ ...secondaries.filter((secondary) => secondary.domainId !== domain.id)
1868
+ ],
1869
+ provisional: domain.status === "provisional" && !promote
1870
+ };
1871
+ }
1872
+ const proposed = proposal.proposed;
1873
+ if (proposed === void 0) throw new Error("schema-invalid");
1874
+ const normalizedTitle = proposed.title.trim().toLowerCase();
1875
+ const existing = current.domains.find((node) => node.name.trim().toLowerCase() === normalizedTitle);
1876
+ if (existing !== void 0 && existing.status !== "provisional") throw new Error("new-domain-conflict");
1877
+ const id = existing?.id ?? mintDomainId(proposed.title);
1878
+ if (current.domains.some((node) => node.id !== id && node.triggers.some((trigger) => proposed.triggers.includes(trigger)))) throw new Error("new-domain-conflict");
1879
+ const domain = existing ?? {
1880
+ id,
1881
+ name: proposed.title,
1882
+ purpose: proposed.purpose,
1883
+ description: proposed.purpose,
1884
+ inScope: proposed.inScope,
1885
+ outOfScope: proposed.outOfScope,
1886
+ triggers: proposed.triggers,
1887
+ parentIds: [],
1888
+ status: "provisional",
1889
+ version: 1,
1890
+ createdAt: entry.createdAt,
1891
+ updatedAt: entry.createdAt,
1892
+ descriptionHash: "pending",
1893
+ descriptionVersion: 1,
1894
+ descriptionStatus: "fresh",
1895
+ provenance: { source: "create" },
1896
+ quality: {}
1897
+ };
1898
+ const members = new Set(current.edges.filter((item) => item.domainId === id).map((item) => item.skillKey));
1899
+ members.add(entry.submission.skillKey);
1900
+ const promote = members.size >= threshold;
1901
+ const finalDomain = promote ? {
1902
+ ...domain,
1903
+ status: "active",
1904
+ updatedAt: entry.createdAt
1905
+ } : domain;
1906
+ return {
1907
+ mapVersion: nextMapVersion(current.mapVersion),
1908
+ domains: existing === void 0 ? [...current.domains, finalDomain] : current.domains.map((node) => node.id === id ? finalDomain : node),
1909
+ edges: [
1910
+ ...current.edges,
1911
+ {
1912
+ ...edge,
1913
+ domainId: id
1914
+ },
1915
+ ...secondaries.filter((secondary) => secondary.domainId !== id)
1916
+ ],
1917
+ provisional: !promote
1918
+ };
1919
+ }
1920
+ //#endregion
1921
+ //#region lib/types/assignment-queue.js
1922
+ /** Durable assignment queue; process-local cache over one JSON file. */
1923
+ var JsonAssignmentQueue = class {
1924
+ entries = /* @__PURE__ */ new Map();
1925
+ loaded = false;
1926
+ io;
1927
+ path;
1928
+ constructor(io, path) {
1929
+ this.io = io;
1930
+ this.path = path;
1931
+ }
1932
+ async load() {
1933
+ if (this.loaded) return;
1934
+ this.loaded = true;
1935
+ const content = await this.io.readText(this.path);
1936
+ if (content === null) return;
1937
+ try {
1938
+ const parsed = JSON.parse(content);
1939
+ if (parsed.schemaVersion !== 1 || !Array.isArray(parsed.entries)) return;
1940
+ for (const entry of parsed.entries) this.entries.set(entry.key, entry);
1941
+ } catch {}
1942
+ }
1943
+ async persist() {
1944
+ await this.io.writeText(this.path, JSON.stringify({
1945
+ schemaVersion: 1,
1946
+ entries: [...this.entries.values()]
1947
+ }));
1948
+ }
1949
+ async enqueue(input) {
1950
+ await this.load();
1951
+ const existing = this.entries.get(input.key);
1952
+ if (existing !== void 0) return {
1953
+ entry: existing,
1954
+ duplicate: true
1955
+ };
1956
+ const entry = {
1957
+ ...input,
1958
+ status: "queued"
1959
+ };
1960
+ this.entries.set(input.key, entry);
1961
+ await this.persist();
1962
+ return {
1963
+ entry,
1964
+ duplicate: false
1965
+ };
1966
+ }
1967
+ async update(key, status, pendingReason) {
1968
+ await this.load();
1969
+ const entry = this.entries.get(key);
1970
+ if (entry === void 0) return;
1971
+ entry.status = status;
1972
+ entry.pendingReason = pendingReason;
1973
+ await this.persist();
1974
+ }
1975
+ list() {
1976
+ return [...this.entries.values()];
1977
+ }
1978
+ };
1979
+ //#endregion
1980
+ //#region lib/types/worker.js
1981
+ /** Drivable worker; the composition may call drain on a schedule. */
1982
+ var AssignmentWorker = class {
1983
+ service;
1984
+ queue;
1985
+ constructor(service, queue) {
1986
+ this.service = service;
1987
+ this.queue = queue;
1988
+ }
1989
+ /** Process queued entries once; returns processed count. */
1990
+ async drain(limit = 10) {
1991
+ const entries = this.queue.list().filter((entry) => entry.status === "queued").slice(0, Math.max(0, limit));
1992
+ for (const entry of entries) await this.service.process(entry);
1993
+ return entries.length;
1994
+ }
1995
+ };
1996
+ //#endregion
1997
+ //#region lib/types/assignment-sidecar.js
1998
+ function submitCreate(service, args, options) {
1999
+ if (typeof args.name !== "string" || args.name.length === 0) return void 0;
2000
+ const parsed = parseFrontmatter(typeof args.content === "string" ? args.content : "");
2001
+ const description = parsed?.frontmatter.description ?? "";
2002
+ const rawWhenToUse = parsed?.frontmatter.whenToUse;
2003
+ const whenToUse = typeof rawWhenToUse === "string" ? rawWhenToUse : void 0;
2004
+ const domain = args.domain;
2005
+ const proposal = typeof domain === "object" && domain !== null ? domain : void 0;
2006
+ return service.submit({
2007
+ skillKey: skillKeyOf({
2008
+ provider: options.provider ?? "dsh-evolution",
2009
+ source: options.source ?? "user-dsh",
2010
+ name: args.name
2011
+ }),
2012
+ threeFieldHash: threeFieldHashOf({
2013
+ name: args.name,
2014
+ description,
2015
+ ...whenToUse === void 0 ? {} : { whenToUse }
2016
+ }),
2017
+ modelInvocable: parsed?.frontmatter["disable-model-invocation"] !== true,
2018
+ ...whenToUse === void 0 ? {} : { whenToUse },
2019
+ ...proposal === void 0 ? {} : { proposal }
2020
+ }).then(() => options.onSubmitted?.());
2021
+ }
2022
+ /**
2023
+ * Observe successful create calls and submit their optional domain proposal.
2024
+ * Direct creates submit immediately; staged creates submit only after approval.
2025
+ * @param ctx - plugin context with tools and approval events.
2026
+ * @param service - the one assignment service.
2027
+ * @param options - provider/source identity for skillKey.
2028
+ * @returns disposer for both listeners.
2029
+ */
2030
+ function installAssignmentSidecar(ctx, service, options = {}) {
2031
+ const stopResult = ctx.on("tools/result", (exec, result) => {
2032
+ if (result.isError || exec.name !== "skill_manage" || exec.agent === void 0) return;
2033
+ if (result.value?.pending_id !== void 0) return;
2034
+ const args = exec.arguments;
2035
+ if (typeof args !== "object" || args === null) return;
2036
+ const record = args;
2037
+ if (record.action !== "create") return;
2038
+ submitCreate(service, record, options)?.catch(() => {});
2039
+ });
2040
+ const stopApproval = ctx.on("evolution/approval-decided", ({ record, action, ok }) => {
2041
+ if (!ok || action !== "approve" || record.kind !== "skill" || typeof record.args !== "object" || record.args === null) return;
2042
+ const raw = record.args;
2043
+ const operation = typeof raw.operation === "object" && raw.operation !== null ? raw.operation : raw;
2044
+ if (operation.action !== "create") return;
2045
+ submitCreate(service, operation, options)?.catch(() => {});
2046
+ });
2047
+ return () => {
2048
+ stopResult();
2049
+ stopApproval();
2050
+ };
2051
+ }
2052
+ //#endregion
2053
+ //#region lib/types/awareness.js
2054
+ /** Static 1–2 sentence awareness; no domain list/tree. */
2055
+ const DISCOVERY_AWARENESS = "When skill coverage for a task is uncertain, the domain cards and skill_search can reduce it; search is optional, not a required step.";
2056
+ /**
2057
+ * Register the static awareness section; absent systemPrompt is a no-op.
2058
+ * platform-declaration anchor: the optional systemPrompt service declared in core PLATFORM_SERVICE_PROBES.
2059
+ */
2060
+ function installDiscoveryAwareness(ctx) {
2061
+ const prompt = ctx.get("systemPrompt");
2062
+ if (prompt === void 0) return () => {};
2063
+ return prompt.section({
2064
+ name: "evolution-skill-discovery-awareness",
2065
+ order: 40,
2066
+ text: DISCOVERY_AWARENESS,
2067
+ interpolate: false
2068
+ });
2069
+ }
2070
+ //#endregion
2071
+ //#region lib/types/domain-description.js
2072
+ /** @returns true only for structural/definition events; normal skill add does not rewrite. */
2073
+ function shouldRegenerateDescription(event) {
2074
+ return event !== "skill-add";
2075
+ }
2076
+ /** @returns true when the existing description may still serve. */
2077
+ function descriptionIsServable(status) {
2078
+ return status === "fresh" || status === "stale";
2079
+ }
2080
+ /** @returns hash of child ids/description versions, never member skill names. */
2081
+ function descriptionHashOf(domain, children) {
2082
+ const childRefs = children.map((child) => JSON.stringify([child.id, child.descriptionVersion])).sort();
2083
+ return createHash("sha256").update(JSON.stringify([
2084
+ domain.id,
2085
+ domain.descriptionVersion,
2086
+ childRefs
2087
+ ])).digest("hex");
2088
+ }
2089
+ /**
2090
+ * Render a domain description from definition fields only. It never appends
2091
+ * evidence/member skill names and is capped to 600 characters.
2092
+ * @param domain - domain definition.
2093
+ * @returns description text.
2094
+ */
2095
+ function renderDomainDescription(domain) {
2096
+ return [
2097
+ domain.name + ":" + domain.purpose,
2098
+ "适用:" + domain.inScope.join(";"),
2099
+ "不适用:" + domain.outOfScope.join(";"),
2100
+ "触发:" + domain.triggers.join(";")
2101
+ ].join("\n").slice(0, 600);
2102
+ }
2103
+ //#endregion
2104
+ export { DomainMapCorruptError as $, ViewStateRegistry as A, renderProjectionContext as B, DOMAIN_GENERATION_PROMPT as C, DOMAIN_GENERATION_PROMPT_VERSION as D, DOMAIN_GENERATION_PROMPT_V2 as E, applyProjectedDecision as F, restoreCatalog as G, captureCatalog as H, applyReplacementDecision as I, projectCatalog as J, buildDomainProjection as K, applyRestoreUpdate as L, canProject as M, deriveViewState as N, buildDomainGenerationPrompt as O, applyCapturedProjection as P, DOMAIN_MAP_SCHEMA_VERSION as Q, captureDecisionCatalog as R, parseDomainGenerationProposal as S, DOMAIN_GENERATION_PROMPT_V1 as T, renderCatalog as U, restoreCapturedProjection as V, renderCatalogUpdate as W, evaluateDomainQuality as X, sanitizeUntrustedText as Y, ProjectionJournal as Z, buildGenerationRequest as _, DISCOVERY_AWARENESS as a, mergeDomainPolicy as at, DomainGenerationError as b, AssignmentWorker as c, bootstrapDomainMapWithFacts as ct, MemoryAssignmentQueue as d, validateDomainGeneration as dt, DomainMapFileStore as et, assignmentKey as f, ViewSnapshotStore as g, validateProposal as h, shouldRegenerateDescription as i, DomainPolicyValidationError as it, canHide as j, ProjectionRuntime as k, JsonAssignmentQueue as l, domainGenerationMode as lt, proposalDigest as m, descriptionIsServable as n, DomainMapVersionConflict as nt, installDiscoveryAwareness as o, DomainGenerationCoordinator as ot, coveredByDomain as p, hiddenRecoverable as q, renderDomainDescription as r, DomainPolicyStore as rt, installAssignmentSidecar as s, bootstrapDomainMap as st, descriptionHashOf as t, DomainMapValidationError as tt, DomainAssignmentService as u, mintDomainId as ut, resolveGenerationPort as v, DOMAIN_GENERATION_PROMPT_HASH as w, LlmDomainGenerationPort as x, bootstrapFactsFromRows as y, findCatalogMessage as z };