agentflow-dashboard 0.8.3 → 0.9.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,1220 @@
1
+ // ../../../soma/dist/chunk-ZZ3NNYCV.js
2
+ import {
3
+ closeSync,
4
+ constants,
5
+ existsSync,
6
+ mkdirSync,
7
+ openSync,
8
+ readdirSync,
9
+ readFileSync,
10
+ renameSync,
11
+ rmSync,
12
+ statfsSync,
13
+ statSync,
14
+ unlinkSync,
15
+ writeFileSync
16
+ } from "fs";
17
+ import { createHash, randomUUID } from "crypto";
18
+ import { join, basename, dirname } from "path";
19
+ import { withGuards } from "agentflow-core";
20
+ import { getPathSignature } from "agentflow-core";
21
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
22
+ var FRONTMATTER_DELIM = "---";
23
+ var WIKILINK_REGEX = /\[\[([^\]]+)\]\]/g;
24
+ function parseEntity(content, defaults) {
25
+ var _a;
26
+ const lines = content.split("\n");
27
+ let frontmatter = {};
28
+ let bodyStartLine = 0;
29
+ if (((_a = lines[0]) == null ? void 0 : _a.trim()) === FRONTMATTER_DELIM) {
30
+ const endIdx = lines.indexOf(FRONTMATTER_DELIM, 1);
31
+ if (endIdx > 0) {
32
+ const yamlLines = lines.slice(1, endIdx);
33
+ frontmatter = parseSimpleYaml(yamlLines.join("\n"));
34
+ bodyStartLine = endIdx + 1;
35
+ }
36
+ }
37
+ const body = lines.slice(bodyStartLine).join("\n").trim();
38
+ const fmRelated = Array.isArray(frontmatter.related) ? frontmatter.related : [];
39
+ const bodyLinks = extractWikilinks(body);
40
+ const allRelated = [.../* @__PURE__ */ new Set([...fmRelated, ...bodyLinks])];
41
+ const now = (/* @__PURE__ */ new Date()).toISOString();
42
+ const resolvedType = frontmatter.type ?? (defaults == null ? void 0 : defaults.type);
43
+ if (!resolvedType) console.warn(`[Entity] Missing type in frontmatter and defaults, using 'untyped'`);
44
+ return {
45
+ ...frontmatter,
46
+ type: resolvedType ?? "untyped",
47
+ id: frontmatter.id ?? (defaults == null ? void 0 : defaults.id) ?? "",
48
+ name: frontmatter.name ?? (defaults == null ? void 0 : defaults.name) ?? "",
49
+ status: frontmatter.status ?? (defaults == null ? void 0 : defaults.status) ?? "active",
50
+ created: frontmatter.created ?? (defaults == null ? void 0 : defaults.created) ?? now,
51
+ updated: frontmatter.updated ?? (defaults == null ? void 0 : defaults.updated) ?? now,
52
+ tags: Array.isArray(frontmatter.tags) ? frontmatter.tags : [],
53
+ related: allRelated,
54
+ body
55
+ };
56
+ }
57
+ function serializeEntity(entity) {
58
+ const { body, ...fm } = entity;
59
+ const yamlLines = [FRONTMATTER_DELIM];
60
+ for (const [key, value] of Object.entries(fm)) {
61
+ if (value === void 0 || value === null) continue;
62
+ if (key === "body") continue;
63
+ yamlLines.push(serializeYamlField(key, value));
64
+ }
65
+ yamlLines.push(FRONTMATTER_DELIM);
66
+ return `${yamlLines.join("\n")}
67
+
68
+ ${body ?? ""}
69
+ `;
70
+ }
71
+ function extractWikilinks(text) {
72
+ const links = [];
73
+ let match;
74
+ while ((match = WIKILINK_REGEX.exec(text)) !== null) {
75
+ if (match[1]) links.push(match[1]);
76
+ }
77
+ return [...new Set(links)];
78
+ }
79
+ function parseSimpleYaml(yaml) {
80
+ const result = {};
81
+ const lines = yaml.split("\n");
82
+ for (let i = 0; i < lines.length; i++) {
83
+ const line = lines[i];
84
+ const colonIdx = line.indexOf(":");
85
+ if (colonIdx <= 0) continue;
86
+ const key = line.slice(0, colonIdx).trim();
87
+ let value = line.slice(colonIdx + 1).trim();
88
+ if (value.startsWith("{") && value.endsWith("}")) {
89
+ try {
90
+ result[key] = JSON.parse(value);
91
+ } catch {
92
+ result[key] = value.replace(/^["']|["']$/g, "");
93
+ }
94
+ continue;
95
+ }
96
+ if (value.startsWith("[") && value.endsWith("]")) {
97
+ try {
98
+ result[key] = JSON.parse(value);
99
+ } catch {
100
+ result[key] = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
101
+ }
102
+ } else if (value === "") {
103
+ const items = [];
104
+ while (i + 1 < lines.length && lines[i + 1].trimStart().startsWith("- ")) {
105
+ i++;
106
+ items.push(lines[i].trimStart().slice(2).trim());
107
+ }
108
+ if (items.length > 0) result[key] = items;
109
+ else result[key] = "";
110
+ } else if (value === "true" || value === "false") {
111
+ result[key] = value === "true";
112
+ } else if (!Number.isNaN(Number(value)) && value !== "") {
113
+ result[key] = Number(value);
114
+ } else {
115
+ result[key] = value.replace(/^["']|["']$/g, "");
116
+ }
117
+ }
118
+ return result;
119
+ }
120
+ function serializeYamlField(key, value) {
121
+ if (Array.isArray(value)) {
122
+ if (value.length === 0) return `${key}: []`;
123
+ if (value.some((v) => typeof v === "object" && v !== null)) {
124
+ return `${key}: ${JSON.stringify(value)}`;
125
+ }
126
+ if (value.length <= 5 && value.every((v) => typeof v === "string" && v.length < 30)) {
127
+ return `${key}: [${value.map((v) => JSON.stringify(v)).join(", ")}]`;
128
+ }
129
+ return `${key}:
130
+ ${value.map((v) => ` - ${typeof v === "string" ? v : JSON.stringify(v)}`).join("\n")}`;
131
+ }
132
+ if (typeof value === "object" && value !== null) {
133
+ return `${key}: ${JSON.stringify(value)}`;
134
+ }
135
+ if (typeof value === "string" && (value.includes(":") || value.includes("#") || value.includes("\n"))) {
136
+ return `${key}: "${value.replace(/"/g, '\\"')}"`;
137
+ }
138
+ return `${key}: ${value}`;
139
+ }
140
+ var DEFAULT_BASE_DIR = ".soma/vault";
141
+ var VAULT_ID_FILE = "_vault_id.json";
142
+ var MIN_DISK_SPACE_BYTES = 10 * 1024 * 1024;
143
+ var MAX_MUTATION_LOG_BYTES = 10 * 1024 * 1024;
144
+ var LOCK_TIMEOUT_MS = 5e3;
145
+ var LOCK_POLL_MS = 50;
146
+ var idCounter = 0;
147
+ function vaultFingerprint(baseDir) {
148
+ const idPath = join(baseDir, VAULT_ID_FILE);
149
+ if (!existsSync(idPath)) return "";
150
+ try {
151
+ const content = readFileSync(idPath, "utf-8");
152
+ return createHash("sha256").update(content).digest("hex");
153
+ } catch {
154
+ return "";
155
+ }
156
+ }
157
+ function vaultEntityCount(baseDir) {
158
+ const indexPath = join(baseDir, "_index.json");
159
+ if (!existsSync(indexPath)) return 0;
160
+ try {
161
+ const content = readFileSync(indexPath, "utf-8");
162
+ const index = JSON.parse(content);
163
+ if (typeof index === "object" && index !== null) {
164
+ return Object.keys(index).length;
165
+ }
166
+ return 0;
167
+ } catch {
168
+ return 0;
169
+ }
170
+ }
171
+ function isProcessAlive(pid) {
172
+ try {
173
+ process.kill(pid, 0);
174
+ return true;
175
+ } catch {
176
+ return false;
177
+ }
178
+ }
179
+ function acquireLock(lockPath) {
180
+ const startTime = Date.now();
181
+ if (existsSync(lockPath)) {
182
+ try {
183
+ const content = readFileSync(lockPath, "utf-8");
184
+ const pid = parseInt(content.trim(), 10);
185
+ if (!isNaN(pid) && !isProcessAlive(pid)) {
186
+ try {
187
+ unlinkSync(lockPath);
188
+ } catch {
189
+ }
190
+ }
191
+ } catch {
192
+ try {
193
+ unlinkSync(lockPath);
194
+ } catch {
195
+ }
196
+ }
197
+ }
198
+ while (true) {
199
+ try {
200
+ const fd = openSync(lockPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
201
+ writeFileSync(fd, `${process.pid}
202
+ `);
203
+ closeSync(fd);
204
+ return;
205
+ } catch (err) {
206
+ const code = err.code;
207
+ if (code !== "EEXIST") throw err;
208
+ try {
209
+ const content = readFileSync(lockPath, "utf-8");
210
+ const pid = parseInt(content.trim(), 10);
211
+ if (!isNaN(pid) && !isProcessAlive(pid)) {
212
+ try {
213
+ unlinkSync(lockPath);
214
+ } catch {
215
+ }
216
+ continue;
217
+ }
218
+ } catch {
219
+ continue;
220
+ }
221
+ if (Date.now() - startTime >= LOCK_TIMEOUT_MS) {
222
+ throw new Error(`Vault lock timeout: could not acquire ${lockPath} within ${LOCK_TIMEOUT_MS}ms`);
223
+ }
224
+ const waitUntil = Date.now() + LOCK_POLL_MS;
225
+ while (Date.now() < waitUntil) {
226
+ }
227
+ }
228
+ }
229
+ }
230
+ function releaseLock(lockPath) {
231
+ try {
232
+ unlinkSync(lockPath);
233
+ } catch {
234
+ }
235
+ }
236
+ function checkDiskSpace(dirPath) {
237
+ try {
238
+ const stats = statfsSync(dirPath);
239
+ const available = stats.bavail * stats.bsize;
240
+ if (available < MIN_DISK_SPACE_BYTES) {
241
+ throw new Error(
242
+ `Insufficient disk space: ${Math.round(available / 1024 / 1024)}MB available, minimum ${Math.round(MIN_DISK_SPACE_BYTES / 1024 / 1024)}MB required`
243
+ );
244
+ }
245
+ } catch (err) {
246
+ if (err instanceof Error && err.message.startsWith("Insufficient disk space")) throw err;
247
+ }
248
+ }
249
+ function cleanupTempFiles(baseDir) {
250
+ let cleaned = 0;
251
+ if (!existsSync(baseDir)) return cleaned;
252
+ try {
253
+ for (const entry of readdirSync(baseDir)) {
254
+ if (entry.startsWith("_") || entry.startsWith(".")) continue;
255
+ const typePath = join(baseDir, entry);
256
+ try {
257
+ const stat = statSync(typePath);
258
+ if (!stat.isDirectory()) continue;
259
+ for (const file of readdirSync(typePath)) {
260
+ if (file.includes(".tmp.")) {
261
+ try {
262
+ unlinkSync(join(typePath, file));
263
+ cleaned++;
264
+ } catch {
265
+ }
266
+ }
267
+ }
268
+ } catch {
269
+ }
270
+ }
271
+ } catch {
272
+ }
273
+ try {
274
+ for (const file of readdirSync(baseDir)) {
275
+ if (file.includes(".tmp.")) {
276
+ try {
277
+ unlinkSync(join(baseDir, file));
278
+ cleaned++;
279
+ } catch {
280
+ }
281
+ }
282
+ }
283
+ } catch {
284
+ }
285
+ return cleaned;
286
+ }
287
+ function createVault(config) {
288
+ const baseDir = (config == null ? void 0 : config.baseDir) ?? DEFAULT_BASE_DIR;
289
+ const indexPath = join(baseDir, "_index.json");
290
+ const mutationLogPath = join(baseDir, "_mutations.jsonl");
291
+ const lockPath = join(baseDir, "_vault.lock");
292
+ let index = /* @__PURE__ */ new Map();
293
+ function ensureDir(dir) {
294
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
295
+ }
296
+ function entityPath(type, id) {
297
+ return join(baseDir, type, `${id}.md`);
298
+ }
299
+ function writeAtomic(filePath, content) {
300
+ ensureDir(dirname(filePath));
301
+ const tmpPath = `${filePath}.tmp.${Date.now()}`;
302
+ try {
303
+ writeFileSync(tmpPath, content, "utf-8");
304
+ renameSync(tmpPath, filePath);
305
+ } catch (err) {
306
+ try {
307
+ unlinkSync(tmpPath);
308
+ } catch (cleanupErr) {
309
+ console.warn(`[Vault] Failed to clean up temp file ${tmpPath}:`, cleanupErr.message);
310
+ }
311
+ throw err;
312
+ }
313
+ }
314
+ function rotateMutationLog() {
315
+ try {
316
+ const stat = statSync(mutationLogPath);
317
+ if (stat.size >= MAX_MUTATION_LOG_BYTES) {
318
+ const rotatedPath = join(baseDir, `_mutations.${Date.now()}.jsonl`);
319
+ renameSync(mutationLogPath, rotatedPath);
320
+ }
321
+ } catch {
322
+ }
323
+ }
324
+ function logMutation(entry) {
325
+ ensureDir(baseDir);
326
+ rotateMutationLog();
327
+ writeFileSync(mutationLogPath, `${JSON.stringify(entry)}
328
+ `, { flag: "a" });
329
+ }
330
+ function generateId(type, name) {
331
+ const normalized = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
332
+ return normalized || `${type}-${Date.now()}-${idCounter++}`;
333
+ }
334
+ function updateIndex(entity, filePath) {
335
+ index.set(entity.id, {
336
+ id: entity.id,
337
+ type: entity.type,
338
+ name: entity.name,
339
+ status: entity.status,
340
+ tags: entity.tags,
341
+ related: entity.related,
342
+ path: filePath,
343
+ layer: entity.layer
344
+ });
345
+ saveIndex();
346
+ }
347
+ function removeFromIndex(id) {
348
+ index.delete(id);
349
+ saveIndex();
350
+ }
351
+ function saveIndex() {
352
+ ensureDir(baseDir);
353
+ const entries = Object.fromEntries(index);
354
+ writeAtomic(indexPath, JSON.stringify(entries, null, 2));
355
+ }
356
+ function loadIndex() {
357
+ if (existsSync(indexPath)) {
358
+ try {
359
+ const data = JSON.parse(readFileSync(indexPath, "utf-8"));
360
+ index = new Map(Object.entries(data));
361
+ } catch {
362
+ rebuildIndex();
363
+ }
364
+ }
365
+ }
366
+ function validateIndex() {
367
+ if (index.size === 0) return true;
368
+ const entries = Array.from(index.values());
369
+ const sampleSize = Math.max(1, Math.ceil(entries.length * 0.1));
370
+ const step = Math.max(1, Math.floor(entries.length / sampleSize));
371
+ let missingCount = 0;
372
+ let checkedCount = 0;
373
+ for (let i = 0; i < entries.length && checkedCount < sampleSize; i += step) {
374
+ checkedCount++;
375
+ const entry = entries[i];
376
+ if (!existsSync(entry.path)) {
377
+ missingCount++;
378
+ }
379
+ }
380
+ if (missingCount > checkedCount / 2) {
381
+ rebuildIndex();
382
+ return false;
383
+ }
384
+ return true;
385
+ }
386
+ function rebuildIndex() {
387
+ index = /* @__PURE__ */ new Map();
388
+ if (!existsSync(baseDir)) return;
389
+ for (const typeDir of readdirSync(baseDir)) {
390
+ if (typeDir.startsWith("_") || typeDir.startsWith(".")) continue;
391
+ const typePath = join(baseDir, typeDir);
392
+ try {
393
+ const stat = statSync(typePath);
394
+ if (!stat.isDirectory()) continue;
395
+ const files = readdirSync(typePath).filter((f) => f.endsWith(".md"));
396
+ for (const file of files) {
397
+ const filePath = join(typePath, file);
398
+ try {
399
+ const content = readFileSync(filePath, "utf-8");
400
+ const entity = parseEntity(content, { type: typeDir, id: basename(file, ".md") });
401
+ if (!entity.id) entity.id = basename(file, ".md");
402
+ index.set(entity.id, {
403
+ id: entity.id,
404
+ type: entity.type,
405
+ name: entity.name,
406
+ status: entity.status,
407
+ tags: entity.tags,
408
+ related: entity.related,
409
+ path: filePath,
410
+ layer: entity.layer
411
+ });
412
+ } catch {
413
+ }
414
+ }
415
+ } catch {
416
+ }
417
+ }
418
+ saveIndex();
419
+ }
420
+ const cleanedCount = cleanupTempFiles(baseDir);
421
+ if (cleanedCount > 0) {
422
+ process.stderr.write(`[soma/vault] cleaned up ${cleanedCount} orphaned temp file(s)
423
+ `);
424
+ }
425
+ if (existsSync(lockPath)) {
426
+ try {
427
+ const content = readFileSync(lockPath, "utf-8");
428
+ const pid = parseInt(content.trim(), 10);
429
+ if (!isNaN(pid) && !isProcessAlive(pid)) {
430
+ try {
431
+ unlinkSync(lockPath);
432
+ } catch {
433
+ }
434
+ }
435
+ } catch {
436
+ try {
437
+ unlinkSync(lockPath);
438
+ } catch {
439
+ }
440
+ }
441
+ }
442
+ loadIndex();
443
+ if (index.size === 0 && existsSync(baseDir)) {
444
+ rebuildIndex();
445
+ } else if (index.size > 0) {
446
+ validateIndex();
447
+ }
448
+ const vaultIdPath = join(baseDir, VAULT_ID_FILE);
449
+ if (existsSync(baseDir) && !existsSync(vaultIdPath)) {
450
+ writeAtomic(vaultIdPath, JSON.stringify({
451
+ id: randomUUID(),
452
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
453
+ }, null, 2) + "\n");
454
+ }
455
+ let onReadCallback = config == null ? void 0 : config.onRead;
456
+ function _readRaw(type, id) {
457
+ const entry = index.get(id);
458
+ if (entry && existsSync(entry.path)) {
459
+ try {
460
+ const content = readFileSync(entry.path, "utf-8");
461
+ return parseEntity(content, { type, id });
462
+ } catch {
463
+ return null;
464
+ }
465
+ }
466
+ const fp = entityPath(type, id);
467
+ if (!existsSync(fp)) return null;
468
+ try {
469
+ const content = readFileSync(fp, "utf-8");
470
+ return parseEntity(content, { type, id });
471
+ } catch {
472
+ return null;
473
+ }
474
+ }
475
+ return {
476
+ baseDir,
477
+ setOnRead(callback) {
478
+ onReadCallback = callback;
479
+ },
480
+ create(partial) {
481
+ ensureDir(baseDir);
482
+ checkDiskSpace(baseDir);
483
+ acquireLock(lockPath);
484
+ try {
485
+ const now = (/* @__PURE__ */ new Date()).toISOString();
486
+ const id = partial.id ?? generateId(partial.type, partial.name);
487
+ const entity = {
488
+ ...partial,
489
+ type: partial.type,
490
+ id,
491
+ name: partial.name,
492
+ status: partial.status ?? "active",
493
+ created: partial.created ?? now,
494
+ updated: now,
495
+ tags: partial.tags ?? [],
496
+ related: partial.related ?? [],
497
+ body: partial.body ?? ""
498
+ };
499
+ const fp = entityPath(entity.type, entity.id);
500
+ writeAtomic(fp, serializeEntity(entity));
501
+ updateIndex(entity, fp);
502
+ logMutation({ ts: Date.now(), op: "create", id: entity.id, type: entity.type });
503
+ return entity.id;
504
+ } finally {
505
+ releaseLock(lockPath);
506
+ }
507
+ },
508
+ read(type, id) {
509
+ const entity = _readRaw(type, id);
510
+ if (entity && onReadCallback) {
511
+ try {
512
+ onReadCallback(entity);
513
+ } catch {
514
+ }
515
+ }
516
+ return entity;
517
+ },
518
+ update(id, patch) {
519
+ if ("layer" in patch && patch.layer !== void 0) {
520
+ const entry = index.get(id);
521
+ if (entry && entry.layer !== void 0 && patch.layer !== entry.layer) {
522
+ throw new Error(
523
+ `Cannot change layer via vault.update() \u2014 layer changes require writeToLayer or the governance API. Current layer: '${entry.layer}', attempted: '${patch.layer}'`
524
+ );
525
+ }
526
+ }
527
+ ensureDir(baseDir);
528
+ checkDiskSpace(baseDir);
529
+ acquireLock(lockPath);
530
+ try {
531
+ const entry = index.get(id);
532
+ if (!entry) return;
533
+ const existing = _readRaw(entry.type, id);
534
+ if (!existing) return;
535
+ const updated = {
536
+ ...existing,
537
+ ...patch,
538
+ updated: (/* @__PURE__ */ new Date()).toISOString()
539
+ };
540
+ writeAtomic(entry.path, serializeEntity(updated));
541
+ updateIndex(updated, entry.path);
542
+ logMutation({
543
+ ts: Date.now(),
544
+ op: "update",
545
+ id,
546
+ type: entry.type,
547
+ fields: Object.keys(patch)
548
+ });
549
+ } finally {
550
+ releaseLock(lockPath);
551
+ }
552
+ },
553
+ remove(id) {
554
+ acquireLock(lockPath);
555
+ try {
556
+ const entry = index.get(id);
557
+ if (!entry) return;
558
+ try {
559
+ rmSync(entry.path);
560
+ } catch {
561
+ }
562
+ removeFromIndex(id);
563
+ logMutation({ ts: Date.now(), op: "delete", id, type: entry.type });
564
+ } finally {
565
+ releaseLock(lockPath);
566
+ }
567
+ },
568
+ list(type, filter) {
569
+ const limit = (filter == null ? void 0 : filter.limit) ?? 1e3;
570
+ const results = [];
571
+ for (const entry of index.values()) {
572
+ if (entry.type !== type) continue;
573
+ if (filter) {
574
+ let match = true;
575
+ for (const [key, value] of Object.entries(filter)) {
576
+ if (key === "limit" || key === "offset") continue;
577
+ if (entry[key] !== value) {
578
+ match = false;
579
+ break;
580
+ }
581
+ }
582
+ if (!match) continue;
583
+ }
584
+ const entity = _readRaw(type, entry.id);
585
+ if (entity) results.push(entity);
586
+ if (results.length >= limit) break;
587
+ }
588
+ return results;
589
+ },
590
+ listByLayer(layer, filter) {
591
+ const limit = (filter == null ? void 0 : filter.limit) ?? 1e3;
592
+ const results = [];
593
+ for (const entry of index.values()) {
594
+ if (entry.layer !== layer) continue;
595
+ const entity = _readRaw(entry.type, entry.id);
596
+ if (!entity) continue;
597
+ if (filter) {
598
+ let match = true;
599
+ for (const [key, value] of Object.entries(filter)) {
600
+ if (key === "limit" || key === "offset" || key === "layer") continue;
601
+ if (entity[key] !== value) {
602
+ match = false;
603
+ break;
604
+ }
605
+ }
606
+ if (!match) continue;
607
+ }
608
+ results.push(entity);
609
+ if (results.length >= limit) break;
610
+ }
611
+ return results;
612
+ },
613
+ findByTag(tag) {
614
+ const results = [];
615
+ for (const entry of index.values()) {
616
+ if (entry.tags.includes(tag)) {
617
+ const entity = _readRaw(entry.type, entry.id);
618
+ if (entity) results.push(entity);
619
+ }
620
+ }
621
+ return results;
622
+ },
623
+ findLinked(id) {
624
+ const entry = index.get(id);
625
+ if (!entry) return [];
626
+ const results = [];
627
+ for (const link of entry.related) {
628
+ const parts = link.split("/");
629
+ if (parts.length >= 2) {
630
+ const linkedType = parts[0];
631
+ const linkedId = parts.slice(1).join("/");
632
+ const entity = _readRaw(linkedType, linkedId);
633
+ if (entity) results.push(entity);
634
+ }
635
+ }
636
+ return results;
637
+ },
638
+ rebuildIndex
639
+ };
640
+ }
641
+ var MAX_REASONING_LEN = 200;
642
+ var MAX_OUTPUT_LEN = 100;
643
+ function truncate(s, max) {
644
+ if (!s) return void 0;
645
+ return s.length > max ? s.slice(0, max) + "..." : s;
646
+ }
647
+ function enrichActionName(toolName, args) {
648
+ var _a;
649
+ if (!args) return toolName;
650
+ if ((toolName === "exec" || toolName === "bash") && typeof args.command === "string") {
651
+ const cmd = args.command.trim();
652
+ const program = (_a = cmd.split(/\s+/)[0]) == null ? void 0 : _a.replace(/^.*\//, "");
653
+ if (program) return `${toolName}:${program}`;
654
+ }
655
+ if ((toolName === "write" || toolName === "read") && typeof args.file_path === "string") {
656
+ const parts = args.file_path.split("/").filter(Boolean);
657
+ for (let i = parts.length - 2; i >= 0; i--) {
658
+ const seg = parts[i];
659
+ if (seg.startsWith(".") || /^(home|tmp|var|usr|etc|lib)$/.test(seg) || /^[a-z]{1,3}\d*$/.test(seg)) continue;
660
+ return `${toolName}:${seg}`;
661
+ }
662
+ }
663
+ if (typeof args.action === "string") {
664
+ return `${toolName}:${args.action}`;
665
+ }
666
+ if (typeof args.url === "string") {
667
+ try {
668
+ const host = new URL(args.url).hostname.replace("www.", "");
669
+ return `${toolName}:${host}`;
670
+ } catch {
671
+ }
672
+ }
673
+ return toolName;
674
+ }
675
+ function extractDecisionsFromSession(events) {
676
+ const decisions = [];
677
+ let lastReasoning;
678
+ let decisionIndex = 0;
679
+ const toolResultsById = /* @__PURE__ */ new Map();
680
+ const toolResultsByParentId = /* @__PURE__ */ new Map();
681
+ for (const event of events) {
682
+ if (event.type !== "message") continue;
683
+ const msg = event.message;
684
+ if (!msg) continue;
685
+ if (msg.role === "tool") {
686
+ for (const c of msg.content) {
687
+ if (typeof c === "object" && c !== null) {
688
+ const item = c;
689
+ const toolId = item.tool_use_id;
690
+ if (toolId) {
691
+ toolResultsById.set(toolId, {
692
+ isError: !!item.is_error,
693
+ content: String(item.content ?? "").slice(0, MAX_OUTPUT_LEN)
694
+ });
695
+ }
696
+ }
697
+ }
698
+ }
699
+ if (msg.role === "toolResult") {
700
+ const parentId = event.parentId;
701
+ let text = "";
702
+ let isError = false;
703
+ for (const c of msg.content) {
704
+ if (typeof c === "object" && c !== null) {
705
+ const item = c;
706
+ if (item.type === "text") text = String(item.text ?? "").slice(0, MAX_OUTPUT_LEN);
707
+ if (item.is_error) isError = true;
708
+ }
709
+ }
710
+ if (event.isError) isError = true;
711
+ if (parentId) {
712
+ toolResultsByParentId.set(parentId, { isError, content: text });
713
+ }
714
+ }
715
+ }
716
+ for (const event of events) {
717
+ if (event.type !== "message") continue;
718
+ const msg = event.message;
719
+ if (!msg || msg.role !== "assistant") continue;
720
+ const eventId = event.id;
721
+ for (const c of msg.content) {
722
+ if (typeof c !== "object" || c === null) continue;
723
+ const item = c;
724
+ if (item.type === "thinking" || item.type === "text") {
725
+ const text = String(item.text ?? "").trim();
726
+ if (text) lastReasoning = text;
727
+ }
728
+ if (item.type === "tool_use" || item.type === "toolCall") {
729
+ const toolId = item.id ?? item.tool_use_id;
730
+ const result = toolResultsById.get(toolId) ?? toolResultsByParentId.get(eventId);
731
+ const toolName = String(item.name ?? "unattributed");
732
+ if (!item.name) console.warn(`[Decision-Extraction] Tool call missing name, using 'unattributed'`);
733
+ const args = item.input ?? item.arguments;
734
+ const action = enrichActionName(toolName, args);
735
+ decisions.push({
736
+ action,
737
+ reasoning: truncate(lastReasoning, MAX_REASONING_LEN),
738
+ tool: toolName,
739
+ args,
740
+ outcome: result ? result.isError ? "failed" : "ok" : "skipped",
741
+ output: result ? truncate(result.content, MAX_OUTPUT_LEN) : void 0,
742
+ error: (result == null ? void 0 : result.isError) ? truncate(result.content, MAX_OUTPUT_LEN) : void 0,
743
+ durationMs: void 0,
744
+ index: decisionIndex++
745
+ });
746
+ lastReasoning = void 0;
747
+ }
748
+ }
749
+ }
750
+ return decisions;
751
+ }
752
+ function extractDecisionsFromNodes(nodes) {
753
+ const nodeList = nodes instanceof Map ? [...nodes.values()] : Object.values(nodes);
754
+ const sorted = nodeList.filter((n) => n.type === "tool" || n.type === "action" || n.type === "decision").sort((a, b) => (a.startTime ?? 0) - (b.startTime ?? 0));
755
+ return sorted.map((node, index) => {
756
+ var _a, _b, _c;
757
+ const dur = node.endTime != null ? node.endTime - node.startTime : void 0;
758
+ const isFailed = node.status === "failed" || node.status === "error";
759
+ const isTimeout = node.status === "timeout" || node.status === "hung";
760
+ return {
761
+ action: node.name,
762
+ tool: node.name,
763
+ args: node.metadata,
764
+ outcome: isTimeout ? "timeout" : isFailed ? "failed" : node.status === "skipped" ? "skipped" : "ok",
765
+ output: truncate(String(((_a = node.state) == null ? void 0 : _a.result) ?? ((_b = node.state) == null ? void 0 : _b.summary) ?? ""), MAX_OUTPUT_LEN) || void 0,
766
+ error: isFailed ? truncate(String(((_c = node.state) == null ? void 0 : _c.error) ?? ""), MAX_OUTPUT_LEN) : void 0,
767
+ durationMs: dur,
768
+ index
769
+ };
770
+ });
771
+ }
772
+ function extractDecisionsFromLangChain(run) {
773
+ const decisions = [];
774
+ let index = 0;
775
+ function walk(r, parentName) {
776
+ if (r.run_type === "tool" || r.run_type === "retriever") {
777
+ decisions.push({
778
+ action: r.name,
779
+ reasoning: parentName ? `Called by ${parentName}` : void 0,
780
+ tool: r.name,
781
+ args: r.inputs,
782
+ outcome: r.status === "error" ? "failed" : "ok",
783
+ output: r.outputs ? truncate(JSON.stringify(r.outputs), MAX_OUTPUT_LEN) : void 0,
784
+ error: r.error ? truncate(r.error, MAX_OUTPUT_LEN) : void 0,
785
+ durationMs: r.end_time - r.start_time,
786
+ index: index++
787
+ });
788
+ }
789
+ for (const child of r.child_runs ?? []) {
790
+ walk(child, r.name);
791
+ }
792
+ }
793
+ walk(run);
794
+ return decisions;
795
+ }
796
+ function computePatternSignature(decisions) {
797
+ if (decisions.length === 0) return "";
798
+ const actions = decisions.map(
799
+ (d) => d.action.toLowerCase().replace(/[-_]\d+$/g, "").replace(/\s+/g, "_")
800
+ );
801
+ const collapsed = [];
802
+ let current = actions[0];
803
+ let count = 1;
804
+ for (let i = 1; i < actions.length; i++) {
805
+ if (actions[i] === current) {
806
+ count++;
807
+ } else {
808
+ collapsed.push(count > 1 ? `${current}[${count}]` : current);
809
+ current = actions[i];
810
+ count = 1;
811
+ }
812
+ }
813
+ collapsed.push(count > 1 ? `${current}[${count}]` : current);
814
+ return collapsed.join("\u2192");
815
+ }
816
+ function computeToolPatternSignature(decisions) {
817
+ if (decisions.length === 0) return "";
818
+ const actions = decisions.map((d) => {
819
+ const action = d.action.toLowerCase().replace(/[-_]\d+$/g, "").replace(/\s+/g, "_");
820
+ const tool = d.tool ? d.tool.toLowerCase().replace(/[-_]\d+$/g, "") : "";
821
+ return tool && tool !== action ? `${action}:${tool}` : action;
822
+ });
823
+ const collapsed = [];
824
+ let current = actions[0];
825
+ let count = 1;
826
+ for (let i = 1; i < actions.length; i++) {
827
+ if (actions[i] === current) {
828
+ count++;
829
+ } else {
830
+ collapsed.push(count > 1 ? `${current}[${count}]` : current);
831
+ current = actions[i];
832
+ count = 1;
833
+ }
834
+ }
835
+ collapsed.push(count > 1 ? `${current}[${count}]` : current);
836
+ return collapsed.join("\u2192");
837
+ }
838
+ function trackConformanceTrend(history, newEntry) {
839
+ return [
840
+ ...history,
841
+ {
842
+ agentId: newEntry.agentId,
843
+ timestamp: Date.now(),
844
+ score: newEntry.score,
845
+ runId: newEntry.runId
846
+ }
847
+ ];
848
+ }
849
+ function detectDrift(history, options) {
850
+ const windowSize = (options == null ? void 0 : options.windowSize) ?? 50;
851
+ const window = history.slice(-windowSize);
852
+ const n = window.length;
853
+ if (n < 10) {
854
+ return { status: "insufficient_data", slope: 0, r2: 0, windowSize, dataPoints: n };
855
+ }
856
+ let sumX = 0;
857
+ let sumY = 0;
858
+ let sumXY = 0;
859
+ let sumX2 = 0;
860
+ for (let i = 0; i < n; i++) {
861
+ const y = window[i].score;
862
+ sumX += i;
863
+ sumY += y;
864
+ sumXY += i * y;
865
+ sumX2 += i * i;
866
+ }
867
+ const denominator = n * sumX2 - sumX * sumX;
868
+ const slope = denominator !== 0 ? (n * sumXY - sumX * sumY) / denominator : 0;
869
+ const intercept = (sumY - slope * sumX) / n;
870
+ const meanY = sumY / n;
871
+ let ssRes = 0;
872
+ let ssTot = 0;
873
+ for (let i = 0; i < n; i++) {
874
+ const y = window[i].score;
875
+ const yHat = intercept + slope * i;
876
+ ssRes += (y - yHat) ** 2;
877
+ ssTot += (y - meanY) ** 2;
878
+ }
879
+ const r2 = ssTot > 0 ? 1 - ssRes / ssTot : 0;
880
+ let status = "stable";
881
+ if (r2 > 0.3) {
882
+ status = slope < 0 ? "degrading" : "improving";
883
+ }
884
+ const currentScore = window[n - 1].score;
885
+ const agentId = window[n - 1].agentId;
886
+ const alert = status === "degrading" ? {
887
+ type: "conformance_trend_degradation",
888
+ agentId,
889
+ currentScore,
890
+ trendSlope: slope,
891
+ windowSize: n,
892
+ message: `Agent '${agentId}' conformance declining (slope: ${slope.toFixed(4)}/run, R\xB2: ${r2.toFixed(2)}, current: ${(currentScore * 100).toFixed(0)}%)`
893
+ } : void 0;
894
+ return { status, slope, r2, windowSize: n, dataPoints: n, alert };
895
+ }
896
+ async function evaluateAssertions(assertions, nodeId, timestamp) {
897
+ const now = timestamp ?? Date.now();
898
+ const violations = [];
899
+ for (const assertion of assertions) {
900
+ const timeoutMs = assertion.timeout ?? 5e3;
901
+ try {
902
+ const result = await Promise.race([
903
+ Promise.resolve(assertion.verify()),
904
+ new Promise((resolve) => setTimeout(() => resolve("timeout"), timeoutMs))
905
+ ]);
906
+ if (result === "timeout" || result === false) {
907
+ const explanation = {
908
+ rule: assertion.name,
909
+ threshold: "pass",
910
+ actual: result === "timeout" ? "timeout" : "fail",
911
+ source: "assertion"
912
+ };
913
+ violations.push({
914
+ type: "outcome_mismatch",
915
+ nodeId,
916
+ message: `Assertion '${assertion.name}' ${result === "timeout" ? "timed out" : "failed"}. Source: assertion.`,
917
+ timestamp: now,
918
+ explanation
919
+ });
920
+ }
921
+ } catch {
922
+ const explanation = {
923
+ rule: assertion.name,
924
+ threshold: "pass",
925
+ actual: "error",
926
+ source: "assertion"
927
+ };
928
+ violations.push({
929
+ type: "outcome_mismatch",
930
+ nodeId,
931
+ message: `Assertion '${assertion.name}' threw an error. Source: assertion.`,
932
+ timestamp: now,
933
+ explanation
934
+ });
935
+ }
936
+ }
937
+ return violations;
938
+ }
939
+ function createGuardedBuilder(builder, config) {
940
+ const guarded = withGuards(builder, config);
941
+ const logger = (config == null ? void 0 : config.logger) ?? ((msg) => console.warn(`[SOMA Guard] ${msg}`));
942
+ const onViolation = (config == null ? void 0 : config.onViolation) ?? "warn";
943
+ function handleViolations(violations) {
944
+ for (const violation of violations) {
945
+ const message = `Guard violation: ${violation.message}`;
946
+ switch (onViolation) {
947
+ case "warn":
948
+ logger(message);
949
+ break;
950
+ case "error":
951
+ logger(message);
952
+ break;
953
+ case "abort":
954
+ throw new Error(`AgentFlow guard violation: ${violation.message}`);
955
+ }
956
+ }
957
+ }
958
+ return {
959
+ ...guarded,
960
+ async endNodeWithAssertions(nodeId, status, assertions) {
961
+ guarded.endNode(nodeId, status);
962
+ const assertionViolations = await evaluateAssertions(assertions, nodeId);
963
+ handleViolations(assertionViolations);
964
+ }
965
+ };
966
+ }
967
+ function extractTokenCost(node) {
968
+ var _a, _b;
969
+ const semantic = (_a = node.metadata) == null ? void 0 : _a.semantic;
970
+ if ((semantic == null ? void 0 : semantic.tokenCost) != null && typeof semantic.tokenCost === "number") {
971
+ return semantic.tokenCost;
972
+ }
973
+ if (((_b = node.state) == null ? void 0 : _b.tokenCost) != null) {
974
+ return node.state.tokenCost;
975
+ }
976
+ return null;
977
+ }
978
+ function median(sorted) {
979
+ if (sorted.length === 0) return 0;
980
+ const mid = Math.floor(sorted.length / 2);
981
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
982
+ }
983
+ function percentile(sorted, p) {
984
+ if (sorted.length === 0) return 0;
985
+ const idx = Math.ceil(p / 100 * sorted.length) - 1;
986
+ return sorted[Math.max(0, idx)];
987
+ }
988
+ function getEfficiency(graphs) {
989
+ const allNodeCosts = [];
990
+ const runs = [];
991
+ let totalNodesWithCost = 0;
992
+ let totalNodes = 0;
993
+ for (const graph of graphs) {
994
+ let graphTokenCost = 0;
995
+ let completedNodes = 0;
996
+ for (const node of graph.nodes.values()) {
997
+ totalNodes++;
998
+ const cost = extractTokenCost(node);
999
+ if (cost !== null) totalNodesWithCost++;
1000
+ const duration = node.startTime && node.endTime != null ? node.endTime - node.startTime : null;
1001
+ allNodeCosts.push({
1002
+ nodeId: node.id,
1003
+ name: node.name,
1004
+ type: node.type,
1005
+ tokenCost: cost,
1006
+ durationMs: duration
1007
+ });
1008
+ if (node.status === "completed") completedNodes++;
1009
+ if (cost !== null) graphTokenCost += cost;
1010
+ }
1011
+ const costPerNode = completedNodes > 0 ? graphTokenCost / completedNodes : 0;
1012
+ runs.push({
1013
+ graphId: graph.id,
1014
+ agentId: graph.agentId,
1015
+ totalTokenCost: graphTokenCost,
1016
+ completedNodes,
1017
+ costPerNode
1018
+ });
1019
+ }
1020
+ const costPerNodeValues = runs.map((r) => r.costPerNode).filter((v) => v > 0).sort((a, b) => a - b);
1021
+ const mean = costPerNodeValues.length > 0 ? costPerNodeValues.reduce((a, b) => a + b, 0) / costPerNodeValues.length : 0;
1022
+ const aggregate = {
1023
+ mean,
1024
+ median: median(costPerNodeValues),
1025
+ p95: percentile(costPerNodeValues, 95)
1026
+ };
1027
+ const flags = [];
1028
+ const medianCostPerNode = aggregate.median;
1029
+ for (let gi = 0; gi < graphs.length; gi++) {
1030
+ const graph = graphs[gi];
1031
+ const run = runs[gi];
1032
+ const nameCounts = /* @__PURE__ */ new Map();
1033
+ const nameCost = /* @__PURE__ */ new Map();
1034
+ for (const node of graph.nodes.values()) {
1035
+ nameCounts.set(node.name, (nameCounts.get(node.name) ?? 0) + 1);
1036
+ const cost = extractTokenCost(node);
1037
+ if (cost !== null) {
1038
+ nameCost.set(node.name, (nameCost.get(node.name) ?? 0) + cost);
1039
+ }
1040
+ }
1041
+ if (medianCostPerNode > 0 && run.costPerNode > 3 * medianCostPerNode) {
1042
+ for (const [name, count] of nameCounts) {
1043
+ if (count > 1) {
1044
+ flags.push({
1045
+ pattern: "wasteful_retry",
1046
+ nodeName: name,
1047
+ retryCount: count,
1048
+ tokenCost: nameCost.get(name) ?? 0,
1049
+ message: `Node "${name}" appears ${count} times with cost-per-node ${run.costPerNode.toFixed(1)} (>3x median ${medianCostPerNode.toFixed(1)})`
1050
+ });
1051
+ }
1052
+ }
1053
+ }
1054
+ }
1055
+ const dataCoverage = totalNodes > 0 ? totalNodesWithCost / totalNodes : 0;
1056
+ return { runs, aggregate, flags, nodeCosts: allNodeCosts, dataCoverage };
1057
+ }
1058
+ function getModelAwareSignature(graph, dimensions) {
1059
+ var _a, _b;
1060
+ const parts = [];
1061
+ if (dimensions.includes("path")) {
1062
+ parts.push(getPathSignature(graph));
1063
+ }
1064
+ if (dimensions.includes("modelId")) {
1065
+ const models = /* @__PURE__ */ new Set();
1066
+ for (const node of graph.nodes.values()) {
1067
+ const semantic = (_a = node.metadata) == null ? void 0 : _a.semantic;
1068
+ const modelId = (semantic == null ? void 0 : semantic.modelId) ?? ((_b = node.state) == null ? void 0 : _b.modelId);
1069
+ models.add(modelId ?? "unattributed");
1070
+ }
1071
+ parts.push(`model:${[...models].sort().join("+")}`);
1072
+ }
1073
+ if (dimensions.includes("status")) {
1074
+ parts.push(`status:${graph.status}`);
1075
+ }
1076
+ return parts.join("|");
1077
+ }
1078
+ function findVariantsWithModel(graphs, options) {
1079
+ if (graphs.length === 0) return [];
1080
+ const dimensions = (options == null ? void 0 : options.dimensions) ?? ["path"];
1081
+ const groups = /* @__PURE__ */ new Map();
1082
+ for (const graph of graphs) {
1083
+ const sig = getModelAwareSignature(graph, dimensions);
1084
+ const group = groups.get(sig) ?? [];
1085
+ group.push(graph);
1086
+ groups.set(sig, group);
1087
+ }
1088
+ const total = graphs.length;
1089
+ const variants = [];
1090
+ for (const [pathSignature, groupGraphs] of groups) {
1091
+ variants.push({
1092
+ pathSignature,
1093
+ count: groupGraphs.length,
1094
+ percentage: groupGraphs.length / total * 100,
1095
+ graphIds: groupGraphs.map((g) => g.id),
1096
+ exampleGraph: groupGraphs[0]
1097
+ });
1098
+ }
1099
+ variants.sort((a, b) => {
1100
+ const freqDiff = b.count - a.count;
1101
+ if (freqDiff !== 0) return freqDiff;
1102
+ return a.pathSignature.localeCompare(b.pathSignature);
1103
+ });
1104
+ return variants;
1105
+ }
1106
+ function getDecisionReplayData(options) {
1107
+ const { trace, vault: vaultDir = ".soma/vault" } = options;
1108
+ if (!trace) {
1109
+ return { error: "Missing trace file path" };
1110
+ }
1111
+ const searchPaths = [trace, `${vaultDir}/../traces/${trace}`];
1112
+ let content = null;
1113
+ let isJsonl = false;
1114
+ for (const p of searchPaths) {
1115
+ if (existsSync2(p)) {
1116
+ content = readFileSync2(p, "utf-8");
1117
+ isJsonl = p.endsWith(".jsonl");
1118
+ break;
1119
+ }
1120
+ }
1121
+ if (!content) {
1122
+ return { error: `Trace not found: ${trace}` };
1123
+ }
1124
+ let decisions;
1125
+ if (isJsonl) {
1126
+ const events = content.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
1127
+ decisions = extractDecisionsFromSession(events);
1128
+ } else {
1129
+ try {
1130
+ const parsed = JSON.parse(content);
1131
+ const nodes = parsed.nodes ?? parsed;
1132
+ decisions = extractDecisionsFromNodes(
1133
+ nodes
1134
+ );
1135
+ } catch {
1136
+ return { error: `Failed to parse trace file: ${trace}` };
1137
+ }
1138
+ }
1139
+ const okCount = decisions.filter((d) => d.outcome === "ok").length;
1140
+ const failedCount = decisions.filter((d) => d.outcome === "failed").length;
1141
+ return {
1142
+ decisions,
1143
+ total: decisions.length,
1144
+ pattern: computePatternSignature(decisions),
1145
+ okCount,
1146
+ failedCount,
1147
+ skippedCount: decisions.length - okCount - failedCount
1148
+ };
1149
+ }
1150
+ function getAgentBriefingData(options) {
1151
+ const { agent: agentId, vault: vaultDir = ".soma/vault" } = options;
1152
+ if (!agentId) {
1153
+ return { error: "Missing agent ID" };
1154
+ }
1155
+ const vault = createVault({ baseDir: vaultDir });
1156
+ const agents = vault.list("agent");
1157
+ const agent = agents.find((a) => a.name === agentId || a.agentId === agentId);
1158
+ if (!agent) {
1159
+ return {
1160
+ error: `Agent not found: ${agentId}`,
1161
+ available: agents.map((a) => a.name)
1162
+ };
1163
+ }
1164
+ const data = agent;
1165
+ const totalExecutions = data.totalExecutions ?? 0;
1166
+ const failureRate = data.failureRate ?? 0;
1167
+ const failureCount = data.failureCount ?? 0;
1168
+ const status = failureRate > 0.5 ? "CRITICAL" : failureRate > 0.1 ? "DEGRADED" : "HEALTHY";
1169
+ const entityTypes = ["decision", "insight", "constraint", "contradiction", "policy", "content"];
1170
+ const intelligence = [];
1171
+ for (const etype of entityTypes) {
1172
+ for (const e of vault.list(etype)) {
1173
+ const eData = e;
1174
+ const body = e.body + " " + (eData.claim ?? "") + " " + e.related.join(" ");
1175
+ if (body.includes(agentId) || body.includes(agent.name)) {
1176
+ intelligence.push({
1177
+ type: etype,
1178
+ name: e.name,
1179
+ claim: String(eData.claim ?? "").slice(0, 100)
1180
+ });
1181
+ }
1182
+ }
1183
+ }
1184
+ const peers = agents.map((a) => ({
1185
+ name: a.name,
1186
+ failureRate: a.failureRate ?? 0,
1187
+ totalExecutions: a.totalExecutions ?? 0
1188
+ })).filter((a) => a.totalExecutions > 0).sort((a, b) => a.failureRate - b.failureRate);
1189
+ return {
1190
+ agentId,
1191
+ status,
1192
+ failureRate,
1193
+ failureCount,
1194
+ totalExecutions,
1195
+ intelligence,
1196
+ peers
1197
+ };
1198
+ }
1199
+
1200
+ export {
1201
+ parseEntity,
1202
+ serializeEntity,
1203
+ extractWikilinks,
1204
+ vaultFingerprint,
1205
+ vaultEntityCount,
1206
+ createVault,
1207
+ extractDecisionsFromSession,
1208
+ extractDecisionsFromNodes,
1209
+ extractDecisionsFromLangChain,
1210
+ computePatternSignature,
1211
+ computeToolPatternSignature,
1212
+ trackConformanceTrend,
1213
+ detectDrift,
1214
+ evaluateAssertions,
1215
+ createGuardedBuilder,
1216
+ getEfficiency,
1217
+ findVariantsWithModel,
1218
+ getDecisionReplayData,
1219
+ getAgentBriefingData
1220
+ };