omp-fabric 1.4.5 → 1.5.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/chunks/{chunk-TR3LQHO7.js → chunk-2ZEMWIHZ.js} +2 -2
  3. package/dist/chunks/chunk-3XPR2AZL.js +74 -0
  4. package/dist/chunks/chunk-3XPR2AZL.js.map +7 -0
  5. package/dist/chunks/{chunk-J6DWC5CX.js → chunk-4MVTUMKO.js} +2 -2
  6. package/dist/chunks/{chunk-SG4GVSWX.js → chunk-EQSL47QL.js} +10 -66
  7. package/dist/chunks/chunk-EQSL47QL.js.map +7 -0
  8. package/dist/chunks/{chunk-PQE42AW4.js → chunk-IGNMZZQH.js} +2 -2
  9. package/dist/chunks/{chunk-LB2EC7FZ.js → chunk-JMYHQUJQ.js} +9 -105
  10. package/dist/chunks/chunk-JMYHQUJQ.js.map +7 -0
  11. package/dist/chunks/{chunk-6JPXLXDS.js → chunk-KHQJKGBC.js} +2 -2
  12. package/dist/chunks/{chunk-6JPXLXDS.js.map → chunk-KHQJKGBC.js.map} +1 -1
  13. package/dist/chunks/chunk-KTB4R3E3.js +51 -0
  14. package/dist/chunks/chunk-KTB4R3E3.js.map +7 -0
  15. package/dist/chunks/chunk-TQQPXV3I.js +58 -0
  16. package/dist/chunks/chunk-TQQPXV3I.js.map +7 -0
  17. package/dist/chunks/chunk-ZDWBTQB6.js +107 -0
  18. package/dist/chunks/chunk-ZDWBTQB6.js.map +7 -0
  19. package/dist/compaction/hook.js +2 -1
  20. package/dist/compaction/lcm-loader.d.ts +12 -0
  21. package/dist/compaction/lcm-loader.d.ts.map +1 -0
  22. package/dist/compaction/lcm-maintenance.d.ts +2 -1
  23. package/dist/compaction/lcm-maintenance.d.ts.map +1 -1
  24. package/dist/compaction/lcm-runtime.js +1169 -0
  25. package/dist/compaction/lcm-runtime.js.map +7 -0
  26. package/dist/fabric-runtime-state.js +8 -6
  27. package/dist/fabric-runtime-state.js.map +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +133 -989
  30. package/dist/index.js.map +4 -4
  31. package/dist/memory/lcm-adapter.d.ts +1 -1
  32. package/dist/memory/lcm-adapter.d.ts.map +1 -1
  33. package/dist/providers/memory-provider.js +2 -2
  34. package/dist/residency/host.js +7 -5
  35. package/dist/residency/omp-entry.js +7 -5
  36. package/dist/residency/omp-entry.js.map +1 -1
  37. package/dist/storage/lcm-identity.d.ts +43 -0
  38. package/dist/storage/lcm-identity.d.ts.map +1 -0
  39. package/dist/storage/lcm-ledger.d.ts +7 -46
  40. package/dist/storage/lcm-ledger.d.ts.map +1 -1
  41. package/dist/storage/sqlite.d.ts +19 -0
  42. package/dist/storage/sqlite.d.ts.map +1 -0
  43. package/dist/ui/dashboard.js +3 -3
  44. package/dist/ui/settings.js +3 -3
  45. package/package.json +1 -1
  46. package/dist/chunks/chunk-LB2EC7FZ.js.map +0 -7
  47. package/dist/chunks/chunk-MBOXJKF6.js +0 -311
  48. package/dist/chunks/chunk-MBOXJKF6.js.map +0 -7
  49. package/dist/chunks/chunk-SG4GVSWX.js.map +0 -7
  50. /package/dist/chunks/{chunk-TR3LQHO7.js.map → chunk-2ZEMWIHZ.js.map} +0 -0
  51. /package/dist/chunks/{chunk-J6DWC5CX.js.map → chunk-4MVTUMKO.js.map} +0 -0
  52. /package/dist/chunks/{chunk-PQE42AW4.js.map → chunk-IGNMZZQH.js.map} +0 -0
package/dist/index.js CHANGED
@@ -46,6 +46,9 @@ import {
46
46
  formatTokens,
47
47
  safeText
48
48
  } from "./chunks/chunk-KIH3KLZU.js";
49
+ import {
50
+ loadSqliteDriver
51
+ } from "./chunks/chunk-TQQPXV3I.js";
49
52
  import {
50
53
  ApprovalController,
51
54
  CapturedToolCatalog,
@@ -67,7 +70,7 @@ import {
67
70
  resolveFabricIdentity,
68
71
  settleInPlacePrewalk,
69
72
  streamedToolCallName
70
- } from "./chunks/chunk-TR3LQHO7.js";
73
+ } from "./chunks/chunk-2ZEMWIHZ.js";
71
74
  import "./chunks/chunk-7VIQNUPS.js";
