omp-fabric 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.10.0
4
+
5
+ ### Added
6
+
7
+ - The dashboard carries the LCM ledger on key `3`. It shows the coverage band for the active branch, the node graph paged in bounded requests, the text a compaction would assemble right now beside the session payload it draws from, and a node detail that opens the exact stored raw entry behind any source. A node whose excerpt was later replaced by a model summary shows both, so the upgrade can be judged rather than assumed.
8
+ - Summary nodes keep their replaced text. `summary_node_revisions` records the prior text and model hash whenever maintenance rewrites a node, which is what makes the excerpt-versus-summary comparison possible.
9
+
10
+ ### Changed
11
+
12
+ - `/fabric lcm` is gone; the dashboard view replaces it. The single-line summary in `/fabric status` stays.
13
+ - The ledger read paths that feed the view are aggregates rather than row walks. Measured on a live 5,392-entry session: the coverage scan reads identity columns in 15.7 ms against 150.7 ms for the previous full-row walk, and session payload size is one aggregate at 35.2 ms against roughly 620 ms for the per-entry loop it replaces. Preview, coverage, and the coverage map now share a single frontier walk, and node listing pages in SQL rather than slicing an oversized read.
14
+
15
+ ## 1.9.1
16
+
17
+ ### Fixed
18
+
19
+ - Reconciling the session a run is actively writing failed every time, so the live session contributed nothing to the ledger and `/fabric lcm` reported a degraded runtime. The reader compared the file's size and mtime before and after reading and rejected any difference, but session JSONL is append-only and a live session grows during the read: a concurrent writer produced `errors: 1, imported: 0` on five attempts out of five. A longer file is now accepted, and only a shrinking one is refused; the same fixture then imported 4,044 entries and kept up with the writer.
20
+ - Tagging a release stopped publishing the GitHub release. The CI rewrite in 1.8.1 dropped that step while removing duplicated verification, so v1.8.1 through v1.9.0 reached npm with no release entry. The step is restored and the missing releases were created.
21
+
3
22
  ## 1.9.0
4
23
 
5
24
  ### Added
package/README.md CHANGED
@@ -127,6 +127,34 @@ Fabric includes a live activity surface in OMP:
127
127
 
128
128
  See the [interface & commands reference](docs/interface.md) for every view, keybinding, and slash command.
129
129
 
130
+ ## Measured against OMP without Fabric
131
+
132
+ A resume task, seeded as a paused session, run three times per arm on `claude-opus-5` through the same provider. The baseline arm is OMP with no extension loaded; the Fabric arm loads `dist/index.js` and compacts the session with LCM before resuming. Both arms receive the same fixture, the same prompt, and the same host configuration, and a run counts as a success only when the fixture's own `verify.mjs` exits zero with the forbidden files untouched.
133
+
134
+ | | Baseline | Fabric | Delta |
135
+ |---|---|---|---|
136
+ | Task success | 3/3 | 3/3 | equal |
137
+ | Tokens | 585,197 | 356,542 | **-39%** |
138
+ | Tool calls | 14 | 6 | **-57%** |
139
+ | Wall time | 67.6 s | 59.1 s | -13% |
140
+ | Cost | $0.6987 | $0.2356 | -66% |
141
+
142
+ Per-run tokens were 208,595 / 209,619 / 166,983 for the baseline and 118,751 / 119,387 / 118,404 with Fabric, so the reduction is stable across runs, not an artifact of one lucky sample. Read the cost row with care: the baseline's first run cost $0.4710 against $0.1271 and $0.1007 for its later two, because a cold prompt cache is charged at full rate. The token column is the honest measure of the difference, and cost follows it once caches are warm on both sides.
143
+
144
+ Reproduce it with your own model and provider:
145
+
146
+ ```sh
147
+ OMP_FABRIC_REAL_RESUME=1 \
148
+ OMP_FABRIC_BENCH_MODEL=claude-opus-5 \
149
+ OMP_FABRIC_BENCH_PROVIDER=sub2api-claude \
150
+ OMP_FABRIC_BENCH_KEY_ENV=YOUR_KEY_VARIABLE \
151
+ OMP_FABRIC_BENCH_REPEATS=3 \
152
+ OMP_FABRIC_BENCH_MAX_USD=8 \
153
+ bun run benchmark:real-resume
154
+ ```
155
+
156
+ The command bills your provider and stops at `OMP_FABRIC_BENCH_MAX_USD`. Without those variables it reports `SKIP` and spends nothing.
157
+
130
158
  ## Reference
