@swestash/agent-lens 0.2.0 → 0.3.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.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync5 } from "fs";
5
- import { dirname as dirname6, join as join9 } from "path";
4
+ import { readFileSync as readFileSync8 } from "fs";
5
+ import { dirname as dirname6, join as join12 } from "path";
6
6
  import { fileURLToPath as fileURLToPath3 } from "url";
7
7
 
8
8
  // ../../node_modules/.pnpm/cac@6.7.14/node_modules/cac/dist/index.mjs
@@ -591,7 +591,7 @@ var CAC = class extends EventEmitter {
591
591
  var cac = (name = "") => new CAC(name);
592
592
 
593
593
  // ../core/dist/schema.js
594
- var SCHEMA_VERSION = 8;
594
+ var SCHEMA_VERSION = 11;
595
595
  var SCHEMA_SQL = (
596
596
  /* sql */
597
597
  `
@@ -781,6 +781,61 @@ CREATE TABLE IF NOT EXISTS classifications (
781
781
  PRIMARY KEY (scope, target_id)
782
782
  );
783
783
 
784
+ -- Workflow-tool run results, ingested from the sidecar JSON the runner writes next to the launching
785
+ -- session (\u2026/projects/<enc>/<sessionId>/workflows/wf_<id>.json). The transcript only carries the
786
+ -- "launched in background" ack for an async run; this sidecar is the authoritative record of how the
787
+ -- run finished \u2014 status (completed/failed), summary, the returned result payload, and roll-up
788
+ -- tokens/tool-calls/duration/agent-count. One row per run id; UPSERT on re-ingest. Stored verbatim
789
+ -- (like tool_calls.input_json) \u2014 the DB is local; redaction is an export-time concern. --------------
790
+ CREATE TABLE IF NOT EXISTS workflow_results (
791
+ run_id TEXT PRIMARY KEY, -- wf_<id> (matches tool_calls.workflow_run_id / sessions.workflow_run_id)
792
+ source_id TEXT, -- which configured source (account) it came from
793
+ session_id TEXT, -- launching session (path segment before /workflows/)
794
+ task_id TEXT, -- async task id (the "Task ID: \u2026" in the launch ack)
795
+ workflow_name TEXT,
796
+ status TEXT, -- completed | failed | running | ...
797
+ summary TEXT,
798
+ default_model TEXT, -- model the run executed on (e.g. claude-fable-5)
799
+ result_json TEXT, -- the returned result object, verbatim JSON (null when none)
800
+ phases_json TEXT, -- [{title}] the run's phase structure (e.g. Generate \u2192 Judge)
801
+ logs_json TEXT, -- concise per-item outcome lines the runner emitted
802
+ progress_json TEXT, -- workflowProgress: the phase/agent event timeline
803
+ agent_count INTEGER, -- agents the run reports having fanned out to
804
+ total_tokens INTEGER,
805
+ total_tool_calls INTEGER,
806
+ duration_ms INTEGER,
807
+ started_at TEXT, -- ISO, from the sidecar startTime/timestamp
808
+ ended_at TEXT, -- ISO, started_at + duration
809
+ ingested_at TEXT
810
+ );
811
+ CREATE INDEX IF NOT EXISTS idx_workflow_results_session ON workflow_results(session_id);
812
+
813
+ -- Per-subagent metadata sidecar (subagents/agent-<id>.meta.json). One row per subagent session,
814
+ -- joined at read time onto sessions (LEFT JOIN, may precede the session row's arrival). Supplies the
815
+ -- authoritative agentType/description/spawnDepth \u2014 Workflow fan-out subagents carry none in-transcript.
816
+ CREATE TABLE IF NOT EXISTS session_meta (
817
+ session_id TEXT PRIMARY KEY, -- 'agent-<id>' = the subagent session id (meta filename stem)
818
+ source_id TEXT,
819
+ agent_type TEXT, -- meta.agentType (e.g. 'Explore', 'claude-code-guide')
820
+ agent_description TEXT, -- meta.description (a real human title)
821
+ spawn_depth INTEGER, -- meta.spawnDepth (optional; absent \u21D2 top-level)
822
+ tool_use_id TEXT, -- meta.toolUseId (the spawning tool_call)
823
+ ingested_at TEXT
824
+ );
825
+
826
+ -- Full un-truncated tool outputs spilled to disk (tool-results/<name>.txt) when a result was too large
827
+ -- for the transcript, which keeps only a 280-char summary naming the file. Joined at read time by
828
+ -- parsing that summary's '\u2026/tool-results/<name>.txt' marker back to (session_id, name).
829
+ CREATE TABLE IF NOT EXISTS tool_results (
830
+ session_id TEXT, -- session the tool ran in (from the archive path)
831
+ name TEXT, -- file basename without .txt (matches the marker in tool_calls.result_summary)
832
+ path TEXT, -- archive path (provenance)
833
+ bytes INTEGER, -- original size, for the UI
834
+ text TEXT, -- full un-truncated output, stored inline (self-contained DB)
835
+ ingested_at TEXT,
836
+ PRIMARY KEY (session_id, name)
837
+ );
838
+
784
839
  -- Idempotent ingest bookkeeping per archive file. --------------------------
785
840
  CREATE TABLE IF NOT EXISTS ingest_state (
786
841
  file_path TEXT PRIMARY KEY,
@@ -1148,16 +1203,17 @@ function snapshot(absPath, relParts, versionsDir, stats) {
1148
1203
  syncMeta(dst, statSync(absPath));
1149
1204
  stats.snapshots++;
1150
1205
  }
1151
- function walkJsonl(dir, out) {
1206
+ function walkCollectable(dir, out, inWorkflows = false, inToolResults = false) {
1152
1207
  for (const e of readdirSync(dir, { withFileTypes: true })) {
1153
1208
  const p = join2(dir, e.name);
1154
- if (e.isDirectory())
1155
- walkJsonl(p, out);
1156
- else if (e.isFile() && e.name.endsWith(".jsonl"))
1209
+ if (e.isDirectory()) {
1210
+ walkCollectable(p, out, inWorkflows || e.name === "workflows", inToolResults || e.name === "tool-results");
1211
+ } else if (e.isFile() && (e.name.endsWith(".jsonl") || inWorkflows || inToolResults || e.name.endsWith(".meta.json"))) {
1157
1212
  out.push(p);
1213
+ }
1158
1214
  }
1159
1215
  }
1160
- function syncFile(src, dst, relParts, archive, versionsDir, stats) {
1216
+ function syncFile(src, dst, relParts, archive, versionsDir, stats, appendable) {
1161
1217
  const srcStat = statSync(src);
1162
1218
  if (!existsSync2(dst)) {
1163
1219
  copyWhole(src, dst, srcStat);
@@ -1169,11 +1225,11 @@ function syncFile(src, dst, relParts, archive, versionsDir, stats) {
1169
1225
  return;
1170
1226
  const ssize = srcStat.size;
1171
1227
  const asize = arcStat.size;
1172
- if (ssize >= asize && prefixEquals(src, dst, asize)) {
1173
- if (ssize > asize) {
1174
- appendRange(src, dst, asize, ssize);
1175
- stats.appended++;
1176
- }
1228
+ if (ssize === asize && prefixEquals(src, dst, asize)) {
1229
+ syncMeta(dst, srcStat);
1230
+ } else if (appendable && ssize > asize && prefixEquals(src, dst, asize)) {
1231
+ appendRange(src, dst, asize, ssize);
1232
+ stats.appended++;
1177
1233
  syncMeta(dst, srcStat);
1178
1234
  } else if (ssize < asize) {
1179
1235
  const last = latestSnapshot(archive, relParts);
@@ -1214,7 +1270,7 @@ function collectSource(source, archiveBase, excludedDirs, runTs, stats, log) {
1214
1270
  const files = [];
1215
1271
  const projectsDir = join2(configDir, "projects");
1216
1272
  if (existsSync2(projectsDir))
1217
- walkJsonl(projectsDir, files);
1273
+ walkCollectable(projectsDir, files);
1218
1274
  const historySrc = join2(configDir, "history.jsonl");
1219
1275
  if (existsSync2(historySrc))
1220
1276
  files.push(historySrc);
@@ -1228,7 +1284,7 @@ function collectSource(source, archiveBase, excludedDirs, runTs, stats, log) {
1228
1284
  if (excludedDirs.some((enc) => relPosix.startsWith(`projects/${enc}/`)))
1229
1285
  continue;
1230
1286
  stats.scanned++;
1231
- syncFile(src, join2(archive, ...relParts), relParts, archive, versionsDir, stats);
1287
+ syncFile(src, join2(archive, ...relParts), relParts, archive, versionsDir, stats, base.endsWith(".jsonl"));
1232
1288
  }
1233
1289
  for (const f of ["settings.json", "settings.local.json"]) {
1234
1290
  const s = join2(configDir, f);
@@ -1627,8 +1683,8 @@ function runService(action, params) {
1627
1683
  }
1628
1684
 
1629
1685
  // ../ingest/dist/run.js
1630
- import { readFileSync as readFileSync4, statSync as statSync2, mkdirSync as mkdirSync4, existsSync as existsSync4 } from "fs";
1631
- import { dirname as dirname4, join as join5 } from "path";
1686
+ import { readFileSync as readFileSync7, statSync as statSync5, mkdirSync as mkdirSync4, existsSync as existsSync4 } from "fs";
1687
+ import { dirname as dirname4, join as join8 } from "path";
1632
1688
 
1633
1689
  // ../ingest/dist/db.js
1634
1690
  import Database from "better-sqlite3";
@@ -1638,9 +1694,16 @@ function openRaw(file) {
1638
1694
  db.pragma("foreign_keys = ON");
1639
1695
  return db;
1640
1696
  }
1697
+ function readSchemaVersion(db) {
1698
+ const row = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
1699
+ if (!row)
1700
+ return null;
1701
+ const n = Number(row.value);
1702
+ return Number.isFinite(n) ? n : null;
1703
+ }
1641
1704
  function applySchema(db) {
1642
1705
  db.exec(SCHEMA_SQL);
1643
- db.prepare("INSERT INTO meta(key, value) VALUES ('schema_version', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(String(SCHEMA_VERSION));
1706
+ db.prepare("INSERT INTO meta(key, value) VALUES ('schema_version', ?) ON CONFLICT(key) DO NOTHING").run(String(SCHEMA_VERSION));
1644
1707
  }
1645
1708
  function openDb(file) {
1646
1709
  const db = openRaw(file);
@@ -1664,6 +1727,9 @@ function resetSchema(db) {
1664
1727
  DROP TABLE IF EXISTS projects;
1665
1728
  DROP TABLE IF EXISTS sources;
1666
1729
  DROP TABLE IF EXISTS agents;
1730
+ DROP TABLE IF EXISTS workflow_results;
1731
+ DROP TABLE IF EXISTS session_meta;
1732
+ DROP TABLE IF EXISTS tool_results;
1667
1733
  DROP TABLE IF EXISTS ingest_state;
1668
1734
  DROP TABLE IF EXISTS meta;
1669
1735
  `);
@@ -2608,6 +2674,10 @@ function ingestFile(db, stmts, adapter, file, lines, meta, now, stats) {
2608
2674
  tx();
2609
2675
  }
2610
2676
 
2677
+ // ../ingest/dist/workflows.js
2678
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
2679
+ import { basename as basename3, join as join5 } from "path";
2680
+
2611
2681
  // ../ingest/dist/redact.js
2612
2682
  import { createHash as createHash2, randomBytes } from "crypto";
2613
2683
  function parseExcludes(csv) {
@@ -2662,6 +2732,334 @@ function* streamLines(path) {
2662
2732
  }
2663
2733
  }
2664
2734
 
2735
+ // ../ingest/dist/workflows.js
2736
+ function newWorkflowStats() {
2737
+ return { files: 0, upserted: 0, skipped: 0, malformed: 0 };
2738
+ }
2739
+ function walkSidecars(dir, inWorkflows, out) {
2740
+ let entries;
2741
+ try {
2742
+ entries = readdirSync3(dir, { withFileTypes: true });
2743
+ } catch {
2744
+ return;
2745
+ }
2746
+ for (const e of entries) {
2747
+ const p = join5(dir, e.name);
2748
+ if (e.isDirectory())
2749
+ walkSidecars(p, inWorkflows || e.name === "workflows", out);
2750
+ else if (e.isFile() && inWorkflows && /^wf_.+\.json$/.test(e.name))
2751
+ out.push(p);
2752
+ }
2753
+ }
2754
+ function discoverSidecars(sourceArchiveDir) {
2755
+ const out = [];
2756
+ walkSidecars(join5(sourceArchiveDir, "projects"), false, out);
2757
+ try {
2758
+ for (const ts of readdirSync3(join5(sourceArchiveDir, ".versions"), { withFileTypes: true }))
2759
+ if (ts.isDirectory())
2760
+ walkSidecars(join5(sourceArchiveDir, ts.name, "projects"), false, out);
2761
+ } catch {
2762
+ }
2763
+ return out;
2764
+ }
2765
+ function sessionFromPath(path) {
2766
+ const parts = path.split("/");
2767
+ const i = parts.indexOf("workflows");
2768
+ return i > 0 ? parts[i - 1] : null;
2769
+ }
2770
+ function toIso(x) {
2771
+ if (typeof x === "number" && Number.isFinite(x))
2772
+ return new Date(x).toISOString();
2773
+ if (typeof x === "string" && x) {
2774
+ const t = Date.parse(x);
2775
+ return Number.isNaN(t) ? null : new Date(t).toISOString();
2776
+ }
2777
+ return null;
2778
+ }
2779
+ var jsonOrNull = (v) => v == null ? null : JSON.stringify(v);
2780
+ var intOrNull = (v) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : null;
2781
+ var strOrNull = (v) => typeof v === "string" && v ? v : null;
2782
+ function cleanStr(v) {
2783
+ const s = strOrNull(v);
2784
+ if (!s)
2785
+ return null;
2786
+ return s.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "").replace(/\[[0-9;]+m\]?/g, "").trim() || null;
2787
+ }
2788
+ function ingestWorkflowResults(db, sourceArchiveDir, sourceId, excludedDirs, now, stats, full) {
2789
+ let paths = discoverSidecars(sourceArchiveDir);
2790
+ if (excludedDirs.length)
2791
+ paths = paths.filter((p) => !isExcludedArchivePath(p, excludedDirs));
2792
+ paths.sort((a, b) => Number(a.includes("/.versions/")) - Number(b.includes("/.versions/")) || (a < b ? -1 : 1));
2793
+ const getState = db.prepare("SELECT size, mtime_ms, sha256 FROM ingest_state WHERE file_path = ?");
2794
+ const setState = db.prepare(`INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)
2795
+ VALUES (@file_path, @size, @mtime_ms, @sha256, 0, @ingested_at)
2796
+ ON CONFLICT(file_path) DO UPDATE SET size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256, ingested_at=excluded.ingested_at`);
2797
+ const upsert = db.prepare(`INSERT INTO workflow_results
2798
+ (run_id, source_id, session_id, task_id, workflow_name, status, summary, default_model,
2799
+ result_json, phases_json, logs_json, progress_json,
2800
+ agent_count, total_tokens, total_tool_calls, duration_ms, started_at, ended_at, ingested_at)
2801
+ VALUES
2802
+ (@run_id, @source_id, @session_id, @task_id, @workflow_name, @status, @summary, @default_model,
2803
+ @result_json, @phases_json, @logs_json, @progress_json,
2804
+ @agent_count, @total_tokens, @total_tool_calls, @duration_ms, @started_at, @ended_at, @ingested_at)
2805
+ ON CONFLICT(run_id) DO UPDATE SET
2806
+ source_id=excluded.source_id, session_id=excluded.session_id, task_id=excluded.task_id,
2807
+ workflow_name=excluded.workflow_name, status=excluded.status, summary=excluded.summary,
2808
+ default_model=excluded.default_model, result_json=excluded.result_json, phases_json=excluded.phases_json,
2809
+ logs_json=excluded.logs_json, progress_json=excluded.progress_json, agent_count=excluded.agent_count,
2810
+ total_tokens=excluded.total_tokens, total_tool_calls=excluded.total_tool_calls,
2811
+ duration_ms=excluded.duration_ms, started_at=excluded.started_at, ended_at=excluded.ended_at,
2812
+ ingested_at=excluded.ingested_at`);
2813
+ for (const path of paths) {
2814
+ stats.files++;
2815
+ const st = statSync2(path);
2816
+ const mtimeMs = Math.trunc(st.mtimeMs);
2817
+ const prev = full ? void 0 : getState.get(path);
2818
+ if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {
2819
+ stats.skipped++;
2820
+ continue;
2821
+ }
2822
+ let buf;
2823
+ try {
2824
+ buf = readFileSync4(path);
2825
+ } catch {
2826
+ stats.malformed++;
2827
+ continue;
2828
+ }
2829
+ const hash = sha256(buf);
2830
+ if (prev && prev.sha256 === hash) {
2831
+ setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });
2832
+ stats.skipped++;
2833
+ continue;
2834
+ }
2835
+ let d;
2836
+ try {
2837
+ d = JSON.parse(buf.toString("utf8"));
2838
+ } catch {
2839
+ stats.malformed++;
2840
+ continue;
2841
+ }
2842
+ if (!d || typeof d !== "object") {
2843
+ stats.malformed++;
2844
+ continue;
2845
+ }
2846
+ const runId = strOrNull(d.runId) ?? basename3(path, ".json");
2847
+ const startedAt = toIso(d.startTime) ?? toIso(d.timestamp);
2848
+ const durationMs2 = intOrNull(d.durationMs);
2849
+ const endedAt = startedAt && durationMs2 != null ? new Date(Date.parse(startedAt) + durationMs2).toISOString() : null;
2850
+ upsert.run({
2851
+ run_id: runId,
2852
+ source_id: sourceId,
2853
+ session_id: sessionFromPath(path),
2854
+ task_id: strOrNull(d.taskId),
2855
+ workflow_name: strOrNull(d.workflowName),
2856
+ status: strOrNull(d.status),
2857
+ summary: strOrNull(d.summary),
2858
+ default_model: cleanStr(d.defaultModel),
2859
+ result_json: jsonOrNull(d.result),
2860
+ phases_json: Array.isArray(d.phases) ? JSON.stringify(d.phases) : null,
2861
+ logs_json: Array.isArray(d.logs) ? JSON.stringify(d.logs) : null,
2862
+ progress_json: Array.isArray(d.workflowProgress) ? JSON.stringify(d.workflowProgress) : null,
2863
+ agent_count: intOrNull(d.agentCount),
2864
+ total_tokens: intOrNull(d.totalTokens),
2865
+ total_tool_calls: intOrNull(d.totalToolCalls),
2866
+ duration_ms: durationMs2,
2867
+ started_at: startedAt,
2868
+ ended_at: endedAt,
2869
+ ingested_at: now
2870
+ });
2871
+ setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });
2872
+ stats.upserted++;
2873
+ }
2874
+ }
2875
+
2876
+ // ../ingest/dist/meta.js
2877
+ import { readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
2878
+ import { basename as basename4, join as join6 } from "path";
2879
+ function newMetaStats() {
2880
+ return { files: 0, upserted: 0, skipped: 0, malformed: 0 };
2881
+ }
2882
+ function walkMeta(dir, inSubagents, out) {
2883
+ let entries;
2884
+ try {
2885
+ entries = readdirSync4(dir, { withFileTypes: true });
2886
+ } catch {
2887
+ return;
2888
+ }
2889
+ for (const e of entries) {
2890
+ const p = join6(dir, e.name);
2891
+ if (e.isDirectory())
2892
+ walkMeta(p, inSubagents || e.name === "subagents", out);
2893
+ else if (e.isFile() && inSubagents && /^agent-.+\.meta\.json$/.test(e.name))
2894
+ out.push(p);
2895
+ }
2896
+ }
2897
+ function discoverMeta(sourceArchiveDir) {
2898
+ const out = [];
2899
+ walkMeta(join6(sourceArchiveDir, "projects"), false, out);
2900
+ try {
2901
+ for (const ts of readdirSync4(join6(sourceArchiveDir, ".versions"), { withFileTypes: true }))
2902
+ if (ts.isDirectory())
2903
+ walkMeta(join6(sourceArchiveDir, ts.name, "projects"), false, out);
2904
+ } catch {
2905
+ }
2906
+ return out;
2907
+ }
2908
+ var intOrNull2 = (v) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : null;
2909
+ var strOrNull2 = (v) => typeof v === "string" && v ? v : null;
2910
+ function ingestSubagentMeta(db, sourceArchiveDir, sourceId, excludedDirs, now, stats, full) {
2911
+ let paths = discoverMeta(sourceArchiveDir);
2912
+ if (excludedDirs.length)
2913
+ paths = paths.filter((p) => !isExcludedArchivePath(p, excludedDirs));
2914
+ paths.sort((a, b) => Number(a.includes("/.versions/")) - Number(b.includes("/.versions/")) || (a < b ? -1 : 1));
2915
+ const getState = db.prepare("SELECT size, mtime_ms, sha256 FROM ingest_state WHERE file_path = ?");
2916
+ const setState = db.prepare(`INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)
2917
+ VALUES (@file_path, @size, @mtime_ms, @sha256, 0, @ingested_at)
2918
+ ON CONFLICT(file_path) DO UPDATE SET size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256, ingested_at=excluded.ingested_at`);
2919
+ const upsert = db.prepare(`INSERT INTO session_meta
2920
+ (session_id, source_id, agent_type, agent_description, spawn_depth, tool_use_id, ingested_at)
2921
+ VALUES
2922
+ (@session_id, @source_id, @agent_type, @agent_description, @spawn_depth, @tool_use_id, @ingested_at)
2923
+ ON CONFLICT(session_id) DO UPDATE SET
2924
+ source_id=excluded.source_id, agent_type=excluded.agent_type,
2925
+ agent_description=excluded.agent_description, spawn_depth=excluded.spawn_depth,
2926
+ tool_use_id=excluded.tool_use_id, ingested_at=excluded.ingested_at`);
2927
+ for (const path of paths) {
2928
+ stats.files++;
2929
+ const st = statSync3(path);
2930
+ const mtimeMs = Math.trunc(st.mtimeMs);
2931
+ const prev = full ? void 0 : getState.get(path);
2932
+ if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {
2933
+ stats.skipped++;
2934
+ continue;
2935
+ }
2936
+ let buf;
2937
+ try {
2938
+ buf = readFileSync5(path);
2939
+ } catch {
2940
+ stats.malformed++;
2941
+ continue;
2942
+ }
2943
+ const hash = sha256(buf);
2944
+ if (prev && prev.sha256 === hash) {
2945
+ setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });
2946
+ stats.skipped++;
2947
+ continue;
2948
+ }
2949
+ let d;
2950
+ try {
2951
+ d = JSON.parse(buf.toString("utf8"));
2952
+ } catch {
2953
+ stats.malformed++;
2954
+ continue;
2955
+ }
2956
+ if (!d || typeof d !== "object") {
2957
+ stats.malformed++;
2958
+ continue;
2959
+ }
2960
+ const sessionId = basename4(path, ".meta.json");
2961
+ upsert.run({
2962
+ session_id: sessionId,
2963
+ source_id: sourceId,
2964
+ agent_type: strOrNull2(d.agentType),
2965
+ agent_description: strOrNull2(d.description),
2966
+ spawn_depth: intOrNull2(d.spawnDepth),
2967
+ tool_use_id: strOrNull2(d.toolUseId),
2968
+ ingested_at: now
2969
+ });
2970
+ setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });
2971
+ stats.upserted++;
2972
+ }
2973
+ }
2974
+
2975
+ // ../ingest/dist/toolResults.js
2976
+ import { readdirSync as readdirSync5, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
2977
+ import { basename as basename5, join as join7 } from "path";
2978
+ function newToolResultStats() {
2979
+ return { files: 0, upserted: 0, skipped: 0, malformed: 0 };
2980
+ }
2981
+ function walkResults(dir, inResults, out) {
2982
+ let entries;
2983
+ try {
2984
+ entries = readdirSync5(dir, { withFileTypes: true });
2985
+ } catch {
2986
+ return;
2987
+ }
2988
+ for (const e of entries) {
2989
+ const p = join7(dir, e.name);
2990
+ if (e.isDirectory())
2991
+ walkResults(p, inResults || e.name === "tool-results", out);
2992
+ else if (e.isFile() && inResults && e.name.endsWith(".txt"))
2993
+ out.push(p);
2994
+ }
2995
+ }
2996
+ function discoverResults(sourceArchiveDir) {
2997
+ const out = [];
2998
+ walkResults(join7(sourceArchiveDir, "projects"), false, out);
2999
+ try {
3000
+ for (const ts of readdirSync5(join7(sourceArchiveDir, ".versions"), { withFileTypes: true }))
3001
+ if (ts.isDirectory())
3002
+ walkResults(join7(sourceArchiveDir, ts.name, "projects"), false, out);
3003
+ } catch {
3004
+ }
3005
+ return out;
3006
+ }
3007
+ function sessionFromPath2(path) {
3008
+ const parts = path.split("/");
3009
+ const i = parts.indexOf("tool-results");
3010
+ return i > 0 ? parts[i - 1] : null;
3011
+ }
3012
+ function ingestToolResults(db, sourceArchiveDir, excludedDirs, now, stats, full) {
3013
+ let paths = discoverResults(sourceArchiveDir);
3014
+ if (excludedDirs.length)
3015
+ paths = paths.filter((p) => !isExcludedArchivePath(p, excludedDirs));
3016
+ paths.sort((a, b) => Number(a.includes("/.versions/")) - Number(b.includes("/.versions/")) || (a < b ? -1 : 1));
3017
+ const getState = db.prepare("SELECT size, mtime_ms, sha256 FROM ingest_state WHERE file_path = ?");
3018
+ const setState = db.prepare(`INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)
3019
+ VALUES (@file_path, @size, @mtime_ms, @sha256, 0, @ingested_at)
3020
+ ON CONFLICT(file_path) DO UPDATE SET size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256, ingested_at=excluded.ingested_at`);
3021
+ const upsert = db.prepare(`INSERT INTO tool_results (session_id, name, path, bytes, text, ingested_at)
3022
+ VALUES (@session_id, @name, @path, @bytes, @text, @ingested_at)
3023
+ ON CONFLICT(session_id, name) DO UPDATE SET
3024
+ path=excluded.path, bytes=excluded.bytes, text=excluded.text, ingested_at=excluded.ingested_at`);
3025
+ for (const path of paths) {
3026
+ const sessionId = sessionFromPath2(path);
3027
+ if (!sessionId)
3028
+ continue;
3029
+ stats.files++;
3030
+ const st = statSync4(path);
3031
+ const mtimeMs = Math.trunc(st.mtimeMs);
3032
+ const prev = full ? void 0 : getState.get(path);
3033
+ if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {
3034
+ stats.skipped++;
3035
+ continue;
3036
+ }
3037
+ let buf;
3038
+ try {
3039
+ buf = readFileSync6(path);
3040
+ } catch {
3041
+ stats.malformed++;
3042
+ continue;
3043
+ }
3044
+ const hash = sha256(buf);
3045
+ if (prev && prev.sha256 === hash) {
3046
+ setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });
3047
+ stats.skipped++;
3048
+ continue;
3049
+ }
3050
+ upsert.run({
3051
+ session_id: sessionId,
3052
+ name: basename5(path, ".txt"),
3053
+ path,
3054
+ bytes: st.size,
3055
+ text: buf.toString("utf8"),
3056
+ ingested_at: now
3057
+ });
3058
+ setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });
3059
+ stats.upserted++;
3060
+ }
3061
+ }
3062
+
2665
3063
  // ../ingest/dist/run.js