72
75
  import {
73
76
  AgentTranscriptReader
@@ -168,10 +171,7 @@ import {
168
171
  replaySuccessfulCallsAsync,
169
172
  schemaDigest
170
173
  } from "./chunks/chunk-4A4MVZQW.js";
171
- import {
172
- sessionDirNamesForCwd,
173
- sessionsDirRoot
174
- } from "./chunks/chunk-FX3XLQYI.js";
174
+ import "./chunks/chunk-FX3XLQYI.js";
175
175
  import "./chunks/chunk-6QBPSZJ5.js";
176
176
  import {
177
177
  FABRIC_COMPONENT_DISCOVER_EVENT,
@@ -189,12 +189,9 @@ import {
189
189
  readFabricToolResultProxyDetailsV1
190
190
  } from "./chunks/chunk-RBA54WC7.js";
191
191
  import {
192
- LcmModelAdapter,
193
192
  awaitPeerSettle,
194
- buildLcmPrompt,
195
- buildPeerCards,
196
- emergencyReduce
197
- } from "./chunks/chunk-LB2EC7FZ.js";
193
+ buildPeerCards
194
+ } from "./chunks/chunk-JMYHQUJQ.js";
198
195
  import {
199
196
  DEFAULT_FABRIC_CONFIG,
200
197
  effectiveToolCaptureConfig,
@@ -209,13 +206,10 @@ import {
209
206
  import {
210
207
  defaultCodePreviewSettings
211
208
  } from "./chunks/chunk-JSBTB3P6.js";
209
+ import "./chunks/chunk-ZDWBTQB6.js";
212
210
  import {
213
- LcmLedger,
214
- canonicalLcmPayload,
215
- canonicalProjectIdentity,
216
- defaultLedgerPath,
217
- hashLcmPayload
218
- } from "./chunks/chunk-MBOXJKF6.js";
211
+ canonicalProjectIdentity
212
+ } from "./chunks/chunk-KTB4R3E3.js";
219
213
  import {
220
214
  FABRIC_EXECUTION_GUIDANCE_SLOT,
221
215
  MAX_FABRIC_MODEL_GUIDANCE_CONTENT_CHARS,
@@ -234,10 +228,11 @@ import {
234
228
  fabricExecTitleHintCached,
235
229
  normalizeRunDisplay,
236
230
  registerCompactionHook
237
- } from "./chunks/chunk-SG4GVSWX.js";
231
+ } from "./chunks/chunk-EQSL47QL.js";
238
232
  import {
239
233
  fabricWriteBindings
240
234
  } from "./chunks/chunk-3J7BKUNG.js";
235
+ import "./chunks/chunk-3XPR2AZL.js";
241
236
  import "./chunks/chunk-4HQDSTHN.js";
242
237
  import {
243
238
  FABRIC_EXECUTION_TRACE_KIND,
@@ -276,13 +271,13 @@ var clearTimingInterval = (state) => {
276
271
  };
277
272
  var ensureTimingInterval = (state, invalidate) => {
278
273
  state.codePreviewTimingInterval ??= setInterval(() => {
279
- const token2 = (state.codePreviewTimingOnlyRenderToken ?? 0) + 1;
280
- state.codePreviewTimingOnlyRenderToken = token2;
274
+ const token = (state.codePreviewTimingOnlyRenderToken ?? 0) + 1;
275
+ state.codePreviewTimingOnlyRenderToken = token;
281
276
  try {
282
277
  invalidate();
283
278
  } finally {
284
279
  queueMicrotask(() => {
285
- if (state.codePreviewTimingOnlyRenderToken === token2) {
280
+ if (state.codePreviewTimingOnlyRenderToken === token) {
286
281
  delete state.codePreviewTimingOnlyRenderToken;
287
282
  }
288
283
  });
@@ -818,10 +813,10 @@ var extractContentText = (content) => {
818
813
  };
819
814
  var summarizeLogLine = (entry) => {
820
815
  if (typeof entry !== "object" || entry === null) return truncateMiddle(String(entry), 200);
821
- const record2 = entry;
822
- const type = typeof record2.type === "string" ? record2.type : void 0;
823
- const tool = typeof record2.toolName === "string" ? record2.toolName : void 0;
824
- const msg = record2.message;
816
+ const record = entry;
817
+ const type = typeof record.type === "string" ? record.type : void 0;
818
+ const tool = typeof record.toolName === "string" ? record.toolName : void 0;
819
+ const msg = record.message;
825
820
  if (typeof msg === "object" && msg !== null && !Array.isArray(msg)) {
826
821
  const m = msg;
827
822
  const role = typeof m.role === "string" ? m.role : "message";
@@ -833,13 +828,13 @@ var summarizeLogLine = (entry) => {
833
828
  if (type) {
834
829
  const bits = [type];
835
830
  if (tool) bits.push(tool);
836
- const model = typeof record2.modelId === "string" ? record2.modelId : void 0;
837
- const provider = typeof record2.provider === "string" && !model ? record2.provider : void 0;
831
+ const model = typeof record.modelId === "string" ? record.modelId : void 0;
832
+ const provider = typeof record.provider === "string" && !model ? record.provider : void 0;
838
833
  if (provider) bits.push(provider);
839
834
  if (model) bits.push(model);
840
835
  return bits.join(" ");
841
836
  }
842
- return truncateMiddle(JSON.stringify(record2), 160);
837
+ return truncateMiddle(JSON.stringify(record), 160);
843
838
  };
844
839
  var resolvePrewalkModel = async (state, context) => {
845
840
  const configured = state.config.prewalk.model?.trim();
@@ -1856,886 +1851,33 @@ var FabricSpeculationWarmup = class {
1856
1851
  }
1857
1852
  };
1858
1853
 
1859
- // src/storage/lcm-migration.ts
1860
- import crypto from "node:crypto";
1861
- import fs2 from "node:fs";
1862
- import path3 from "node:path";
1863
- import { DatabaseSync } from "node:sqlite";
1864
- var hash = (data) => crypto.createHash("sha256").update(data).digest("hex");
1865
- var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1866
- var nonblank = (value) => typeof value === "string" && value.trim().length > 0;
1867
- var utc = (value) => {
1868
- 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");
1869
- const at = Date.parse(value);
1870
- const normalized = value.replace("+00:00", "Z").replace(/Z$/, "");
1871
- if (!Number.isFinite(at) || new Date(at).toISOString().slice(0, 19) !== normalized.slice(0, 19)) throw new Error("invalid UTC timestamp");
1872
- return at;
1873
- };
1874
- function migrationWindow(options) {
1875
- const now = options.now ?? Date.now();
1876
- const since = options.since === void 0 ? now - 72 * 60 * 60 * 1e3 : utc(options.since);
1877
- const until = options.until === void 0 ? now : utc(options.until);
1878
- if (!Number.isFinite(since) || !Number.isFinite(until) || since > until) throw new Error("invalid migration window");
1879
- return { since, until };
1880
- }
1881
- var positive = (value, fallback) => {
1882
- const result = value ?? fallback;
1883
- if (!Number.isSafeInteger(result) || result < 1) throw new Error("scan limits must be positive safe integers");
1884
- return result;
1885
- };
1886
- function discoverMigrationSessions(options) {
1887
- const maxFiles = positive(options.maxFiles, 1e4);
1888
- const maxEntries = positive(options.maxDiscoveryEntries, 1e5);
1889
- const files = /* @__PURE__ */ new Set();
1890
- let incomplete = 0;
1891
- let entries = 0;
1892
- const add = (file) => {
1893
- if (files.size >= maxFiles && !files.has(path3.resolve(file))) {
1894
- incomplete++;
1895
- return;
1896
- }
1897
- files.add(path3.resolve(file));
1898
- };
1899
- if (options.files) {
1900
- for (const file of options.files.slice(0, maxFiles)) add(file);
1901
- if (options.files.length > maxFiles) incomplete++;
1902
- return { files: [...files].sort(), incomplete };
1903
- }
1904
- const list = (dir, visit) => {
1905
- let handle;
1906
- try {
1907
- handle = fs2.opendirSync(dir);
1908
- let entry;
1909
- while ((entry = handle.readSync()) !== null) {
1910
- if (++entries > maxEntries) {
1911
- incomplete++;
1912
- break;
1913
- }
1914
- visit(entry);
1915
- }
1916
- } catch {
1917
- incomplete++;
1918
- } finally {
1919
- handle?.closeSync();
1920
- }
1921
- };
1922
- const scan = (dir) => list(dir, (entry) => {
1923
- if (entry.isFile() && entry.name.endsWith(".jsonl")) add(path3.join(dir, entry.name));
1924
- else if (entry.isSymbolicLink() && entry.name.endsWith(".jsonl")) incomplete++;
1925
- });
1926
- if (options.candidateDirs) {
1927
- for (const dir of options.candidateDirs) {
1928
- if (entries >= maxEntries) {
1929
- incomplete++;
1930
- break;
1931
- }
1932
- scan(dir);
1933
- }
1934
- } else if (options.projectCwd) {
1935
- const names = sessionDirNamesForCwd(options.projectCwd);
1936
- let found = false;
1937
- for (const name of [names.canonical, ...names.legacy]) {
1938
- const dir = path3.join(sessionsDirRoot(options.agentDir), name);
1854
+ // src/compaction/lcm-loader.ts
1855
+ var LCM_SQLITE_NOTICE = "omp-fabric LCM compaction needs a SQLite driver, which this runtime does not provide";
1856
+ var createLcmRuntimeLoader = (importRuntime = () => import("./compaction/lcm-runtime.js"), loadSqlite = loadSqliteDriver) => {
1857
+ let unavailable = false;
1858
+ return {
1859
+ get unavailable() {
1860
+ return unavailable;
1861
+ },
1862
+ async load(context) {
1863
+ if (unavailable) return void 0;
1939
1864
  try {
1940
- if (fs2.statSync(dir).isDirectory()) {
1941
- found = true;
1942
- scan(dir);
1943
- }
1865
+ await loadSqlite();
1866
+ return (await importRuntime()).LcmRuntime;
1944
1867
  } catch (error) {
1945
- if (error.code !== "ENOENT") incomplete++;
1868
+ unavailable = true;
1869
+ const detail = error instanceof Error ? error.message : String(error);
1870
+ const notice = `${LCM_SQLITE_NOTICE} (${detail}). Compaction falls back to OMP core.`;
1871
+ console.warn(`[omp-fabric] ${notice}`);
1872
+ if (context.hasUI) context.ui.notify(notice, "warning");
1873
+ return void 0;
1946
1874
  }
1947
1875
  }
1948
- if (!found) incomplete++;
1949
- } else {
1950
- const root = sessionsDirRoot(options.agentDir);
1951
- list(root, (entry) => {
1952
- if (entry.isDirectory()) scan(path3.join(root, entry.name));
1953
- else if (entry.isFile() && entry.name.endsWith(".jsonl")) add(path3.join(root, entry.name));
1954
- else if (entry.isSymbolicLink()) incomplete++;
1955
- });
1956
- }
1957
- return { files: [...files].sort(), incomplete };
1958
- }
1959
- var CHECKPOINT_SCHEMA = "CREATE TABLE IF NOT EXISTS migration_checkpoints (project_key TEXT NOT NULL, session_path TEXT NOT NULL, source_hash TEXT NOT NULL, line_ordinal INTEGER NOT NULL, entry_id TEXT NOT NULL, content_hash TEXT NOT NULL, PRIMARY KEY(project_key,session_path,source_hash,line_ordinal,entry_id,content_hash))";
1960
- function reconcileSession(options) {
1961
- return migrateSessions({ ...options, onDemand: true });
1962
- }
1963
- function migrateSessions(options) {
1964
- const window = migrationWindow(options);
1965
- const maxFileBytes = positive(options.maxFileBytes, 64 * 1024 ** 2);
1966
- const maxLineBytes = positive(options.maxLineBytes, 8 * 1024 ** 2);
1967
- const maxTotalBytes = positive(options.maxTotalBytes, 1024 ** 3);
1968
- const discovery = discoverMigrationSessions(options);
1969
- const result = {
1970
- mode: options.apply ? "apply" : "dry-run",
1971
- since: new Date(window.since).toISOString(),
1972
- until: new Date(window.until).toISOString(),
1973
- counts: { eligible: 0, imported: 0, skippedDuplicate: 0, skippedOutOfWindow: 0, malformed: 0, oversized: 0, incompleteDiscovery: discovery.incomplete, errors: 0 },
1974
- filesScanned: 0,
1975
- bytesScanned: 0,
1976
- generations: [],
1977
- exitCode: 0
1978
1876
  };
1979
- const counts = result.counts;
1980
- const drySeen = /* @__PURE__ */ new Set();
1981
- for (const file of discovery.files) {
1982
- let data;
1983
- let fd;
1984
- try {
1985
- fd = fs2.openSync(file, "r");
1986
- const before = fs2.fstatSync(fd);
1987
- if (!before.isFile()) {
1988
- counts.incompleteDiscovery++;
1989
- continue;
1990
- }
1991
- if (before.size > maxFileBytes) {
1992
- counts.oversized++;
1993
- continue;
1994
- }
1995
- if (result.bytesScanned + before.size > maxTotalBytes) {
1996
- counts.incompleteDiscovery++;
1997
- break;
1998
- }
1999
- data = Buffer.allocUnsafe(before.size);
2000
- let offset = 0;
2001
- while (offset < data.length) {
2002
- const size = fs2.readSync(fd, data, offset, data.length - offset, offset);
2003
- if (size === 0) throw new Error("source shortened during read");
2004
- offset += size;
2005
- }
2006
- const after = fs2.fstatSync(fd);
2007
- if (after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs) throw new Error("source changed during read");
2008
- } catch {
2009
- counts.errors++;
2010
- counts.incompleteDiscovery++;
2011
- continue;
2012
- } finally {
2013
- if (fd !== void 0) fs2.closeSync(fd);
2014
- }
2015
- result.filesScanned++;
2016
- result.bytesScanned += data.length;
2017
- const sourceHash = hash(data);
2018
- result.generations.push({ sessionPath: file, sourceHash });
2019
- let header;
2020
- let ledger;
2021
- let database;
2022
- let sessionId = "";
2023
- let projectKey = "";
2024
- let cwd;
2025
- let ordinal = 0;
2026
- try {
2027
- for (let start = 0; start < data.length; ) {
2028
- ordinal++;
2029
- const newline = data.indexOf(10, start);
2030
- const end = newline < 0 ? data.length : newline;
2031
- const bytes = data.subarray(start, end);
2032
- start = newline < 0 ? data.length : end + 1;
2033
- if (bytes.length > maxLineBytes) {
2034
- counts.oversized++;
2035
- continue;
2036
- }
2037
- const content = bytes.toString("utf8").replace(/\r$/, "");
2038
- if (!content.trim()) continue;
2039
- let row;
2040
- try {
2041
- row = record(JSON.parse(content));
2042
- } catch {
2043
- counts.malformed++;
2044
- continue;
2045
- }
2046
- if (!row) {
2047
- counts.malformed++;
2048
- continue;
2049
- }
2050
- if (row.type === "message_end") continue;
2051
- if (!header) {
2052
- if (row.type !== "session") {
2053
- if (record(row.message)) counts.malformed++;
2054
- continue;
2055
- }
2056
- if (!nonblank(row.id)) {
2057
- counts.malformed++;
2058
- break;
2059
- }
2060
- header = row;
2061
- sessionId = row.id;
2062
- cwd = nonblank(row.cwd) ? row.cwd : options.liveCwd;
2063
- if (!cwd) {
2064
- counts.malformed++;
2065
- break;
2066
- }
2067
- const projectInput = options.liveCwd ? { recordedCwd: cwd, liveCwd: options.liveCwd } : { recordedCwd: cwd };
2068
- projectKey = canonicalProjectIdentity(projectInput).key;
2069
- if (options.ledger && options.ledger.project.key !== projectKey) {
2070
- counts.errors++;
2071
- break;
2072
- }
2073
- if (options.apply) {
2074
- if (options.ledger) ledger = options.ledger;
2075
- else if (options.ledgerRoot) ledger = new LcmLedger({ rootDir: options.ledgerRoot, project: { recordedCwd: cwd } });
2076
- else ledger = new LcmLedger({ project: { recordedCwd: cwd } });
2077
- database = ledger.db;
2078
- database.exec(CHECKPOINT_SCHEMA);
2079
- } else if (options.ledger) database = options.ledger.db;
2080
- else {
2081
- const dbPath = defaultLedgerPath(options.ledgerRoot, projectKey);
2082
- if (fs2.existsSync(dbPath)) database = new DatabaseSync(dbPath, { readOnly: true });
2083
- }
2084
- continue;
2085
- }
2086
- if (row.type === "session" || !nonblank(row.type) || !nonblank(row.id) || row.parentId !== null && row.parentId !== void 0 && typeof row.parentId !== "string") {
2087
- counts.malformed++;
2088
- continue;
2089
- }
2090
- const message = record(row.message);
2091
- if (row.type === "message" && (!message || !nonblank(message.role))) {
2092
- counts.malformed++;
2093
- continue;
2094
- }
2095
- let at;
2096
- try {
2097
- at = typeof row.timestamp === "string" ? utc(row.timestamp) : NaN;
2098
- } catch {
2099
- at = NaN;
2100
- }
2101
- if (!Number.isFinite(at)) {
2102
- counts.malformed++;
2103
- continue;
2104
- }
2105
- if (!options.onDemand && (at < window.since || at > window.until)) {
2106
- counts.skippedOutOfWindow++;
2107
- continue;
2108
- }
2109
- counts.eligible++;
2110
- const payloadJson = canonicalLcmPayload(row);
2111
- const contentHash = hashLcmPayload(row);
2112
- const identity = JSON.stringify([projectKey, sessionId, row.id, contentHash]);
2113
- const progress = { projectKey, sessionPath: file, sourceHash, lineOrdinal: ordinal, entryId: row.id, contentHash };
2114
- const duplicate = drySeen.has(identity) || database?.prepare("SELECT 1 FROM raw_entries WHERE project_key=? AND session_id=? AND entry_id=? AND content_hash=?").get(projectKey, sessionId, row.id, contentHash) !== void 0;
2115
- if (duplicate) counts.skippedDuplicate++;
2116
- else if (ledger) {
2117
- ledger.appendRaw({
2118
- projectKey,
2119
- sessionId,
2120
- entryId: row.id,
2121
- role: message && typeof message.role === "string" ? message.role : row.type,
2122
- content,
2123
- payloadJson,
2124
- parentEntryId: typeof row.parentId === "string" ? row.parentId : null,
2125
- branch: typeof row.branch === "string" ? row.branch : typeof row.branchId === "string" ? row.branchId : null,
2126
- ...cwd ? { recordedCwd: cwd } : {},
2127
- createdAt: at
2128
- });
2129
- counts.imported++;
2130
- } else if (!duplicate) counts.imported++;
2131
- drySeen.add(identity);
2132
- if (ledger) database.prepare("INSERT OR IGNORE INTO migration_checkpoints(project_key,session_path,source_hash,line_ordinal,entry_id,content_hash) VALUES(?,?,?,?,?,?)").run(projectKey, file, sourceHash, ordinal, row.id, contentHash);
2133
- options.onProgress?.(progress);
2134
- }
2135
- if (!header) counts.malformed++;
2136
- } catch (error) {
2137
- counts.errors++;
2138
- if (options.onProgress) throw error;
2139
- } finally {
2140
- if (ledger && ledger !== options.ledger) ledger.close();
2141
- else if (!ledger && database && database !== options.ledger?.db) database.close();
2142
- }
2143
- }
2144
- result.exitCode = counts.errors || counts.malformed || counts.oversized || counts.incompleteDiscovery && !options.allowIncompleteDiscovery ? 1 : 0;
2145
- return result;
2146
- }
2147
-
2148
- // src/compaction/lcm-maintenance.ts
2149
- import crypto2 from "node:crypto";
2150
- var DEFAULT_LCM_BUDGET = { calls: 32, inputTokens: 2e6, outputTokens: 128e3, cost: Number.POSITIVE_INFINITY, wallMs: 6e4, sessionCalls: 16 };
2151
- var hash2 = (v) => hashLcmPayload(v);
2152
- var parse = (v) => JSON.parse(String(v));
2153
- var rawHash = (e) => e.payloadHash;
2154
- var token = () => crypto2.randomUUID();
2155
- var LcmMaintenance = class {
2156
- constructor(ledger, options = {}) {
2157
- this.ledger = ledger;
2158
- this.projectKey = ledger.project.key;
2159
- this.now = options.now ?? Date.now;
2160
- this.ownerId = options.ownerId ?? crypto2.randomUUID();
2161
- this.policyHash = options.policyHash ?? hash2("lcm-policy-v1");
2162
- this.maxLeaf = options.maxLeafEntries ?? 8;
2163
- this.maxChildren = options.maxCondenseChildren ?? 4;
2164
- this.maxInputChars = options.maxInputChars ?? 48e3;
2165
- this.maxOutputChars = options.maxOutputChars ?? 4096;
2166
- this.budget = { ...DEFAULT_LCM_BUDGET, ...options.budget };
2167
- }
2168
- projectKey;
2169
- now;
2170
- ownerId;
2171
- policyHash;
2172
- maxLeaf;
2173
- maxChildren;
2174
- maxInputChars;
2175
- maxOutputChars;
2176
- budget;
2177
- listNodes(limit = 100) {
2178
- 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) => {
2179
- const node = parse(r.payload);
2180
- if (node.projectKey !== this.projectKey) throw new Error("node project does not match ledger project");
2181
- return node;
2182
- }));
2183
- }
2184
- getNode(nodeId) {
2185
- return this.ledger.readOnly((db) => {
2186
- const row = db.prepare("SELECT payload FROM summary_nodes WHERE node_id=? AND project_key=?").get(nodeId, this.projectKey);
2187
- if (!row?.payload) return void 0;
2188
- const node = parse(row.payload);
2189
- if (node.projectKey !== this.projectKey) throw new Error("node project does not match ledger project");
2190
- return node;
2191
- });
2192
- }
2193
- getFrontier(sessionId, branch, activeSources) {
2194
- const nodes = this.listNodes(1e5).filter((n) => n.state === "ready" && n.policyHash === this.policyHash && (!sessionId || n.sessionId === sessionId) && (branch === void 0 || n.branch === branch || n.branch === null) && (!activeSources || n.sources.every((s) => activeSources.has(this.sourceKey(s)))));
2195
- const covered = new Set(nodes.flatMap((n) => n.children));
2196
- return nodes.filter((n) => !covered.has(n.nodeId));
2197
- }
2198
- listJobs(limit = 100) {
2199
- return this.ledger.readOnly((db) => db.prepare("SELECT payload FROM maintenance_jobs WHERE project_key=? ORDER BY created_at,job_id LIMIT ?").all(this.projectKey, limit).map((r) => {
2200
- const job = parse(r.payload);
2201
- if (job.projectKey !== this.projectKey) throw new Error("job project does not match ledger project");
2202
- return job;
2203
- }));
2204
- }
2205
- selectLeaf(entries = this.ledger.readRaw(this.projectKey), branch) {
2206
- const first = entries[0];
2207
- if (!first) return [];
2208
- const nodes = this.listNodes(1e5).filter((n) => n.state !== "failed" && n.policyHash === this.policyHash && n.sessionId === first.sessionId && (branch === void 0 || n.branch === branch || n.branch === null));
2209
- const covered = new Set(nodes.flatMap((n) => n.sources.map((s) => this.sourceKey(s))));
2210
- return entries.filter((e) => e.sessionId === first.sessionId && (branch === void 0 || e.branch === branch || e.branch === null) && !covered.has(this.sourceKey(e))).slice(0, this.maxLeaf);
2211
- }
2212
- selectCondensation(sessionId, activeSources, branch) {
2213
- const nodes = this.listNodes(1e5).filter((n) => n.state === "ready" && n.policyHash === this.policyHash && (!sessionId || n.sessionId === sessionId) && (!activeSources || n.sources.every((s) => activeSources.has(this.sourceKey(s)))) && (branch === void 0 || n.branch === branch || n.branch === null));
2214
- const eligible = new Set(nodes.map((n) => n.nodeId));
2215
- const edges = this.ledger.readOnly((db) => db.prepare("SELECT e.parent_id,e.child_id FROM summary_edges e JOIN summary_nodes parent ON parent.node_id=e.parent_id AND parent.project_key=? JOIN summary_nodes child ON child.node_id=e.child_id AND child.project_key=parent.project_key WHERE parent.project_key=?").all(this.projectKey, this.projectKey));
2216
- const consumed = new Set(edges.filter((e) => eligible.has(e.parent_id)).map((e) => e.child_id));
2217
- const candidates = nodes.filter((n) => !consumed.has(n.nodeId));
2218
- const first = candidates[0];
2219
- if (!first) return [];
2220
- const depth = Math.min(...candidates.map((n) => n.depth));
2221
- const sameDepth = candidates.filter((n) => n.depth === depth);
2222
- if (depth > 0 && sameDepth.length < 2) return [];
2223
- return candidates.filter((n) => n.projectKey === first.projectKey && n.sessionId === first.sessionId && n.depth === depth).sort((a, b) => a.nodeId.localeCompare(b.nodeId)).slice(0, this.maxChildren);
2224
- }
2225
- createLeaf(entries = this.selectLeaf()) {
2226
- if (!entries.length) return void 0;
2227
- const branches = new Set(entries.map((entry) => entry.branch).filter((value) => typeof value === "string"));
2228
- if (branches.size > 1) throw new Error("cross-branch ranges");
2229
- const branch = [...branches][0] ?? null;
2230
- const source = entries.map((e, index) => {
2231
- if (e.projectKey !== this.projectKey) throw new Error("entry project does not match ledger project");
2232
- const payload = parse(e.payloadJson);
2233
- if (typeof payload.type !== "string" || !payload.type || payload.id !== e.entryId) throw new Error("invalid raw payload");
2234
- if (hash2(payload) !== rawHash(e)) throw new Error("payload hash mismatch");
2235
- return { sessionId: e.sessionId, entryId: e.entryId, revision: e.revision, payloadHash: rawHash(e) };
2236
- });
2237
- this.validateSources(source);
2238
- const node = { nodeId: `leaf:${hash2({ projectKey: this.projectKey, source, branch, policyHash: this.policyHash })}`, projectKey: this.projectKey, sessionId: entries[0].sessionId, branch, kind: "leaf", sources: source, children: [], depth: 0, sourceHash: hash2(source.map((s) => s.payloadHash)), policyHash: this.policyHash, modelHash: "", state: "pending", createdAt: this.now() };
2239
- this.publish(node);
2240
- return node;
2241
- }
2242
- createCondensed(children = this.selectCondensation()) {
2243
- if (!children.length) return void 0;
2244
- if (children.some((c) => c.state !== "ready")) throw new Error("condensation requires ready children");
2245
- if (new Set(children.map((c) => c.sessionId)).size !== 1 || new Set(children.map((c) => c.depth)).size !== 1) throw new Error("cross-branch ranges");
2246
- const storedChildren = children.map((child) => this.getNode(child.nodeId));
2247
- if (storedChildren.some((child) => !child || child.projectKey !== this.projectKey)) throw new Error("child project does not match ledger project");
2248
- if (storedChildren.some((child, index) => JSON.stringify(child) !== JSON.stringify(children[index]))) throw new Error("child payload changed");
2249
- const concreteBranches = new Set(storedChildren.map((c) => c.branch).filter((value) => typeof value === "string"));
2250
- if (concreteBranches.size > 1) throw new Error("cross-branch ranges");
2251
- const branch = [...concreteBranches][0] ?? null;
2252
- const source = [...new Map(storedChildren.flatMap((c) => c.sources).map((value) => [this.sourceKey(value), value])).values()];
2253
- this.validateSources(source);
2254
- const node = { nodeId: `condensed:${hash2({ projectKey: this.projectKey, children: storedChildren.map((c) => c.nodeId), branch, policyHash: this.policyHash })}`, projectKey: this.projectKey, sessionId: children[0].sessionId, branch, kind: "condensed", sources: source, children: children.map((c) => c.nodeId), depth: Math.max(...children.map((c) => c.depth)) + 1, sourceHash: hash2(children.map((c) => c.sourceHash)), policyHash: this.policyHash, modelHash: "", state: "pending", createdAt: this.now() };
2255
- this.publish(node);
2256
- return node;
2257
- }
2258
- validateSources(source) {
2259
- const seen = /* @__PURE__ */ new Set();
2260
- for (const s of source) {
2261
- if (seen.has(this.sourceKey(s))) throw new Error("duplicate source identity");
2262
- seen.add(this.sourceKey(s));
2263
- }
2264
- if (new Set(source.map((s) => s.sessionId)).size !== 1) throw new Error("cross-session ranges");
2265
- }
2266
- sourceKey(source) {
2267
- return `${source.sessionId}:${source.entryId}:${source.revision}`;
2268
- }
2269
- publish(node) {
2270
- this.ledger.transaction((db) => {
2271
- if (node.children.includes(node.nodeId)) throw new Error("cycle");
2272
- for (const child of node.children) if (!db.prepare("SELECT 1 FROM summary_nodes WHERE node_id=? AND project_key=?").get(child, this.projectKey)) throw new Error("missing child");
2273
- db.prepare("INSERT OR IGNORE INTO summary_nodes(node_id,project_key,payload,created_at) VALUES(?,?,?,?)").run(node.nodeId, this.projectKey, JSON.stringify(node), node.createdAt);
2274
- for (const child of node.children) db.prepare("INSERT OR IGNORE INTO summary_edges(parent_id,child_id) VALUES(?,?)").run(node.nodeId, child);
2275
- const job = this.job(node);
2276
- db.prepare("INSERT OR IGNORE INTO maintenance_jobs(job_id,project_key,status,payload,created_at,updated_at) VALUES(?,?,?,?,?,?)").run(job.jobId, this.projectKey, job.state, JSON.stringify(job), job.createdAt, job.updatedAt);
2277
- });
2278
- }
2279
- job(node) {
2280
- const t = this.now();
2281
- return { jobId: `job:${node.nodeId}`, projectKey: this.projectKey, nodeId: node.nodeId, priority: node.kind === "condensed" ? 20 : 10, eligibleAt: t, state: "pending", attempts: 0, nextRetryAt: t, createdAt: t, updatedAt: t };
2282
- }
2283
- claim(jobId, ownerId = this.ownerId) {
2284
- return this.ledger.transaction((db) => {
2285
- const row = db.prepare("SELECT payload FROM maintenance_jobs WHERE job_id=? AND project_key=?").get(jobId, this.projectKey);
2286
- if (!row?.payload) throw new Error("job not found");
2287
- const old = parse(row.payload);
2288
- if (old.projectKey !== this.projectKey) throw new Error("job project does not match ledger project");
2289
- const t = this.now();
2290
- const expired = old.state === "running" && (old.leaseUntil ?? 0) <= t;
2291
- if (old.state === "running" && !expired) throw new Error("job lease held");
2292
- if (old.state !== "pending" && !expired || old.eligibleAt > t || old.nextRetryAt > t) throw new Error("job not eligible");
2293
- const active = db.prepare("SELECT payload FROM maintenance_jobs WHERE project_key=? AND status=?").all(this.projectKey, "running").map((r) => parse(r.payload)).filter((j) => j.jobId !== old.jobId && (j.leaseUntil ?? 0) > t);
2294
- if (active.length) throw new Error("project lease held");
2295
- const nodeRow = db.prepare("SELECT payload FROM summary_nodes WHERE node_id=? AND project_key=?").get(old.nodeId, this.projectKey);
2296
- const sessionId = nodeRow?.payload ? parse(nodeRow.payload).sessionId : "";
2297
- const day = this.day(t);
2298
- const projectUsage = this.usage(db, day);
2299
- const sessionUsage = this.usage(db, day, sessionId);
2300
- if (projectUsage.calls >= this.budget.calls || projectUsage.inputTokens >= this.budget.inputTokens || projectUsage.outputTokens >= this.budget.outputTokens || projectUsage.cost >= this.budget.cost || projectUsage.wallMs >= this.budget.wallMs || sessionUsage.calls >= this.budget.sessionCalls) throw new Error("budget exhausted");
2301
- const out = { ...old, state: "running", ownerId, leaseToken: token(), leaseUntil: t + 3e4, updatedAt: t };
2302
- db.prepare("UPDATE maintenance_jobs SET status=?,payload=?,updated_at=? WHERE job_id=? AND project_key=?").run(out.state, JSON.stringify(out), t, jobId, this.projectKey);
2303
- return out;
2304
- });
2305
- }
2306
- claimEmergency(jobId, ownerId = this.ownerId) {
2307
- return this.ledger.transaction((db) => {
2308
- const row = db.prepare("SELECT payload FROM maintenance_jobs WHERE job_id=? AND project_key=?").get(jobId, this.projectKey);
2309
- if (!row?.payload) throw new Error("job not found");
2310
- const old = parse(row.payload);
2311
- if (old.projectKey !== this.projectKey) throw new Error("job project does not match ledger project");
2312
- const t = this.now();
2313
- const expired = old.state === "running" && (old.leaseUntil ?? 0) <= t;
2314
- if (old.state === "running" && !expired) throw new Error("job lease held");
2315
- if (old.state === "completed") throw new Error("job already completed");
2316
- const out = { ...old, state: "running", ownerId, leaseToken: token(), leaseUntil: t + 3e4, updatedAt: t };
2317
- db.prepare("UPDATE maintenance_jobs SET status=?,payload=?,updated_at=? WHERE job_id=? AND project_key=?").run(out.state, JSON.stringify(out), t, jobId, this.projectKey);
2318
- return out;
2319
- });
2320
- }
2321
- renew(job) {
2322
- return this.fenced(job, (old) => ({ ...old, leaseUntil: this.now() + 3e4, updatedAt: this.now() }));
2323
- }
2324
- validateText(text) {
2325
- if (typeof text !== "string" || text.length === 0) throw new Error("invalid summary text");
2326
- let length = 0;
2327
- for (const _ of text) {
2328
- length += 1;
2329
- if (length > this.maxOutputChars) throw new Error("summary exceeds output bound");
2330
- }
2331
- }
2332
- complete(job, result) {
2333
- this.validateText(result.text);
2334
- return this.ledger.transaction((db) => {
2335
- const current = this.readJob(db, job.jobId);
2336
- this.assertLease(current, job);
2337
- if (current.nodeId !== job.nodeId) throw new Error("job node mismatch");
2338
- const row = db.prepare("SELECT payload FROM summary_nodes WHERE node_id=? AND project_key=?").get(job.nodeId, this.projectKey);
2339
- if (!row?.payload) throw new Error("node not found");
2340
- const node = parse(row.payload);
2341
- if (node.projectKey !== this.projectKey || node.nodeId !== current.nodeId) throw new Error("node project does not match ledger project");
2342
- for (const source of node.sources) {
2343
- 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);
2344
- if (!raw?.payload_json || hash2(parse(raw.payload_json)) !== source.payloadHash || raw.session_id !== source.sessionId) throw new Error("stale source");
2345
- }
2346
- if (![result.inputTokens, result.outputTokens, result.cost, result.wallMs].every((value) => Number.isFinite(value) && value >= 0)) throw new Error("invalid model usage");
2347
- const projectUsage = this.usage(db, this.day(this.now()));
2348
- const sessionUsage = this.usage(db, this.day(this.now()), node.sessionId);
2349
- 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");
2350
- const out = { ...node, state: "ready", text: result.text, modelHash: result.modelHash };
2351
- this.account(db, node.sessionId, result);
2352
- db.prepare("UPDATE summary_nodes SET payload=? WHERE node_id=? AND project_key=?").run(JSON.stringify(out), node.nodeId, this.projectKey);
2353
- const { ownerId: _ownerId, leaseToken: _leaseToken, leaseUntil: _leaseUntil, ...base } = current;
2354
- const done = { ...base, state: "completed", updatedAt: this.now() };
2355
- db.prepare("UPDATE maintenance_jobs SET status=?,payload=?,updated_at=? WHERE job_id=? AND project_key=?").run(done.state, JSON.stringify(done), done.updatedAt, done.jobId, this.projectKey);
2356
- return out;
2357
- });
2358
- }
2359
- completeEmergency(job, text) {
2360
- this.validateText(text);
2361
- return this.ledger.transaction((db) => {
2362
- const current = this.readJob(db, job.jobId);
2363
- this.assertLease(current, job);
2364
- if (current.nodeId !== job.nodeId) throw new Error("job node mismatch");
2365
- const row = db.prepare("SELECT payload FROM summary_nodes WHERE node_id=? AND project_key=?").get(job.nodeId, this.projectKey);
2366
- if (!row?.payload) throw new Error("node not found");
2367
- const node = parse(row.payload);
2368
- if (node.projectKey !== this.projectKey || node.nodeId !== current.nodeId) throw new Error("node project does not match ledger project");
2369
- for (const source of node.sources) {
2370
- 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);
2371
- if (!raw?.payload_json || hash2(parse(raw.payload_json)) !== source.payloadHash || raw.session_id !== source.sessionId) throw new Error("stale source");
2372
- }
2373
- const out = { ...node, state: "ready", text, modelHash: "emergency" };
2374
- db.prepare("UPDATE summary_nodes SET payload=? WHERE node_id=? AND project_key=?").run(JSON.stringify(out), node.nodeId, this.projectKey);
2375
- const { ownerId: _ownerId, leaseToken: _leaseToken, leaseUntil: _leaseUntil, ...base } = current;
2376
- const done = { ...base, state: "completed", updatedAt: this.now() };
2377
- db.prepare("UPDATE maintenance_jobs SET status=?,payload=?,updated_at=? WHERE job_id=? AND project_key=?").run(done.state, JSON.stringify(done), done.updatedAt, done.jobId, this.projectKey);
2378
- return out;
2379
- });
2380
- }
2381
- fail(job, error) {
2382
- return this.fenced(job, (old) => {
2383
- const attempts = old.attempts + 1;
2384
- const delay = Math.min(9e5, 3e4 * 2 ** (attempts - 1));
2385
- const { ownerId: _ownerId, leaseToken: _leaseToken, leaseUntil: _leaseUntil, ...base } = old;
2386
- return { ...base, attempts, error, state: attempts >= 3 ? "failed" : "pending", eligibleAt: this.now() + delay, nextRetryAt: this.now() + delay, updatedAt: this.now() };
2387
- });
2388
- }
2389
- async run(job, model, input, signal = new AbortController().signal) {
2390
- let sources = [];
2391
- let claimed;
2392
- const controller = new AbortController();
2393
- let renew;
2394
- const timer = setTimeout(() => controller.abort(), 6e4);
2395
- const abort = () => controller.abort();
2396
- signal.addEventListener("abort", abort, { once: true });
2397
- try {
2398
- claimed = this.claim(job.jobId, job.ownerId ?? this.ownerId);
2399
- const node = this.getNode(claimed.nodeId);
2400
- sources = node?.sources ?? [];
2401
- renew = setInterval(() => {
2402
- try {
2403
- if (claimed) claimed = this.renew(claimed);
2404
- } catch {
2405
- }
2406
- }, 1e4);
2407
- const result = await Promise.race([model.generate({ prompt: buildLcmPrompt(node?.kind ?? "leaf", input, this.maxInputChars), sessionId: node?.sessionId ?? "", signal: controller.signal }), new Promise((_, reject) => controller.signal.addEventListener("abort", () => reject(new Error("LCM timeout")), { once: true }))]);
2408
- return this.complete(claimed, result);
2409
- } catch (error) {
2410
- if (!claimed) throw error;
2411
- const fallback = emergencyReduce(input, this.maxOutputChars, sources);
2412
- try {
2413
- return this.completeEmergency(claimed, fallback);
2414
- } catch {
2415
- try {
2416
- this.fail(claimed, String(error));
2417
- } catch {
2418
- }
2419
- throw error;
2420
- }
2421
- } finally {
2422
- clearTimeout(timer);
2423
- if (renew) clearInterval(renew);
2424
- signal.removeEventListener("abort", abort);
2425
- }
2426
- }
2427
- day(ms) {
2428
- return new Date(ms).toISOString().slice(0, 10);
2429
- }
2430
- usage(db, day, sessionId) {
2431
- const q = sessionId === void 0 ? "SELECT COALESCE(SUM(calls),0) calls,COALESCE(SUM(input_tokens),0) inputTokens,COALESCE(SUM(output_tokens),0) outputTokens,COALESCE(SUM(cost),0) cost,COALESCE(SUM(wall_ms),0) wallMs FROM maintenance_usage WHERE project_key=? AND day=?" : "SELECT COALESCE(SUM(calls),0) calls,COALESCE(SUM(input_tokens),0) inputTokens,COALESCE(SUM(output_tokens),0) outputTokens,COALESCE(SUM(cost),0) cost,COALESCE(SUM(wall_ms),0) wallMs FROM maintenance_usage WHERE project_key=? AND day=? AND session_id=?";
2432
- const r = sessionId === void 0 ? db.prepare(q).get(this.projectKey, day) : db.prepare(q).get(this.projectKey, day, sessionId);
2433
- return r;
2434
- }
2435
- account(db, sessionId, result) {
2436
- const d = this.day(this.now());
2437
- db.prepare("INSERT INTO maintenance_usage(project_key,day,session_id,calls,input_tokens,output_tokens,cost,wall_ms) VALUES(?,?,?,?,?,?,?,?) ON CONFLICT(project_key,day,session_id) DO UPDATE SET calls=calls+excluded.calls,input_tokens=input_tokens+excluded.input_tokens,output_tokens=output_tokens+excluded.output_tokens,cost=cost+excluded.cost,wall_ms=wall_ms+excluded.wall_ms").run(this.projectKey, d, sessionId, 1, result.inputTokens, result.outputTokens, result.cost, result.wallMs);
2438
- }
2439
- readJob(db, id) {
2440
- const row = db.prepare("SELECT payload FROM maintenance_jobs WHERE job_id=? AND project_key=?").get(id, this.projectKey);
2441
- if (!row) throw new Error("job not found");
2442
- const job = parse(row.payload);
2443
- if (job.projectKey !== this.projectKey) throw new Error("job project does not match ledger project");
2444
- return job;
2445
- }
2446
- assertLease(current, expected) {
2447
- if (current.ownerId !== expected.ownerId || current.leaseToken !== expected.leaseToken || current.state !== "running" || (current.leaseUntil ?? 0) < this.now()) throw new Error("lease fenced");
2448
- }
2449
- fenced(job, update) {
2450
- return this.ledger.transaction((db) => {
2451
- const old = this.readJob(db, job.jobId);
2452
- this.assertLease(old, job);
2453
- const out = update(old);
2454
- db.prepare("UPDATE maintenance_jobs SET status=?,payload=?,updated_at=? WHERE job_id=? AND project_key=?").run(out.state, JSON.stringify(out), out.updatedAt, out.jobId, this.projectKey);
2455
- return out;
2456
- });
2457
- }
2458
- };
2459
-
2460
- // src/compaction/lcm-runtime.ts
2461
- var LcmRuntime = class {
2462
- ledger;
2463
- maintenance;
2464
- context;
2465
- resolveOptions;
2466
- abort = new AbortController();
2467
- writePending = Promise.resolve();
2468
- maintenancePending = Promise.resolve();
2469
- closed = false;
2470
- dirty = false;
2471
- degradedError;
2472
- activeSources = /* @__PURE__ */ new Set();
2473
- activeSessionId;
2474
- constructor(context, options = {}) {
2475
- this.context = context;
2476
- this.resolveOptions = typeof options === "function" ? options : () => options;
2477
- const initial = this.resolveOptions();
2478
- const recorded = context.sessionManager.getRecordedCwd?.();
2479
- const liveCwd = recorded || context.cwd;
2480
- const project = canonicalProjectIdentity({ liveCwd });
2481
- const ledgerOptions = initial.rootDir === void 0 ? { project: { liveCwd: project.canonicalPath ?? liveCwd } } : { rootDir: initial.rootDir, project: { liveCwd: project.canonicalPath ?? liveCwd } };
2482
- this.ledger = new LcmLedger(ledgerOptions);
2483
- this.maintenance = new LcmMaintenance(this.ledger, {
2484
- ...initial,
2485
- ...initial.lcmMaxInputChars === void 0 ? {} : { maxInputChars: initial.lcmMaxInputChars },
2486
- ...initial.lcmMaxOutputChars === void 0 ? {} : { maxOutputChars: initial.lcmMaxOutputChars }
2487
- });
2488
- }
2489
- get options() {
2490
- return this.resolveOptions();
2491
- }
2492
- get projectKey() {
2493
- return this.ledger.project.key;
2494
- }
2495
- get signal() {
2496
- return this.abort.signal;
2497
- }
2498
- get status() {
2499
- return this.degradedError === void 0 ? "healthy" : "degraded";
2500
- }
2501
- get error() {
2502
- return this.degradedError;
2503
- }
2504
- markDirty() {
2505
- this.dirty = true;
2506
- }
2507
- enqueueWrite(operation) {
2508
- const run = this.writePending.then(operation);
2509
- this.writePending = run.catch((error) => {
2510
- this.degradedError = error;
2511
- });
2512
- return run;
2513
- }
2514
- refreshActiveSources(sessionId, entries) {
2515
- const ids = new Set(entries.map((entry) => entry.id));
2516
- const latest = /* @__PURE__ */ new Map();
2517
- for (const entry of this.ledger.readRaw(this.projectKey, sessionId)) {
2518
- if (!ids.has(entry.entryId)) continue;
2519
- const previous = latest.get(entry.entryId);
2520
- if (!previous || previous.revision < entry.revision) latest.set(entry.entryId, entry);
2521
- }
2522
- this.activeSources = new Set(
2523
- [...latest.values()].map((entry) => `${entry.sessionId}:${entry.entryId}:${entry.revision}`)
2524
- );
2525
- this.activeSessionId = sessionId;
2526
- }
2527
- reconcileSelectedSession() {
2528
- if (this.closed) return Promise.resolve();
2529
- return this.enqueueWrite(() => {
2530
- const getSessionFile = this.context.sessionManager.getSessionFile;
2531
- if (typeof getSessionFile !== "function") return;
2532
- const sessionFile = getSessionFile.call(this.context.sessionManager);
2533
- if (!sessionFile) return;
2534
- const recorded = this.context.sessionManager.getRecordedCwd?.();
2535
- const cwd = recorded || this.context.cwd;
2536
- const result = reconcileSession({
2537
- agentDir: this.options.rootDir ?? process.env.PI_CODING_AGENT_DIR ?? `${process.env.HOME ?? "."}/.omp/agent`,
2538
- ledger: this.ledger,
2539
- files: [sessionFile],
2540
- liveCwd: cwd,
2541
- projectCwd: cwd,
2542
- apply: true
2543
- });
2544
- this.degradedError = result.counts.errors > 0 ? new Error(`LCM session reconciliation reported ${result.counts.errors} error(s)`) : void 0;
2545
- const sessionId = this.context.sessionManager.getSessionId();
2546
- this.refreshActiveSources(sessionId, this.context.sessionManager.getBranch());
2547
- });
2548
- }
2549
- readback() {
2550
- if (this.closed) return Promise.resolve();
2551
- return this.enqueueWrite(() => {
2552
- if (this.closed) return;
2553
- const entries = this.context.sessionManager.getBranch();
2554
- const sessionId = this.context.sessionManager.getSessionId();
2555
- const branch = this.context.sessionManager.getLeafId();
2556
- const recorded = this.context.sessionManager.getRecordedCwd?.();
2557
- const active = /* @__PURE__ */ new Set();
2558
- for (const entry of entries) {
2559
- const payloadJson = canonicalLcmPayload(entry);
2560
- const message = entry.type === "message" ? entry.message : void 0;
2561
- const stored = this.ledger.appendRaw({
2562
- projectKey: this.projectKey,
2563
- sessionId,
2564
- entryId: entry.id,
2565
- role: message?.role ?? entry.type,
2566
- content: message ? JSON.stringify(message.content ?? "") : payloadJson,
2567
- payloadJson,
2568
- parentEntryId: entry.parentId ?? null,
2569
- branch,
2570
- ...recorded || this.context.cwd ? { recordedCwd: recorded || this.context.cwd } : {}
2571
- });
2572
- active.add(`${sessionId}:${stored.entryId}:${stored.revision}`);
2573
- }
2574
- this.activeSources = active;
2575
- this.activeSessionId = sessionId;
2576
- this.dirty = false;
2577
- this.degradedError = void 0;
2578
- });
2579
- }
2580
- async syncAndSchedule() {
2581
- if (this.closed) return;
2582
- await this.readback();
2583
- this.scheduleMaintenance();
2584
- }
2585
- maintain() {
2586
- return this.syncAndSchedule();
2587
- }
2588
- scheduleMaintenance() {
2589
- if (this.closed) return;
2590
- const run = this.maintenancePending.then(() => this.runMaintenance());
2591
- this.maintenancePending = run.catch((error) => {
2592
- this.degradedError = error;
2593
- });
2594
- }
2595
- async runMaintenance() {
2596
- if (this.closed || !this.activeSessionId || this.activeSources.size === 0) return;
2597
- let model;
2598
- try {
2599
- model = new LcmModelAdapter(this.context, this.options.summaryModel, true, {
2600
- ...this.options.lcmMaxInputChars === void 0 ? {} : { maxInputChars: this.options.lcmMaxInputChars },
2601
- ...this.options.lcmMaxOutputTokens === void 0 ? {} : { maxOutputTokens: this.options.lcmMaxOutputTokens },
2602
- ...this.options.lcmMaxOutputChars === void 0 ? {} : { maxOutputChars: this.options.lcmMaxOutputChars }
2603
- });
2604
- } catch (error) {
2605
- this.degradedError = error;
2606
- model = { modelHash: "unavailable", generate: async () => {
2607
- throw error instanceof Error ? error : new Error(String(error));
2608
- } };
2609
- }
2610
- const fanIn = Math.max(2, this.options.maxCondenseChildren ?? 4);
2611
- const passes = Math.max(1, this.options.maxMaintenancePasses ?? 4);
2612
- const sessionId = this.activeSessionId;
2613
- const raw = this.ledger.readRaw(this.projectKey, sessionId).filter(
2614
- (entry) => this.activeSources.has(`${entry.sessionId}:${entry.entryId}:${entry.revision}`)
2615
- );
2616
- const inputFor = (node) => {
2617
- if (node.kind === "condensed") return node.children.map((id) => this.maintenance.getNode(id)?.text ?? "").filter(Boolean).join("\n\n");
2618
- return node.sources.map((source) => {
2619
- const entry = this.ledger.readRawEntry(this.projectKey, source.sessionId, source.entryId, source.revision);
2620
- if (!entry || entry.payloadHash !== source.payloadHash) throw new Error("stale source");
2621
- return entry.payloadJson;
2622
- }).join("\n");
2623
- };
2624
- const runJob = async (job, node) => {
2625
- try {
2626
- await this.maintenance.run(job, model, inputFor(node), this.signal);
2627
- } catch {
2628
- }
2629
- };
2630
- for (let pass = 0; pass < passes && !this.closed; pass += 1) {
2631
- const leafCandidate = this.maintenance.createLeaf(this.maintenance.selectLeaf(raw, this.context.sessionManager.getLeafId()));
2632
- const leaf = leafCandidate ? this.maintenance.getNode(leafCandidate.nodeId) : void 0;
2633
- if (leaf?.state === "pending") {
2634
- const job = this.maintenance.listJobs().find((item) => item.nodeId === leaf.nodeId);
2635
- if (job) await runJob(job, leaf);
2636
- }
2637
- const branch = this.context.sessionManager.getLeafId();
2638
- const pending = this.maintenance.listJobs().filter((job) => job.state === "pending").flatMap((job) => {
2639
- const node = this.maintenance.getNode(job.nodeId);
2640
- if (!node || node.sessionId !== sessionId || branch !== null && node.branch !== branch && node.branch !== null || !node.sources.every((source) => this.activeSources.has(`${source.sessionId}:${source.entryId}:${source.revision}`))) return [];
2641
- return [{ job, node }];
2642
- }).slice(0, passes);
2643
- for (const item of pending) await runJob(item.job, item.node);
2644
- const children = this.maintenance.selectCondensation(sessionId, this.activeSources, this.context.sessionManager.getLeafId());
2645
- if (children.length >= fanIn) {
2646
- const node = this.maintenance.createCondensed(children);
2647
- if (node) {
2648
- const job = this.maintenance.listJobs().find((item) => item.nodeId === node.nodeId);
2649
- if (job) await runJob(job, node);
2650
- }
2651
- }
2652
- if (!leaf && pending.length === 0 && children.length < fanIn) break;
2653
- }
2654
- }
2655
- compact(input) {
2656
- const persistedEntries = /* @__PURE__ */ new Map();
2657
- for (const entry of input.branchEntries) {
2658
- const message = entry.type === "message" ? entry.message : void 0;
2659
- const payloadJson = canonicalLcmPayload(entry);
2660
- const stored = this.ledger.appendRaw({
2661
- projectKey: this.projectKey,
2662
- sessionId: input.sessionId,
2663
- entryId: entry.id,
2664
- role: message?.role ?? entry.type,
2665
- content: message ? JSON.stringify(message.content ?? "") : payloadJson,
2666
- payloadJson,
2667
- parentEntryId: entry.parentId ?? null,
2668
- branch: input.branch
2669
- });
2670
- persistedEntries.set(`${entry.id}:${stored.payloadHash}`, stored);
2671
- }
2672
- const firstKeptIndex = input.firstKeptEntryId ? input.branchEntries.findIndex((entry) => entry.id === input.firstKeptEntryId) : input.branchEntries.length;
2673
- const sourceEntries = input.branchEntries.slice(0, firstKeptIndex < 0 ? input.branchEntries.length : firstKeptIndex);
2674
- const selectedStored = sourceEntries.map((entry) => {
2675
- const payloadHash = hashLcmPayload(JSON.parse(canonicalLcmPayload(entry)));
2676
- const stored = persistedEntries.get(`${entry.id}:${payloadHash}`);
2677
- if (!stored) throw new Error("compaction source was not persisted");
2678
- return stored;
2679
- });
2680
- const activeSources = new Set(selectedStored.map((entry) => `${entry.sessionId}:${entry.entryId}:${entry.revision}`));
2681
- const frontier = this.maintenance.getFrontier(input.sessionId, input.branch, activeSources);
2682
- const coveredSources = new Set(frontier.flatMap((node) => node.sources.map((source) => `${source.sessionId}:${source.entryId}:${source.revision}`)));
2683
- const summary = frontier.map((node) => node.text ?? "").filter(Boolean).join("\n\n");
2684
- if (summary && selectedStored.every((entry) => coveredSources.has(`${entry.sessionId}:${entry.entryId}:${entry.revision}`))) return { summary, firstKeptEntryId: input.firstKeptEntryId, tokensBefore: input.tokensBefore, source: "ready-frontier", branch: input.branch };
2685
- if (selectedStored.length === 0) throw new Error("LCM emergency fallback has no persisted sources");
2686
- const payloads = sourceEntries.map((entry) => canonicalLcmPayload(entry)).join("\n");
2687
- const sources = selectedStored.map((stored) => ({ sessionId: stored.sessionId, entryId: stored.entryId, revision: stored.revision, payloadHash: stored.payloadHash }));
2688
- const fallback = emergencyReduce(payloads, this.options.lcmMaxOutputChars ?? 4096, sources);
2689
- const leaf = this.maintenance.createLeaf(selectedStored);
2690
- if (!leaf) throw new Error("LCM emergency fallback node was not created");
2691
- const persisted = this.maintenance.getNode(leaf.nodeId);
2692
- if (persisted?.state === "ready") {
2693
- if (persisted.branch !== input.branch || JSON.stringify(persisted.sources) !== JSON.stringify(sources)) throw new Error("LCM emergency fallback provenance mismatch");
2694
- if (!persisted.text) throw new Error("LCM emergency fallback text is missing");
2695
- return { summary: persisted.text, firstKeptEntryId: input.firstKeptEntryId, tokensBefore: input.tokensBefore, source: "emergency", branch: input.branch };
2696
- }
2697
- const job = this.maintenance.listJobs().find((item) => item.nodeId === leaf.nodeId);
2698
- if (!job) throw new Error("LCM emergency fallback job was not created");
2699
- const completed = this.maintenance.completeEmergency(this.maintenance.claimEmergency(job.jobId), fallback);
2700
- return { summary: completed.text ?? fallback, firstKeptEntryId: input.firstKeptEntryId, tokensBefore: input.tokensBefore, source: "emergency", branch: input.branch };
2701
- }
2702
- memoryContext() {
2703
- return {
2704
- ledger: {
2705
- projectKey: this.projectKey,
2706
- readRaw: (sessionId) => this.ledger.readRaw(this.projectKey, sessionId),
2707
- readRawPage: (sessionId, offset, limit) => this.ledger.readRawPage(this.projectKey, sessionId, offset, limit),
2708
- readRawEntry: (sessionId, entryId, revision) => this.ledger.readRawEntry(this.projectKey, sessionId, entryId, revision)
2709
- },
2710
- ...this.activeSessionId === void 0 ? {} : { currentSessionId: this.activeSessionId },
2711
- summaries: {
2712
- listNodes: ({ sessionId, branch, limit }) => this.maintenance.getFrontier(sessionId, branch, sessionId === this.activeSessionId ? this.activeSources : void 0).slice(0, limit).map((node) => ({ ...node, text: node.text ?? "", sources: node.sources.map((source) => ({ entryId: source.entryId, revision: source.revision, contentHash: source.payloadHash })) })),
2713
- getNode: (nodeId) => {
2714
- const node = this.maintenance.getNode(nodeId);
2715
- return node ? { ...node, text: node.text ?? "", sources: node.sources.map((source) => ({ entryId: source.entryId, revision: source.revision, contentHash: source.payloadHash })) } : void 0;
2716
- }
2717
- },
2718
- branchForSession: (sessionId) => sessionId === this.activeSessionId ? { branch: this.context.sessionManager.getLeafId(), activeSourceKeys: [...this.activeSources], ready: this.status === "healthy" } : void 0
2719
- };
2720
- }
2721
- frontier(sessionId, branch) {
2722
- return this.maintenance.getFrontier(sessionId, branch, sessionId === this.activeSessionId ? this.activeSources : void 0);
2723
- }
2724
- raw(sessionId) {
2725
- return this.ledger.readRaw(this.projectKey, sessionId);
2726
- }
2727
- async shutdown() {
2728
- if (this.closed) return;
2729
- this.closed = true;
2730
- this.abort.abort();
2731
- await this.writePending.catch(() => void 0);
2732
- await this.maintenancePending.catch(() => void 0);
2733
- this.ledger.close();
2734
- }
2735
1877
  };
2736
1878
 
2737
1879
  // src/core/tool-ownership.ts
2738
- import path4 from "node:path";
1880
+ import path3 from "node:path";
2739
1881
 
2740
1882
  // src/audit/details.ts
2741
1883
  var FABRIC_EXECUTION_DETAILS_MAX_BYTES = 512 * 1024;
@@ -2837,7 +1979,7 @@ var readFabricExecutionRenderDetails = (value) => {
2837
1979
  var FABRIC_TOOL_NAME = "fabric_exec";
2838
1980
  var TOP_LEVEL_SCHEMA_REF_PREFIX = "schema.top_level_tool.";
2839
1981
  var ownsFabricToolSource = (tools, extensionEntryPath) => tools.some(
2840
- (tool) => tool.name === FABRIC_TOOL_NAME && path4.resolve(tool.sourceInfo.path) === path4.resolve(extensionEntryPath)
1982
+ (tool) => tool.name === FABRIC_TOOL_NAME && path3.resolve(tool.sourceInfo.path) === path3.resolve(extensionEntryPath)
2841
1983
  );
2842
1984
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2843
1985
  var finalFabricDetailsFailed = (details) => {
@@ -3000,7 +2142,7 @@ ${sections.join("\n")}` : "";
3000
2142
 
3001
2143
  // src/core/system-guidance.ts
3002
2144
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
3003
- import * as path5 from "node:path";
2145
+ import * as path4 from "node:path";
3004
2146
  var fabricExecutionKernelGuidance = (fullCodeMode) => [
3005
2147
  fullCodeMode ? "OMP Fabric full code mode: `fabric_exec` is the only way to call OMP core tools \u2014 use them as `omp.*` inside `code`." : "OMP Fabric is in orchestration-only mode. OMP core and registered extension tools stay on their native direct execution path; inside fabric_exec, `omp.*` and `extensions.*` are unavailable.",
3006
2148
  // Files the model has not opened (images in particular) must be read before
@@ -3012,10 +2154,10 @@ var defaultFabricExecutionGuidance = (fullCodeMode) => fullCodeMode ? "Examples
3012
2154
  var manifestNameCache = /* @__PURE__ */ new Map();
3013
2155
  var packageNameFromManifest = (startPath) => {
3014
2156
  if (!startPath) return void 0;
3015
- let directory = path5.dirname(path5.resolve(startPath));
2157
+ let directory = path4.dirname(path4.resolve(startPath));
3016
2158
  while (true) {
3017
2159
  if (manifestNameCache.has(directory)) return manifestNameCache.get(directory);
3018
- const manifestPath = path5.join(directory, "package.json");
2160
+ const manifestPath = path4.join(directory, "package.json");
3019
2161
  let name;
3020
2162
  if (existsSync2(manifestPath)) {
3021
2163
  try {
@@ -3026,7 +2168,7 @@ var packageNameFromManifest = (startPath) => {
3026
2168
  }
3027
2169
  manifestNameCache.set(directory, name);
3028
2170
  if (name) return name;
3029
- const parent = path5.dirname(directory);
2171
+ const parent = path4.dirname(directory);
3030
2172
  if (parent === directory) return void 0;
3031
2173
  directory = parent;
3032
2174
  }
@@ -3294,8 +2436,8 @@ var formatFailureProgress = (trace) => {
3294
2436
  );
3295
2437
  if (completed.length === 0) return void 0;
3296
2438
  const summaries = completed.slice(0, MAX_COMPLETED_CALLS).map((operation) => {
3297
- const path12 = operation.args.path;
3298
- return typeof path12 === "string" ? `${operation.ref}(${compactPath(path12)})` : operation.ref;
2439
+ const path11 = operation.args.path;
2440
+ return typeof path11 === "string" ? `${operation.ref}(${compactPath(path11)})` : operation.ref;
3299
2441
  });
3300
2442
  const omitted = completed.length - summaries.length;
3301
2443
  return [
@@ -3604,12 +2746,12 @@ var PartialResultObserver = class {
3604
2746
  // src/output-budget.ts
3605
2747
  import { mkdtemp, writeFile } from "node:fs/promises";
3606
2748
  import { tmpdir } from "node:os";
3607
- import path6 from "node:path";
2749
+ import path5 from "node:path";
3608
2750
  var MAX_FAILURE_MODEL_OUTPUT_CHARS = 2e4;
3609
2751
  var modelOutputBudget = (configuredMaxChars, success) => success ? configuredMaxChars : Math.min(configuredMaxChars, MAX_FAILURE_MODEL_OUTPUT_CHARS);
3610
2752
  var writeOutputArtifact = async (content) => {
3611
- const directory = await mkdtemp(path6.join(tmpdir(), "omp-fabric-output-"));
3612
- const artifactPath = path6.join(directory, "output.txt");
2753
+ const directory = await mkdtemp(path5.join(tmpdir(), "omp-fabric-output-"));
2754
+ const artifactPath = path5.join(directory, "output.txt");
3613
2755
  await writeFile(artifactPath, content, { encoding: "utf8", mode: 384 });
3614
2756
  return artifactPath;
3615
2757
  };
@@ -3645,20 +2787,20 @@ var normalizeFabricExecParams = (input) => {
3645
2787
  if (typeof prepared !== "object" || prepared === null || Array.isArray(prepared)) {
3646
2788
  throw new Error("fabric_exec arguments must be an object");
3647
2789
  }
3648
- const record2 = prepared;
3649
- const code = record2.code;
2790
+ const record = prepared;
2791
+ const code = record.code;
3650
2792
  if (typeof code !== "string" && !(Array.isArray(code) && code.every((line) => typeof line === "string"))) {
3651
2793
  throw new Error("fabric_exec code must be a string or string array");
3652
2794
  }
3653
- const resultFormat = RESULT_FORMATS.includes(record2.resultFormat) ? record2.resultFormat : void 0;
2795
+ const resultFormat = RESULT_FORMATS.includes(record.resultFormat) ? record.resultFormat : void 0;
3654
2796
  return {
3655
2797
  code,
3656
- ...record2.payloads !== void 0 ? { payloads: record2.payloads } : {},
3657
- ...record2.display !== void 0 ? { display: record2.display } : {},
2798
+ ...record.payloads !== void 0 ? { payloads: record.payloads } : {},
2799
+ ...record.display !== void 0 ? { display: record.display } : {},
3658
2800
  ...resultFormat !== void 0 ? { resultFormat } : {},
3659
- ...typeof record2.tokenBudget === "number" ? { tokenBudget: record2.tokenBudget } : {},
3660
- ...typeof record2.agentBudget === "number" ? { agentBudget: record2.agentBudget } : {},
3661
- ...typeof record2.timeoutMs === "number" ? { timeoutMs: record2.timeoutMs } : {}
2801
+ ...typeof record.tokenBudget === "number" ? { tokenBudget: record.tokenBudget } : {},
2802
+ ...typeof record.agentBudget === "number" ? { agentBudget: record.agentBudget } : {},
2803
+ ...typeof record.timeoutMs === "number" ? { timeoutMs: record.timeoutMs } : {}
3662
2804
  };
3663
2805
  };
3664
2806
  var toolDisplayMode = (state) => state.bootstrapped ? state.config.ui.toolDisplay : "full";
@@ -4393,8 +3535,8 @@ ${recoveryHint}` : ""}`,
4393
3535
  );
4394
3536
 
4395
3537
  // src/fabric-state.ts
4396
- import fs3 from "node:fs";
4397
- import path7 from "node:path";
3538
+ import fs2 from "node:fs";
3539
+ import path6 from "node:path";
4398
3540
  var FabricState = class {
4399
3541
  constructor(omp, capturedTools, options = {}) {
4400
3542
  this.omp = omp;
@@ -4533,15 +3675,15 @@ var FabricState = class {
4533
3675
  const sessionId = context.sessionManager.getSessionId();
4534
3676
  if (resolveFabricIdentity(sessionId).identity.kind !== "main") return false;
4535
3677
  const projectRoot = process.env.OMP_FABRIC_PROJECT_ROOT ?? context.cwd;
4536
- const meshRoot = process.env.OMP_FABRIC_MESH_ROOT ?? (this.config.mesh.root ? path7.resolve(projectRoot, this.config.mesh.root) : path7.join(projectRoot, ".omp", "fabric", "mesh"));
3678
+ const meshRoot = process.env.OMP_FABRIC_MESH_ROOT ?? (this.config.mesh.root ? path6.resolve(projectRoot, this.config.mesh.root) : path6.join(projectRoot, ".omp", "fabric", "mesh"));
4537
3679
  const fabricSessionId = process.env.OMP_FABRIC_SESSION_ID?.trim() || sessionId;
4538
3680
  const actorRoots = [
4539
- path7.join(meshRoot, "actors"),
4540
- path7.join(meshRoot, "actors", fabricSessionId)
3681
+ path6.join(meshRoot, "actors"),
3682
+ path6.join(meshRoot, "actors", fabricSessionId)
4541
3683
  ];
4542
3684
  return actorRoots.some((actorRoot) => {
4543
3685
  try {
4544
- const registry = JSON.parse(fs3.readFileSync(path7.join(actorRoot, "actors.json"), "utf8"));
3686
+ const registry = JSON.parse(fs2.readFileSync(path6.join(actorRoot, "actors.json"), "utf8"));
4545
3687
  if (typeof registry !== "object" || registry === null || Array.isArray(registry)) return false;
4546
3688
  const actors = registry.actors;
4547
3689
  return Array.isArray(actors) && actors.some((value) => {
@@ -4944,7 +4086,7 @@ var classifyToolResult = (input) => {
4944
4086
 
4945
4087
  // src/host-compatibility.ts
4946
4088
  import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
4947
- import path8 from "node:path";
4089
+ import path7 from "node:path";
4948
4090
  var MINIMUM_OMP_HOST_VERSION = "18.1.10";
4949
4091
  var OMP_HOST_PACKAGE_NAMES = {
4950
4092
  "@oh-my-pi/pi-coding-agent": true
@@ -4974,12 +4116,12 @@ var detectOmpHostVersion = (cliPath = process.argv[1]) => {
4974
4116
  if (!cliPath) return void 0;
4975
4117
  let directory;
4976
4118
  try {
4977
- directory = path8.dirname(realpathSync2(cliPath));
4119
+ directory = path7.dirname(realpathSync2(cliPath));
4978
4120
  } catch {
4979
4121
  return void 0;
4980
4122
  }
4981
4123
  while (true) {
4982
- const manifestPath = path8.join(directory, "package.json");
4124
+ const manifestPath = path7.join(directory, "package.json");
4983
4125
  if (existsSync3(manifestPath)) {
4984
4126
  try {
4985
4127
  const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
@@ -4989,7 +4131,7 @@ var detectOmpHostVersion = (cliPath = process.argv[1]) => {
4989
4131
  } catch {
4990
4132
  }
4991
4133
  }
4992
- const parent = path8.dirname(directory);
4134
+ const parent = path7.dirname(directory);
4993
4135
  if (parent === directory) return void 0;
4994
4136
  directory = parent;
4995
4137
  }
@@ -5002,8 +4144,8 @@ var ompHostCompatibilityWarning = (version = detectOmpHostVersion()) => {
5002
4144
  };
5003
4145
 
5004
4146
  // src/update/check.ts
5005
- import fs4 from "node:fs";
5006
- import path9 from "node:path";
4147
+ import fs3 from "node:fs";
4148
+ import path8 from "node:path";
5007
4149
  import { fileURLToPath } from "node:url";
5008
4150
  var PACKAGE_NAME = "omp-fabric";
5009
4151
  var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
@@ -5011,27 +4153,27 @@ var UPGRADE_COMMAND = `omp plugin install ${PACKAGE_NAME}`;
5011
4153
  var REQUEST_TIMEOUT_MS = 4e3;
5012
4154
  var UPDATE_STATUS_KEY = "fabric-update";
5013
4155
  var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
5014
- var updateCheckCachePath = (agentDir) => path9.join(agentDir, "fabric", "update-check.json");
4156
+ var updateCheckCachePath = (agentDir) => path8.join(agentDir, "fabric", "update-check.json");
5015
4157
  var installedVersionFromManifest = () => {
5016
- let directory = path9.dirname(fileURLToPath(import.meta.url));
4158
+ let directory = path8.dirname(fileURLToPath(import.meta.url));
5017
4159
  while (true) {
5018
4160
  try {
5019
4161
  const manifest = JSON.parse(
5020
- fs4.readFileSync(path9.join(directory, "package.json"), "utf8")
4162
+ fs3.readFileSync(path8.join(directory, "package.json"), "utf8")
5021
4163
  );
5022
4164
  if (manifest.name === PACKAGE_NAME && typeof manifest.version === "string") {
5023
4165
  return manifest.version;
5024
4166
  }
5025
4167
  } catch {
5026
4168
  }
5027
- const parent = path9.dirname(directory);
4169
+ const parent = path8.dirname(directory);
5028
4170
  if (parent === directory) return void 0;
5029
4171
  directory = parent;
5030
4172
  }
5031
4173
  };
5032
4174
  var readCache = (cachePath) => {
5033
4175
  try {
5034
- const parsed = JSON.parse(fs4.readFileSync(cachePath, "utf8"));
4176
+ const parsed = JSON.parse(fs3.readFileSync(cachePath, "utf8"));
5035
4177
  if (typeof parsed.checkedAt !== "number" || !Number.isFinite(parsed.checkedAt)) return void 0;
5036
4178
  if (typeof parsed.latest !== "string" || parsed.latest.length === 0) return void 0;
5037
4179
  return {
@@ -5078,32 +4220,32 @@ var checkFabricUpdate = async (options) => {
5078
4220
  const cached = readCache(cachePath);
5079
4221
  const age = cached === void 0 ? void 0 : now - cached.checkedAt;
5080
4222
  const fresh = cached !== void 0 && age !== void 0 && age >= 0 && age < UPDATE_CHECK_TTL_MS;
5081
- let record2;
4223
+ let record;
5082
4224
  if (fresh && cached !== void 0) {
5083
- record2 = cached;
4225
+ record = cached;
5084
4226
  } else {
5085
4227
  const latest = await fetchLatestVersion(
5086
4228
  options.fetchImpl ?? globalThis.fetch,
5087
4229
  options.timeoutMs ?? REQUEST_TIMEOUT_MS
5088
4230
  );
5089
4231
  if (latest === void 0) return void 0;
5090
- record2 = {
4232
+ record = {
5091
4233
  format: 1,
5092
4234
  checkedAt: now,
5093
4235
  latest,
5094
4236
  ...cached?.notified === void 0 ? {} : { notified: cached.notified }
5095
4237
  };
5096
4238
  }
5097
- const behind = compareVersions(record2.latest, installed) === 1;
5098
- const announce = behind && record2.notified !== record2.latest;
4239
+ const behind = compareVersions(record.latest, installed) === 1;
4240
+ const announce = behind && record.notified !== record.latest;
5099
4241
  if (!fresh || announce) {
5100
- writeCache(cachePath, announce ? { ...record2, notified: record2.latest } : record2);
4242
+ writeCache(cachePath, announce ? { ...record, notified: record.latest } : record);
5101
4243
  }
5102
4244
  if (!announce) return void 0;
5103
4245
  return {
5104
4246
  installed,
5105
- latest: record2.latest,
5106
- message: `${PACKAGE_NAME} ${installed} \u2192 ${record2.latest} \xB7 ${UPGRADE_COMMAND}`
4247
+ latest: record.latest,
4248
+ message: `${PACKAGE_NAME} ${installed} \u2192 ${record.latest} \xB7 ${UPGRADE_COMMAND}`
5107
4249
  };
5108
4250
  } catch {
5109
4251
  return void 0;
@@ -5132,7 +4274,7 @@ var scheduleFabricUpdateCheck = async (context, options) => {
5132
4274
  };
5133
4275
 
5134
4276
  // src/ui/controller.ts
5135
- import path10 from "node:path";
4277
+ import path9 from "node:path";
5136
4278
 
5137
4279
  // src/ui/snapshot.ts
5138
4280
  var MAX_UI_AGENTS = 240;
@@ -5189,57 +4331,57 @@ var createDashboardSnapshot = (state, events, context, activityRuns) => {
5189
4331
  if (leftLaunch !== rightLaunch) return leftLaunch ? -1 : 1;
5190
4332
  return left.call.startedAt - right.call.startedAt;
5191
4333
  });
5192
- const agentFromRecord = (record2, nestingDepth, parentId, parent) => {
4334
+ const agentFromRecord = (record, nestingDepth, parentId, parent) => {
5193
4335
  const linked = parentId ? void 0 : agentLinks.find(
5194
- ({ call }) => call.entityId && (record2.id.startsWith(call.entityId) || call.entityId.startsWith(record2.id))
4336
+ ({ call }) => call.entityId && (record.id.startsWith(call.entityId) || call.entityId.startsWith(record.id))
5195
4337
  );
5196
4338
  const base = {
5197
- id: record2.id,
5198
- name: record2.name,
5199
- status: record2.status,
5200
- runner: record2.runner,
5201
- transport: record2.transport,
5202
- cwd: record2.cwd,
5203
- ...!isRunRecord(record2) && linked ? { startedAt: linked.call.startedAt } : {},
5204
- ...record2.model ? { model: record2.model } : {},
5205
- ...record2.thinking ? { thinking: record2.thinking } : {},
5206
- ...record2.attachCommand ? { attachCommand: record2.attachCommand } : {},
5207
- ...isRunRecord(record2) && record2.logFile ? { logFile: record2.logFile } : {},
5208
- ...record2.branch ? { branch: record2.branch } : {},
5209
- ...record2.worktree ? { worktree: record2.worktree } : {},
5210
- ...record2.actorId ? { actorId: record2.actorId } : {},
5211
- ...record2.actorName ? { actorName: record2.actorName } : {},
4339
+ id: record.id,
4340
+ name: record.name,
4341
+ status: record.status,
4342
+ runner: record.runner,
4343
+ transport: record.transport,
4344
+ cwd: record.cwd,
4345
+ ...!isRunRecord(record) && linked ? { startedAt: linked.call.startedAt } : {},
4346
+ ...record.model ? { model: record.model } : {},
4347
+ ...record.thinking ? { thinking: record.thinking } : {},
4348
+ ...record.attachCommand ? { attachCommand: record.attachCommand } : {},
4349
+ ...isRunRecord(record) && record.logFile ? { logFile: record.logFile } : {},
4350
+ ...record.branch ? { branch: record.branch } : {},
4351
+ ...record.worktree ? { worktree: record.worktree } : {},
4352
+ ...record.actorId ? { actorId: record.actorId } : {},
4353
+ ...record.actorName ? { actorName: record.actorName } : {},
5212
4354
  ...parentId ? { parentId } : {},
5213
4355
  ...nestingDepth > 0 ? { nestingDepth } : {},
5214
4356
  ...linked ? { runId: linked.runId } : parent?.runId ? { runId: parent.runId } : {},
5215
4357
  ...linked?.call.phaseId ? { phaseId: linked.call.phaseId } : parent?.phaseId ? { phaseId: parent.phaseId } : {}
5216
4358
  };
5217
- if (!isRunRecord(record2)) return base;
4359
+ if (!isRunRecord(record)) return base;
5218
4360
  return {
5219
4361
  ...base,
5220
- task: record2.task,
5221
- startedAt: record2.startedAt,
5222
- updatedAt: record2.updatedAt,
5223
- ...record2.finishedAt !== void 0 ? { finishedAt: record2.finishedAt } : {},
5224
- ...record2.currentTool ? { currentTool: record2.currentTool } : {},
5225
- turns: record2.turns,
5226
- toolCalls: record2.toolCalls,
5227
- usage: { ...record2.usage },
5228
- ...record2.text ? { text: record2.text } : {},
5229
- ...record2.value !== void 0 ? { value: structuredClone(record2.value) } : {},
5230
- ...record2.error ? { error: record2.error } : {}
4362
+ task: record.task,
4363
+ startedAt: record.startedAt,
4364
+ updatedAt: record.updatedAt,
4365
+ ...record.finishedAt !== void 0 ? { finishedAt: record.finishedAt } : {},
4366
+ ...record.currentTool ? { currentTool: record.currentTool } : {},
4367
+ turns: record.turns,
4368
+ toolCalls: record.toolCalls,
4369
+ usage: { ...record.usage },
4370
+ ...record.text ? { text: record.text } : {},
4371
+ ...record.value !== void 0 ? { value: structuredClone(record.value) } : {},
4372
+ ...record.error ? { error: record.error } : {}
5231
4373
  };
5232
4374
  };
5233
4375
  const allAgents = [];
5234
- const appendAgent = (record2, nestingDepth, parentId, parent) => {
5235
- const agent = agentFromRecord(record2, nestingDepth, parentId, parent);
4376
+ const appendAgent = (record, nestingDepth, parentId, parent) => {
4377
+ const agent = agentFromRecord(record, nestingDepth, parentId, parent);
5236
4378
  allAgents.push(agent);
5237
- if (!isRunRecord(record2)) return;
5238
- for (const nested of record2.nestedAgents ?? []) {
5239
- appendAgent(nested, nestingDepth + 1, record2.id, agent);
4379
+ if (!isRunRecord(record)) return;
4380
+ for (const nested of record.nestedAgents ?? []) {
4381
+ appendAgent(nested, nestingDepth + 1, record.id, agent);
5240
4382
  }
5241
4383
  };
5242
- for (const record2 of agentRecords) appendAgent(record2, 0);
4384
+ for (const record of agentRecords) appendAgent(record, 0);
5243
4385
  const participants = typeof state.participantInfos === "function" ? state.participantInfos({ scope: "project" }) : [];
5244
4386
  const participantById = new Map(participants.map((participant) => [participant.id, participant]));
5245
4387
  const actors = state.actors.list().map((actor) => {
@@ -5989,7 +5131,7 @@ var FabricUiController = class {
5989
5131
  logFile: actor.worker.logFile
5990
5132
  };
5991
5133
  }
5992
- const retained = actor.lastRunId && actor.logDir ? path10.join(actor.logDir, actor.lastRunId, "events.jsonl") : void 0;
5134
+ const retained = actor.lastRunId && actor.logDir ? path9.join(actor.logDir, actor.lastRunId, "events.jsonl") : void 0;
5993
5135
  if (retained) return { id: actor.id, status: actor.status, logFile: retained };
5994
5136
  if (actor.sessionFile) {
5995
5137
  return { id: actor.id, status: actor.status, logFile: actor.sessionFile };
@@ -6148,15 +5290,15 @@ var REFRESH_CARDS_PER_TICK = 3;
6148
5290
 
6149
5291
  // src/index.ts
6150
5292
  import { existsSync as existsSync4 } from "node:fs";
6151
- import path11 from "node:path";
5293
+ import path10 from "node:path";
6152
5294
  import { fileURLToPath as fileURLToPath2 } from "node:url";
6153
- var FABRIC_EXTENSION_ENTRY_PATH = path11.resolve(fileURLToPath2(import.meta.url));
6154
- var FABRIC_ENTRY_DIR = path11.dirname(FABRIC_EXTENSION_ENTRY_PATH);
5295
+ var FABRIC_EXTENSION_ENTRY_PATH = path10.resolve(fileURLToPath2(import.meta.url));
5296
+ var FABRIC_ENTRY_DIR = path10.dirname(FABRIC_EXTENSION_ENTRY_PATH);
6155
5297
  var FABRIC_RUNTIME_PATHS = {
6156
5298
  extension: FABRIC_EXTENSION_ENTRY_PATH,
6157
- worker: path11.join(FABRIC_ENTRY_DIR, "worker.js"),
6158
- residentHost: path11.join(FABRIC_ENTRY_DIR, "residency", "launcher.js"),
6159
- skills: path11.resolve(FABRIC_ENTRY_DIR, "..", "skills")
5299
+ worker: path10.join(FABRIC_ENTRY_DIR, "worker.js"),
5300
+ residentHost: path10.join(FABRIC_ENTRY_DIR, "residency", "launcher.js"),
5301
+ skills: path10.resolve(FABRIC_ENTRY_DIR, "..", "skills")
6160
5302
  };
6161
5303
  var FABRIC_SKILLS_DIR = FABRIC_RUNTIME_PATHS.skills;
6162
5304
  var componentRegistrationFrom = (value) => {
@@ -6200,6 +5342,7 @@ async function ompFabric(omp) {
6200
5342
  );
6201
5343
  const pendingHandoffs = /* @__PURE__ */ new Map();
6202
5344
  let lcmRuntime;
5345
+ const lcmLoader = createLcmRuntimeLoader();
6203
5346
  let lcmCwd;
6204
5347
  let lcmProjectKey;
6205
5348
  const toolOwnership = new FabricToolOwnership(omp);
@@ -6475,7 +5618,8 @@ async function ompFabric(omp) {
6475
5618
  lcmProjectKey = void 0;
6476
5619
  }
6477
5620
  if (state.config.compaction.engine === "lcm" && !lcmRuntime) {
6478
- lcmRuntime = new LcmRuntime(context, () => ({
5621
+ const LcmRuntimeClass = await lcmLoader.load(context);
5622
+ if (LcmRuntimeClass) lcmRuntime = new LcmRuntimeClass(context, () => ({
6479
5623
  ...state.config.compaction.summaryModel ? { summaryModel: state.config.compaction.summaryModel } : {},
6480
5624
  maxLeafEntries: state.config.compaction.lcmMaxLeafEntries,
6481
5625
  maxCondenseChildren: state.config.compaction.lcmMaxCondenseChildren,
@@ -6484,8 +5628,8 @@ async function ompFabric(omp) {
6484
5628
  lcmMaxOutputTokens: state.config.compaction.lcmMaxOutputTokens,
6485
5629
  lcmMaxOutputChars: state.config.compaction.lcmMaxOutputChars
6486
5630
  }));
6487
- lcmCwd = context.cwd;
6488
- lcmProjectKey = lcmRuntime.projectKey;
5631
+ lcmCwd = lcmRuntime ? context.cwd : void 0;
5632
+ lcmProjectKey = lcmRuntime?.projectKey;
6489
5633
  }
6490
5634
  if (state.config.compaction.engine === "lcm" && lcmRuntime) await lcmRuntime.reconcileSelectedSession();
6491
5635
  refreshCodePreviewSettings();
@@ -6635,7 +5779,7 @@ async function ompFabric(omp) {
6635
5779
  await state.publishHostLifecycle("omp.session_compact", event);
6636
5780
  });
6637
5781
  registerCompactionHook(omp, {
6638
- getEngine: () => state.cwd ? state.config.compaction.engine : DEFAULT_FABRIC_CONFIG.compaction.engine,
5782
+ getEngine: () => lcmLoader.unavailable ? "omp" : state.cwd ? state.config.compaction.engine : DEFAULT_FABRIC_CONFIG.compaction.engine,
6639
5783
  getTargetContextRatio: () => state.cwd ? state.config.compaction.targetContextRatio : DEFAULT_FABRIC_CONFIG.compaction.targetContextRatio,
6640
5784
  lcm: {
6641
5785
  compact: (input) => lcmRuntime?.compact(input)