131
159
 
132
160
  - [Configuration](docs/configuration.md): `fabric.json`, code modes, tool capture, approvals, and budgets.
@@ -1 +1 @@
1
- {"version":3,"file":"fabric.d.ts","sourceRoot":"","sources":["../../src/commands/fabric.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAErF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AA8C9D,UAAU,iBAAiB;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,aAAa,EAAE,mBAAmB,CAAC;IACnC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,0BAA0B,CAAC,EAAE,MAAM,IAAI,CAAC;IACxC,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;CACjC;AA6HD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,iBAAiB,GAAG,IAAI,CA0hCtF"}
1
+ {"version":3,"file":"fabric.d.ts","sourceRoot":"","sources":["../../src/commands/fabric.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAErF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AA8C9D,UAAU,iBAAiB;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,aAAa,EAAE,mBAAmB,CAAC;IACnC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,0BAA0B,CAAC,EAAE,MAAM,IAAI,CAAC;IACxC,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;CACjC;AA6HD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAw+BtF"}
@@ -76,10 +76,17 @@ export declare class LcmMaintenance {
76
76
  private readonly modelTimeoutMs;
77
77
  constructor(ledger: LcmLedger, options?: LcmMaintenanceOptions);
78
78
  budgetPolicy(): LcmBudgetPolicy;
79
+ private recordRevision;
80
+ revisionsOf(nodeId: string): Array<{
81
+ revision: number;
82
+ text: string;
83
+ modelHash: string;
84
+ createdAt: number;
85
+ }>;
79
86
  selectUpgrades(sessionId?: string, activeSources?: ReadonlySet<string>, branch?: string | null, limit?: number): LcmNode[];
80
87
  ancestorsOf(nodeId: string): string[];
81
88
  reopen(nodeId: string, force?: boolean): LcmJob;
82
- listNodes(limit?: number): LcmNode[];
89
+ listNodes(limit?: number, offset?: number): LcmNode[];
83
90
  getNode(nodeId: string): LcmNode | undefined;
84
91
  getFrontier(sessionId?: string, branch?: string | null, activeSources?: ReadonlySet<string>): LcmNode[];
85
92
  listJobs(limit?: number): LcmJob[];
@@ -1 +1 @@
1
- {"version":3,"file":"lcm-maintenance.d.ts","sourceRoot":"","sources":["../../src/compaction/lcm-maintenance.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lcm-maintenance.d.ts","sourceRoot":"","sources":["../../src/compaction/lcm-maintenance.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAmC,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE1G,KAAK,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAC/D,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;AAClE,MAAM,WAAW,YAAY;IAAG,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;CAAE;AAE5G,MAAM,WAAW,OAAO;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;CAAE;AACpT,MAAM,WAAW,MAAM;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;CAAE;AACjS,MAAM,WAAW,SAAS;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;CAAE;AACtH,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAAG,YAAY,EAAE,MAAM,CAAC;CAAE;AAE5E,MAAM,WAAW,qBAAqB;IAAG,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;CAAE;AAMzQ,qBAAa,cAAc;IAWb,OAAO,CAAC,QAAQ,CAAC,MAAM;IAVnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBACX,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE,qBAA0B;IACnF,YAAY,IAAI,eAAe;IAC/B,OAAO,CAAC,cAAc;IAMtB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAI5G,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,SAAI,GAAG,OAAO,EAAE;IASrH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAkBrC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,MAAM;IAc7C,SAAS,CAAC,KAAK,SAAM,EAAE,MAAM,SAAI,GAAG,OAAO,EAAE;IAC7C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAC5C,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,EAAE;IACvG,QAAQ,CAAC,KAAK,SAAM,GAAG,MAAM,EAAE;IAC/B,UAAU,CAAC,OAAO,aAAuC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,QAAQ,EAAE;IAC9F,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,EAAE;IAC9G,UAAU,CAAC,OAAO,aAAoB,GAAG,OAAO,GAAG,SAAS;IAC5D,eAAe,CAAC,QAAQ,YAA4B,GAAG,OAAO,GAAG,SAAS;IAC1E,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,GAAG;IACX,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,SAAe,GAAG,MAAM;IACpD,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,SAAe,GAAG,MAAM;IAC7D,KAAK,CAAC,GAAG,EAAC,MAAM,GAAG,MAAM;IACzB,OAAO,CAAC,YAAY;IACpB,QAAQ,CAAC,GAAG,EAAC,MAAM,EAAE,MAAM,EAAC,cAAc,GAAG,OAAO;IACpD,iBAAiB,CAAC,GAAG,EAAC,MAAM,EAAE,IAAI,EAAC,MAAM,GAAG,OAAO;IACnD,IAAI,CAAC,GAAG,EAAC,MAAM,EAAC,KAAK,EAAC,MAAM,GAAG,MAAM;IAC/B,GAAG,CAAC,GAAG,EAAC,MAAM,EAAE,KAAK,EAAC,aAAa,EAAE,KAAK,EAAC,MAAM,EAAE,MAAM,cAA6B,GAAG,OAAO,CAAC,OAAO,CAAC;IAC/G,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;CACf"}
@@ -2,6 +2,14 @@ import type { ExtensionContext } from "@oh-my-pi/pi-coding-agent";
2
2
  import { LcmLedger, type RawEntry } from "../storage/lcm-ledger.js";
3
3
  import { LcmMaintenance, type LcmNode } from "./lcm-maintenance.js";
4
4
  import type { LcmCompactionInput, LcmCompactionOutput } from "./hook.js";
5
+ export interface LcmPreview {
6
+ text: string;
7
+ nodes: number;
8
+ summaryBytes: number;
9
+ sourceBytes: number;
10
+ coveredSources: number;
11
+ activeSources: number;
12
+ }
5
13
  export interface LcmReport {
6
14
  projectKey: string;
7
15
  sessionId: string | undefined;
@@ -74,6 +82,24 @@ export declare class LcmRuntime {
74
82
  private runMaintenance;
75
83
  compact(input: LcmCompactionInput): LcmCompactionOutput;
76
84
  report(): LcmReport;
85
+ private frontierCache;
86
+ /** One frontier walk shared by preview, coverage, and the coverage map. */
87
+ private frontierSnapshot;
88
+ private sourceKey;
89
+ /** Assembles what a compaction would serve now. Reads only; it never persists a node. */
90
+ preview(): LcmPreview;
91
+ /** Ordered coverage of the active branch: each entry says whether a ready node holds it. */
92
+ coverageMap(limit?: number): Array<{
93
+ key: string;
94
+ covered: boolean;
95
+ }>;
96
+ nodes(limit?: number, offset?: number): LcmNode[];
97
+ node(nodeId: string): {
98
+ node: LcmNode;
99
+ revisions: ReturnType<LcmMaintenance["revisionsOf"]>;
100
+ ancestors: string[];
101
+ } | undefined;
102
+ source(sessionId: string, entryId: string, revision: number): RawEntry | undefined;
77
103
  coverage(): {
78
104
  active: number;
79
105
  covered: number;
@@ -1 +1 @@
1
- {"version":3,"file":"lcm-runtime.d.ts","sourceRoot":"","sources":["../../src/compaction/lcm-runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lcm-runtime.d.ts","sourceRoot":"","sources":["../../src/compaction/lcm-runtime.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,7 @@ CREATE TABLE IF NOT EXISTS project_aliases (alias TEXT PRIMARY KEY, project_key
36
36
  CREATE TABLE IF NOT EXISTS sessions (project_key TEXT NOT NULL, session_id TEXT NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY(project_key, session_id));
37
37
  CREATE TABLE IF NOT EXISTS raw_entries (project_key TEXT NOT NULL, session_id TEXT NOT NULL, entry_id TEXT NOT NULL, revision INTEGER NOT NULL, role TEXT NOT NULL, content TEXT NOT NULL, content_hash TEXT NOT NULL, payload_json TEXT NOT NULL, parent_entry_id TEXT, branch TEXT, created_at INTEGER NOT NULL, PRIMARY KEY(project_key, session_id, entry_id, revision), UNIQUE(project_key, session_id, entry_id, content_hash));
38
38
  CREATE TABLE IF NOT EXISTS summary_nodes (node_id TEXT PRIMARY KEY, project_key TEXT NOT NULL, payload TEXT NOT NULL, created_at INTEGER NOT NULL);
39
+ CREATE TABLE IF NOT EXISTS summary_node_revisions (node_id TEXT NOT NULL, revision INTEGER NOT NULL, project_key TEXT NOT NULL, text TEXT NOT NULL, model_hash TEXT NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY(node_id, revision));
39
40
  CREATE TABLE IF NOT EXISTS summary_edges (parent_id TEXT NOT NULL, child_id TEXT NOT NULL, PRIMARY KEY(parent_id, child_id));
40
41
  CREATE TABLE IF NOT EXISTS frontiers (project_key TEXT NOT NULL, frontier_id TEXT NOT NULL, node_id TEXT NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY(project_key, frontier_id, node_id));
41
42
  CREATE TABLE IF NOT EXISTS maintenance_jobs (job_id TEXT PRIMARY KEY, project_key TEXT NOT NULL, status TEXT NOT NULL, payload TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL);
@@ -198,6 +199,20 @@ var LcmLedger = class {
198
199
  const row = this.db.prepare("SELECT * FROM raw_entries WHERE project_key=? AND session_id=? AND entry_id=? AND revision=?").get(projectKey, sessionId, entryId, revision);
199
200
  return row ? { projectKey: row.project_key, sessionId: row.session_id, entryId: row.entry_id, revision: row.revision, role: row.role, content: row.content, contentHash: row.content_hash, payloadHash: row.content_hash, payloadJson: row.payload_json, parentEntryId: row.parent_entry_id, branch: row.branch, createdAt: row.created_at } : void 0;
200
201
  }
202
+ /** Identity columns only, so a coverage scan never loads payloads. */
203
+ readRawKeys(projectKey = this.project.key, sessionId, limit = 1e5) {
204
+ this.guard();
205
+ this.assertProjectKey(projectKey);
206
+ const rows = sessionId ? this.db.prepare("SELECT session_id, entry_id, revision FROM raw_entries WHERE project_key=? AND session_id=? ORDER BY created_at,revision LIMIT ?").all(projectKey, sessionId, limit) : this.db.prepare("SELECT session_id, entry_id, revision FROM raw_entries WHERE project_key=? ORDER BY created_at,session_id,entry_id,revision LIMIT ?").all(projectKey, limit);
207
+ return rows.map((row) => ({ sessionId: row.session_id, entryId: row.entry_id, revision: Number(row.revision) }));
208
+ }
209
+ /** Total stored payload bytes for one session, as a single aggregate. */
210
+ payloadBytes(projectKey = this.project.key, sessionId) {
211
+ this.guard();
212
+ this.assertProjectKey(projectKey);
213
+ const row = sessionId ? this.db.prepare("SELECT coalesce(sum(length(payload_json)),0) n FROM raw_entries WHERE project_key=? AND session_id=?").get(projectKey, sessionId) : this.db.prepare("SELECT coalesce(sum(length(payload_json)),0) n FROM raw_entries WHERE project_key=?").get(projectKey);
214
+ return Number(row.n);
215
+ }
201
216
  readOnly(fn) {
202
217
  this.guard();
203
218
  return fn(this.db);
@@ -294,6 +309,7 @@ import path2 from "node:path";
294
309
  var hash2 = (data) => crypto2.createHash("sha256").update(data).digest("hex");
295
310
  var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
296
311
  var nonblank = (value) => typeof value === "string" && value.trim().length > 0;
312
+ var sourceStillValid = (before, after) => after >= before;
297
313
  var utc = (value) => {
298
314
  if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|\+00:00)$/.test(value)) throw new Error("timestamps must be explicit UTC ISO-8601 values");
299
315
  const at = Date.parse(value);
@@ -434,7 +450,7 @@ function migrateSessions(options) {
434
450
  offset += size;
435
451
  }
436
452
  const after = fs2.fstatSync(fd);
437
- if (after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs) throw new Error("source changed during read");
453
+ if (!sourceStillValid(before.size, after.size)) throw new Error("source shrank during read");
438
454
  } catch {
439
455
  counts.errors++;
440
456
  counts.incompleteDiscovery++;
@@ -609,6 +625,14 @@ var LcmMaintenance = class {
609
625
  budgetPolicy() {
610
626
  return { ...this.budget };
611
627
  }
628
+ recordRevision(db, node) {
629
+ if (!node.text) return;
630
+ const row = db.prepare("SELECT coalesce(max(revision),0) n FROM summary_node_revisions WHERE node_id=?").get(node.nodeId);
631
+ db.prepare("INSERT OR IGNORE INTO summary_node_revisions(node_id,revision,project_key,text,model_hash,created_at) VALUES(?,?,?,?,?,?)").run(node.nodeId, Number(row.n) + 1, this.projectKey, node.text, node.modelHash, this.now());
632
+ }
633
+ revisionsOf(nodeId) {
634
+ return this.ledger.readOnly((db) => db.prepare("SELECT revision, text, model_hash, created_at FROM summary_node_revisions WHERE node_id=? AND project_key=? ORDER BY revision").all(nodeId, this.projectKey).map((row) => ({ revision: Number(row.revision), text: row.text, modelHash: row.model_hash, createdAt: Number(row.created_at) })));
635
+ }
612
636
  selectUpgrades(sessionId, activeSources, branch, limit = 1) {
613
637
  return this.listNodes(1e5).filter((node) => node.state === "ready" && node.modelHash === "emergency" && node.policyHash === this.policyHash && (!sessionId || node.sessionId === sessionId) && (branch === void 0 || node.branch === branch || node.branch === null) && (!activeSources || node.sources.every((source) => activeSources.has(this.sourceKey(source))))).sort((left, right) => left.depth - right.depth || left.nodeId.localeCompare(right.nodeId)).slice(0, limit);
614
638
  }
@@ -643,8 +667,8 @@ var LcmMaintenance = class {
643
667
  return job;
644
668
  });
645
669
  }
646
- listNodes(limit = 100) {
647
- return this.ledger.readOnly((db) => db.prepare("SELECT payload FROM summary_nodes WHERE project_key=? ORDER BY created_at,node_id LIMIT ?").all(this.projectKey, limit).map((r) => {
670
+ listNodes(limit = 100, offset = 0) {
671
+ return this.ledger.readOnly((db) => db.prepare("SELECT payload FROM summary_nodes WHERE project_key=? ORDER BY created_at,node_id LIMIT ? OFFSET ?").all(this.projectKey, limit, offset).map((r) => {
648
672
  const node = parse(r.payload);
649
673
  if (node.projectKey !== this.projectKey) throw new Error("node project does not match ledger project");
650
674
  return node;
@@ -826,6 +850,7 @@ var LcmMaintenance = class {
826
850
  const projectUsage = this.usage(db, this.day(this.now()));
827
851
  const sessionUsage = this.usage(db, this.day(this.now()), node.sessionId);
828
852
  if (projectUsage.calls + 1 > this.budget.calls || projectUsage.inputTokens + result.inputTokens > this.budget.inputTokens || projectUsage.outputTokens + result.outputTokens > this.budget.outputTokens || projectUsage.cost + result.cost > this.budget.cost || projectUsage.wallMs + result.wallMs > this.budget.wallMs || sessionUsage.calls + 1 > this.budget.sessionCalls) throw new Error("budget exhausted");
853
+ this.recordRevision(db, node);
829
854
  const out = { ...node, state: "ready", text: result.text, modelHash: result.modelHash };
830
855
  this.account(db, node.sessionId, result);
831
856
  db.prepare("UPDATE summary_nodes SET payload=? WHERE node_id=? AND project_key=?").run(JSON.stringify(out), node.nodeId, this.projectKey);
@@ -849,6 +874,7 @@ var LcmMaintenance = class {
849
874
  const raw = db.prepare("SELECT payload_json,session_id FROM raw_entries WHERE project_key=? AND session_id=? AND entry_id=? AND revision=?").get(this.projectKey, source.sessionId, source.entryId, source.revision);
850
875
  if (!raw?.payload_json || hash3(parse(raw.payload_json)) !== source.payloadHash || raw.session_id !== source.sessionId) throw new Error("stale source");
851
876
  }
877
+ this.recordRevision(db, node);
852
878
  const out = { ...node, state: "ready", text, modelHash: "emergency" };
853
879
  db.prepare("UPDATE summary_nodes SET payload=? WHERE node_id=? AND project_key=?").run(JSON.stringify(out), node.nodeId, this.projectKey);
854
880
  const { ownerId: _ownerId, leaseToken: _leaseToken, leaseUntil: _leaseUntil, ...base } = current;
@@ -1225,10 +1251,62 @@ var LcmRuntime = class {
1225
1251
  };
1226
1252
  });
1227
1253
  }
1254
+ frontierCache;
1255
+ /** One frontier walk shared by preview, coverage, and the coverage map. */
1256
+ frontierSnapshot() {
1257
+ const sessionId = this.activeSessionId ?? "";
1258
+ const branch = this.context.sessionManager.getLeafId();
1259
+ const cached = this.frontierCache;
1260
+ if (cached && cached.sessionId === sessionId && cached.branch === branch && Date.now() - cached.at < 250) {
1261
+ return { nodes: cached.nodes, covered: cached.covered };
1262
+ }
1263
+ const nodes = sessionId ? this.maintenance.getFrontier(sessionId, branch, this.activeSources) : [];
1264
+ const covered = new Set(nodes.flatMap((node) => node.sources.map((source) => this.sourceKey(source))));
1265
+ this.frontierCache = { at: Date.now(), sessionId, branch, nodes, covered };
1266
+ return { nodes, covered };
1267
+ }
1268
+ sourceKey(source) {
1269
+ return `${source.sessionId}:${source.entryId}:${source.revision}`;
1270
+ }
1271
+ /** Assembles what a compaction would serve now. Reads only; it never persists a node. */
1272
+ preview() {
1273
+ const sessionId = this.activeSessionId;
1274
+ if (!sessionId) return { text: "", nodes: 0, summaryBytes: 0, sourceBytes: 0, coveredSources: 0, activeSources: 0 };
1275
+ const { nodes, covered } = this.frontierSnapshot();
1276
+ const text = nodes.map((node) => node.text ?? "").filter(Boolean).join("\n\n");
1277
+ return {
1278
+ text,
1279
+ nodes: nodes.length,
1280
+ summaryBytes: Buffer.byteLength(text, "utf8"),
1281
+ sourceBytes: this.ledger.payloadBytes(this.projectKey, sessionId),
1282
+ coveredSources: covered.size,
1283
+ activeSources: this.activeSources.size
1284
+ };
1285
+ }
1286
+ /** Ordered coverage of the active branch: each entry says whether a ready node holds it. */
1287
+ coverageMap(limit = 2e3) {
1288
+ const sessionId = this.activeSessionId;
1289
+ if (!sessionId) return [];
1290
+ const { covered } = this.frontierSnapshot();
1291
+ return this.ledger.readRawKeys(this.projectKey, sessionId, limit).map((entry) => {
1292
+ const key = this.sourceKey(entry);
1293
+ return { key, covered: covered.has(key) };
1294
+ });
1295
+ }
1296
+ nodes(limit = 200, offset = 0) {
1297
+ return this.maintenance.listNodes(limit, offset);
1298
+ }
1299
+ node(nodeId) {
1300
+ const node = this.maintenance.getNode(nodeId);
1301
+ if (!node) return void 0;
1302
+ return { node, revisions: this.maintenance.revisionsOf(nodeId), ancestors: this.maintenance.ancestorsOf(nodeId) };
1303
+ }
1304
+ source(sessionId, entryId, revision) {
1305
+ return this.ledger.readRawEntry(this.projectKey, sessionId, entryId, revision);
1306
+ }
1228
1307
  coverage() {
1229
1308
  if (!this.activeSessionId) return { active: 0, covered: 0 };
1230
- const frontier = this.maintenance.getFrontier(this.activeSessionId, this.context.sessionManager.getLeafId(), this.activeSources);
1231
- const covered = new Set(frontier.flatMap((node) => node.sources.map((source) => `${source.sessionId}:${source.entryId}:${source.revision}`)));
1309
+ const { covered } = this.frontierSnapshot();
1232
1310
  let hits = 0;
1233
1311
  for (const key of this.activeSources) if (covered.has(key)) hits += 1;
1234
1312
  return { active: this.activeSources.size, covered: hits };