2666
3064
  function parseIngestArgs(argv) {
2667
3065
  const a = { full: false, db: "", archive: "" };
@@ -2678,16 +3076,24 @@ function parseIngestArgs(argv) {
2678
3076
  function runIngest(argv = process.argv.slice(2)) {
2679
3077
  const args = parseIngestArgs(argv);
2680
3078
  const dataDir = resolveDataDir(findRepoRoot());
2681
- const archiveRoot = args.archive || process.env.AGENT_LENS_ARCHIVE || join5(dataDir, "archive");
2682
- const dbPath = args.db || process.env.AGENT_LENS_DB || join5(dataDir, "agent-lens.db");
3079
+ const archiveRoot = args.archive || process.env.AGENT_LENS_ARCHIVE || join8(dataDir, "archive");
3080
+ const dbPath = args.db || process.env.AGENT_LENS_DB || join8(dataDir, "agent-lens.db");
2683
3081
  if (!existsSync4(archiveRoot)) {
2684
3082
  console.error(`agent-lens-ingest: archive not found: ${archiveRoot} (run 'agent-lens collect' first)`);
2685
3083
  process.exit(1);
2686
3084
  }
2687
3085
  mkdirSync4(dirname4(dbPath), { recursive: true });
2688
3086
  const db = args.full ? openRaw(dbPath) : openDb(dbPath);
2689
- if (args.full)
3087
+ if (args.full) {
2690
3088
  resetSchema(db);
3089
+ } else {
3090
+ const dbVer = readSchemaVersion(db);
3091
+ if (dbVer != null && dbVer !== SCHEMA_VERSION) {
3092
+ console.error(`agent-lens-ingest: schema mismatch \u2014 DB is v${dbVer}, this build expects v${SCHEMA_VERSION}. Run 'agent-lens ingest --full' to rebuild.`);
3093
+ db.close();
3094
+ process.exit(1);
3095
+ }
3096
+ }
2691
3097
  const adapterList = [new ClaudeCodeAdapter()];
2692
3098
  const adapterById = new Map(adapterList.map((a) => [a.agentId, a]));
2693
3099
  const sources = loadSources();
@@ -2697,6 +3103,9 @@ function runIngest(argv = process.argv.slice(2)) {
2697
3103
  const pruned = pruneExcluded(db, excludePaths);
2698
3104
  const stmts = prepareStatements(db);
2699
3105
  const stats = newStats();
3106
+ const wfStats = newWorkflowStats();
3107
+ const metaStats = newMetaStats();
3108
+ const trStats = newToolResultStats();
2700
3109
  const dirty = /* @__PURE__ */ new Set();
2701
3110
  for (const source of sources) {
2702
3111
  const adapter = adapterById.get(source.agent);
@@ -2706,13 +3115,13 @@ function runIngest(argv = process.argv.slice(2)) {
2706
3115
  }
2707
3116
  stmts.insAgent.run(adapter.agentId, adapter.agentName);
2708
3117
  stmts.insSource.run({ id: source.label, label: source.label, agent_id: adapter.agentId, config_dir: source.configDir });
2709
- let files = adapter.discover(join5(archiveRoot, source.label), source.label);
3118
+ let files = adapter.discover(join8(archiveRoot, source.label), source.label);
2710
3119
  if (excludedDirs.length)
2711
3120
  files = files.filter((f) => !isExcludedArchivePath(f.path, excludedDirs));
2712
3121
  files.sort((a, b) => Number(a.isVersion) - Number(b.isVersion));
2713
3122
  for (const file of files) {
2714
3123
  stats.files++;
2715
- const st = statSync2(file.path);
3124
+ const st = statSync5(file.path);
2716
3125
  const mtimeMs = Math.trunc(st.mtimeMs);
2717
3126
  const prev = args.full ? void 0 : stmts.getState.get(file.path);
2718
3127
  if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {
@@ -2720,7 +3129,7 @@ function runIngest(argv = process.argv.slice(2)) {
2720
3129
  continue;
2721
3130
  }
2722
3131
  const small = st.size <= STREAM_THRESHOLD;
2723
- const buf = small ? readFileSync4(file.path) : null;
3132
+ const buf = small ? readFileSync7(file.path) : null;
2724
3133
  const hash = small ? sha256(buf) : sha256File(file.path);
2725
3134
  if (prev && prev.sha256 === hash) {
2726
3135
  stmts.setState.run({
@@ -2738,6 +3147,9 @@ function runIngest(argv = process.argv.slice(2)) {
2738
3147
  ingestFile(db, stmts, adapter, file, lines, { size: st.size, mtimeMs, hash }, now, stats);
2739
3148
  dirty.add(file.sessionId);
2740
3149
  }
3150
+ ingestWorkflowResults(db, join8(archiveRoot, source.label), source.label, excludedDirs, now, wfStats, args.full);
3151
+ ingestSubagentMeta(db, join8(archiveRoot, source.label), source.label, excludedDirs, now, metaStats, args.full);
3152
+ ingestToolResults(db, join8(archiveRoot, source.label), excludedDirs, now, trStats, args.full);
2741
3153
  }
2742
3154
  const expanded = rebuildDerived(db, args.full ? null : dirty);
2743
3155
  const classified = classify(db, args.full ? null : expanded);
@@ -2753,9 +3165,15 @@ function runIngest(argv = process.argv.slice(2)) {
2753
3165
  cost += costForUsage(u.model, u);
2754
3166
  totalTokens += u.input_tokens + u.output_tokens + u.cache_creation_input_tokens + u.cache_read_input_tokens;
2755
3167
  }
3168
+ const wfRuns = count("SELECT COUNT(*) n FROM workflow_results");
3169
+ const metaRows = count("SELECT COUNT(*) n FROM session_meta");
3170
+ const trRows = count("SELECT COUNT(*) n FROM tool_results");
2756
3171
  db.close();
2757
3172
  console.log(`agent-lens-ingest: files=${stats.files} skipped=${stats.skipped} new_events=${stats.newEvents} malformed=${stats.malformed}${pruned ? ` excluded_pruned=${pruned}` : ""}
2758
3173
  sessions=${sessions} turns=${turns} events=${events} tool_calls=${tools} classified=${classified.count}
3174
+ workflow_results=${wfRuns} (sidecar upserted=${wfStats.upserted} skipped=${wfStats.skipped} malformed=${wfStats.malformed})
3175
+ session_meta=${metaRows} (upserted=${metaStats.upserted} skipped=${metaStats.skipped} malformed=${metaStats.malformed})
3176
+ tool_results=${trRows} (upserted=${trStats.upserted} skipped=${trStats.skipped} malformed=${trStats.malformed})
2759
3177
  tokens=${totalTokens.toLocaleString()} est_cost=$${cost.toFixed(2)} db=${dbPath}`);
2760
3178
  }
2761
3179
  function runMetrics(argv = process.argv.slice(2)) {
@@ -2764,7 +3182,7 @@ function runMetrics(argv = process.argv.slice(2)) {
2764
3182
  if (argv[i] === "--db")
2765
3183
  dbArg = argv[++i] ?? "";
2766
3184
  const dataDir = resolveDataDir(findRepoRoot());
2767
- const dbPath = dbArg || process.env.AGENT_LENS_DB || join5(dataDir, "agent-lens.db");
3185
+ const dbPath = dbArg || process.env.AGENT_LENS_DB || join8(dataDir, "agent-lens.db");
2768
3186
  if (!existsSync4(dbPath)) {
2769
3187
  console.error(`agent-lens-metrics: db not found: ${dbPath} (run ingest first)`);
2770
3188
  process.exit(1);
@@ -2777,7 +3195,7 @@ function runMetrics(argv = process.argv.slice(2)) {
2777
3195
 
2778
3196
  // ../server/dist/run.js
2779
3197
  import { existsSync as existsSync7 } from "fs";
2780
- import { dirname as dirname5, join as join7 } from "path";
3198
+ import { dirname as dirname5, join as join10 } from "path";
2781
3199
  import { fileURLToPath as fileURLToPath2 } from "url";
2782
3200
 
2783
3201
  // ../server/dist/db.js
@@ -2787,6 +3205,12 @@ function openReadonly(file) {
2787
3205
  db.pragma("query_only = ON");
2788
3206
  return db;
2789
3207
  }
3208
+ function schemaStatus(db) {
3209
+ const row = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
3210
+ const dbVersion = row ? Number(row.value) : null;
3211
+ const db_version = dbVersion != null && Number.isFinite(dbVersion) ? dbVersion : null;
3212
+ return { db_version, expected: SCHEMA_VERSION, stale: db_version != null && db_version !== SCHEMA_VERSION };
3213
+ }
2790
3214
  function lastIngested(db) {
2791
3215
  const row = db.prepare("SELECT MAX(ingested_at) AS last FROM ingest_state").get();
2792
3216
  return row?.last ?? null;
@@ -2937,6 +3361,17 @@ function getSession(db, id) {
2937
3361
  total_duration_ms, total_tokens, input_json, result_summary,
2938
3362
  (SELECT COUNT(*) FROM sessions s WHERE s.workflow_run_id = tool_calls.workflow_run_id) AS workflow_agent_count
2939
3363
  FROM tool_calls WHERE session_id = ?`).all(id);
3364
+ if (tableExists(db, "tool_results")) {
3365
+ const getFull = db.prepare("SELECT text, bytes FROM tool_results WHERE session_id = ? AND name = ?");
3366
+ for (const t of toolRows) {
3367
+ const m = typeof t.result_summary === "string" ? t.result_summary.match(/tool-results\/([A-Za-z0-9_-]+)\.txt/) : null;
3368
+ if (!m)
3369
+ continue;
3370
+ const full = getFull.get(id, m[1]);
3371
+ if (full)
3372
+ t.full_result = { text: full.text, bytes: full.bytes };
3373
+ }
3374
+ }
2940
3375
  const toolsByEvent = /* @__PURE__ */ new Map();
2941
3376
  for (const t of toolRows) {
2942
3377
  if (!t.event_uuid)
@@ -3004,9 +3439,12 @@ function getSession(db, id) {
3004
3439
  if (p)
3005
3440
  parent = { id: p.id, title: p.ai_title || p.slug || null, turn_seq: p.turn_seq ?? null };
3006
3441
  }
3442
+ const hasMeta = tableExists(db, "session_meta");
3007
3443
  const children = db.prepare(`SELECT s.id, s.ai_title, s.slug, s.turn_count, s.started_at, s.workflow_run_id,
3008
3444
  (SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models
3009
- FROM sessions s WHERE s.parent_session_id = ? ORDER BY s.started_at`).all(id);
3445
+ ${hasMeta ? ", sm.agent_type, sm.agent_description, sm.spawn_depth" : ", NULL AS agent_type, NULL AS agent_description, NULL AS spawn_depth"}
3446
+ FROM sessions s ${hasMeta ? "LEFT JOIN session_meta sm ON sm.session_id = s.id" : ""}
3447
+ WHERE s.parent_session_id = ? ORDER BY s.started_at`).all(id);
3010
3448
  for (const ch of children) {
3011
3449
  ch.title = ch.ai_title || ch.slug || null;
3012
3450
  const u = db.prepare(`SELECT model, SUM(input_tokens) i, SUM(output_tokens) o,
@@ -3015,8 +3453,10 @@ function getSession(db, id) {
3015
3453
  ch.tokens = u.reduce((a, r) => a + r.i + r.o + r.cw + r.cr, 0);
3016
3454
  ch.cost = Number(u.reduce((a, r) => a + costForUsage(r.model, { input_tokens: r.i, output_tokens: r.o, cache_creation_input_tokens: r.cw, cache_read_input_tokens: r.cr }), 0).toFixed(4));
3017
3455
  }
3456
+ const hasWR = tableExists(db, "workflow_results");
3018
3457
  const workflow_runs = db.prepare(`SELECT tc.workflow_run_id AS run_id, tc.workflow_name AS name, t.seq AS turn_seq,
3019
3458
  (SELECT COUNT(*) FROM sessions s WHERE s.workflow_run_id = tc.workflow_run_id) AS agent_count
3459
+ ${hasWR ? ", (SELECT status FROM workflow_results wr WHERE wr.run_id = tc.workflow_run_id) AS status" : ", NULL AS status"}
3020
3460
  FROM tool_calls tc LEFT JOIN turns t ON t.id = tc.turn_id
3021
3461
  WHERE tc.session_id = ? AND tc.workflow_run_id IS NOT NULL
3022
3462
  ORDER BY t.seq`).all(id);
@@ -3025,8 +3465,21 @@ function getSession(db, id) {
3025
3465
  function xmlTag(text, tag) {
3026
3466
  return text.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`))?.[1]?.trim() ?? null;
3027
3467
  }
3468
+ function tableExists(db, name) {
3469
+ return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?").get(name);
3470
+ }
3471
+ function safeJson(s) {
3472
+ if (!s)
3473
+ return null;
3474
+ try {
3475
+ return JSON.parse(s);
3476
+ } catch {
3477
+ return null;
3478
+ }
3479
+ }
3028
3480
  function getWorkflow(db, runId) {
3029
3481
  const wf = db.prepare(`SELECT tc.id AS tool_use_id, tc.workflow_run_id AS run_id, tc.workflow_name AS name, tc.status, tc.result_summary,
3482
+ tc.input_json AS input_json,
3030
3483
  tc.session_id AS parent_session_id, t.seq AS turn_seq,
3031
3484
  ps.ai_title AS parent_ai_title, ps.slug AS parent_slug
3032
3485
  FROM tool_calls tc
@@ -3037,9 +3490,12 @@ function getWorkflow(db, runId) {
3037
3490
  LIMIT 1`).get(runId);
3038
3491
  if (!wf)
3039
3492
  return null;
3493
+ const hasMeta = tableExists(db, "session_meta");
3040
3494
  const agents = db.prepare(`SELECT s.id, s.ai_title, s.slug, s.turn_count, s.started_at, s.ended_at, s.duration_ms,
3041
3495
  (SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models
3042
- FROM sessions s WHERE s.workflow_run_id = ? ORDER BY s.started_at`).all(runId);
3496
+ ${hasMeta ? ", sm.agent_type, sm.agent_description, sm.spawn_depth" : ", NULL AS agent_type, NULL AS agent_description, NULL AS spawn_depth"}
3497
+ FROM sessions s ${hasMeta ? "LEFT JOIN session_meta sm ON sm.session_id = s.id" : ""}
3498
+ WHERE s.workflow_run_id = ? ORDER BY s.started_at`).all(runId);
3043
3499
  let total_tokens = 0;
3044
3500
  let total_cost = 0;
3045
3501
  let started_at = null;
@@ -3070,12 +3526,44 @@ function getWorkflow(db, runId) {
3070
3526
  completion = { status: xmlTag(t, "status"), summary: xmlTag(t, "summary"), result: xmlTag(t, "result"), failures: xmlTag(t, "failures") };
3071
3527
  }
3072
3528
  }
3529
+ const wr = tableExists(db, "workflow_results") ? db.prepare(`SELECT status, summary, default_model, result_json, phases_json, logs_json, progress_json,
3530
+ agent_count, total_tokens, total_tool_calls, duration_ms, started_at, ended_at
3531
+ FROM workflow_results WHERE run_id = ?`).get(wf.run_id) : null;
3532
+ let run = null;
3533
+ if (wr) {
3534
+ run = {
3535
+ status: wr.status ?? null,
3536
+ summary: wr.summary ?? null,
3537
+ default_model: wr.default_model ?? null,
3538
+ agent_count: wr.agent_count ?? null,
3539
+ total_tokens: wr.total_tokens ?? null,
3540
+ total_tool_calls: wr.total_tool_calls ?? null,
3541
+ duration_ms: wr.duration_ms ?? null,
3542
+ started_at: wr.started_at ?? null,
3543
+ ended_at: wr.ended_at ?? null,
3544
+ phases: safeJson(wr.phases_json),
3545
+ logs: safeJson(wr.logs_json),
3546
+ // The per-phase/per-agent event timeline (workflowProgress) — lets the page render a phase graph
3547
+ // with a per-phase descriptor (agent count, models). Absent on older/failed runs → UI falls back.
3548
+ progress: safeJson(wr.progress_json)
3549
+ };
3550
+ if (wr.result_json != null || wr.status) {
3551
+ completion = {
3552
+ status: wr.status ?? completion?.status ?? null,
3553
+ summary: wr.summary ?? completion?.summary ?? null,
3554
+ result: wr.result_json ?? completion?.result ?? null,
3555
+ failures: completion?.failures ?? null
3556
+ };
3557
+ }
3558
+ }
3073
3559
  return {
3074
3560
  run_id: wf.run_id,
3075
3561
  name: wf.name ?? null,
3076
- status: wf.status ?? null,
3562
+ status: wr?.status ?? wf.status ?? null,
3077
3563
  result_summary: wf.result_summary ?? null,
3564
+ input_json: wf.input_json ?? null,
3078
3565
  completion,
3566
+ run,
3079
3567
  parent: { id: wf.parent_session_id, title: wf.parent_ai_title || wf.parent_slug || null, turn_seq: wf.turn_seq ?? null },
3080
3568
  agents,
3081
3569
  stats: { agent_count: agents.length, total_tokens, total_cost: Number(total_cost.toFixed(4)), started_at, ended_at, duration_ms }
@@ -3305,7 +3793,7 @@ function dashboardBreakdowns(db, f) {
3305
3793
 
3306
3794
  // ../server/dist/refresh.js
3307
3795
  import { existsSync as existsSync5, mkdirSync as mkdirSync5 } from "fs";
3308
- import { join as join6 } from "path";
3796
+ import { join as join9 } from "path";
3309
3797
  function originAllowed(origin) {
3310
3798
  if (!origin)
3311
3799
  return true;
@@ -3318,12 +3806,12 @@ function originAllowed(origin) {
3318
3806
  }
3319
3807
  function runRefresh() {
3320
3808
  const dataDir = resolveDataDir(findRepoRoot());
3321
- const lock = acquireLock(join6(dataDir, ".agent-lens.lock"));
3809
+ const lock = acquireLock(join9(dataDir, ".agent-lens.lock"));
3322
3810
  if (!lock)
3323
3811
  return null;
3324
3812
  try {
3325
3813
  const collected = collectAll();
3326
- const archiveRoot = process.env.AGENT_LENS_ARCHIVE || join6(dataDir, "archive");
3814
+ const archiveRoot = process.env.AGENT_LENS_ARCHIVE || join9(dataDir, "archive");
3327
3815
  if (!existsSync5(archiveRoot))
3328
3816
  mkdirSync5(archiveRoot, { recursive: true });
3329
3817
  runIngest([]);
@@ -3336,7 +3824,10 @@ function runRefresh() {
3336
3824
  // ../server/dist/app.js
3337
3825
  async function createApp(db, opts = {}) {
3338
3826
  const app = Fastify({ logger: false });
3339
- app.get("/api/health", async () => ({ ok: true, last_ingested: lastIngested(db) }));
3827
+ app.get("/api/health", async () => {
3828
+ const s = schemaStatus(db);
3829
+ return { ok: true, last_ingested: lastIngested(db), schema_version: s.db_version, schema_stale: s.stale };
3830
+ });
3340
3831
  app.post("/api/refresh", async (req, reply) => {
3341
3832
  if (!originAllowed(req.headers.origin)) {
3342
3833
  return reply.code(403).send({ error: { code: "FORBIDDEN_ORIGIN", message: "cross-origin refresh blocked" } });
@@ -3452,7 +3943,7 @@ async function createApp(db, opts = {}) {
3452
3943
  async function startServer() {
3453
3944
  const here = dirname5(fileURLToPath2(import.meta.url));
3454
3945
  const repoRoot = findRepoRoot(here);
3455
- const dbPath = process.env.AGENT_LENS_DB || join7(resolveDataDir(repoRoot), "agent-lens.db");
3946
+ const dbPath = process.env.AGENT_LENS_DB || join10(resolveDataDir(repoRoot), "agent-lens.db");
3456
3947
  const host = process.env.AGENT_LENS_HOST || "127.0.0.1";
3457
3948
  const port = Number(process.env.AGENT_LENS_PORT || 4477);
3458
3949
  const webDist = resolveWebDist(here, repoRoot);
@@ -3479,11 +3970,11 @@ async function startServer() {
3479
3970
 
3480
3971
  // src/watch.ts
3481
3972
  import { existsSync as existsSync8 } from "fs";
3482
- import { join as join8 } from "path";
3973
+ import { join as join11 } from "path";
3483
3974
  import chokidar from "chokidar";
3484
3975
  async function runWatch(opts = {}) {
3485
3976
  const sources = loadSources();
3486
- const lockPath = join8(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
3977
+ const lockPath = join11(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
3487
3978
  let running = false;
3488
3979
  let pending = false;
3489
3980
  const cycle = () => {
@@ -3516,7 +4007,7 @@ async function runWatch(opts = {}) {
3516
4007
  }
3517
4008
  };
3518
4009
  cycle();
3519
- const watchPaths = sources.map((s) => join8(s.configDir, "projects")).filter((p) => existsSync8(p));
4010
+ const watchPaths = sources.map((s) => join11(s.configDir, "projects")).filter((p) => existsSync8(p));
3520
4011
  if (watchPaths.length === 0) {
3521
4012
  console.error("agent-lens watch: no source 'projects' directories found \u2014 nothing to watch");
3522
4013
  }
@@ -3544,14 +4035,14 @@ async function runWatch(opts = {}) {
3544
4035
  // src/index.ts
3545
4036
  function version() {
3546
4037
  try {
3547
- const pkg = JSON.parse(readFileSync5(join9(dirname6(fileURLToPath3(import.meta.url)), "../package.json"), "utf8"));
4038
+ const pkg = JSON.parse(readFileSync8(join12(dirname6(fileURLToPath3(import.meta.url)), "../package.json"), "utf8"));
3548
4039
  return pkg.version ?? "0.0.0";
3549
4040
  } catch {
3550
4041
  return "0.0.0";
3551
4042
  }
3552
4043
  }
3553
4044
  function withLock(fn) {
3554
- const lockPath = join9(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
4045
+ const lockPath = join12(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
3555
4046
  const lock = acquireLock(lockPath);
3556
4047
  if (!lock) {
3557
4048
  console.error("agent-lens: another collect/ingest run is in progress \u2014 skipping");