omp-fabric 1.4.4 → 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 (60) hide show
  1. package/CHANGELOG.md +26 -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-SZWF6C4V.js → chunk-4MVTUMKO.js} +3 -3
  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-OW2LSF5R.js → chunk-IGNMZZQH.js} +3 -3
  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-2J2GOB4A.js → chunk-VDLZAORG.js} +5 -3
  18. package/dist/chunks/chunk-VDLZAORG.js.map +7 -0
  19. package/dist/chunks/chunk-ZDWBTQB6.js +107 -0
  20. package/dist/chunks/chunk-ZDWBTQB6.js.map +7 -0
  21. package/dist/compaction/hook.js +2 -1
  22. package/dist/compaction/lcm-loader.d.ts +12 -0
  23. package/dist/compaction/lcm-loader.d.ts.map +1 -0
  24. package/dist/compaction/lcm-maintenance.d.ts +2 -1
  25. package/dist/compaction/lcm-maintenance.d.ts.map +1 -1
  26. package/dist/compaction/lcm-runtime.d.ts +1 -0
  27. package/dist/compaction/lcm-runtime.d.ts.map +1 -1
  28. package/dist/compaction/lcm-runtime.js +1169 -0
  29. package/dist/compaction/lcm-runtime.js.map +7 -0
  30. package/dist/config.d.ts +1 -0
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/fabric-runtime-state.js +9 -7
  33. package/dist/fabric-runtime-state.js.map +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +135 -989
  36. package/dist/index.js.map +4 -4
  37. package/dist/memory/lcm-adapter.d.ts +1 -1
  38. package/dist/memory/lcm-adapter.d.ts.map +1 -1
  39. package/dist/providers/memory-provider.js +2 -2
  40. package/dist/residency/host.js +8 -6
  41. package/dist/residency/omp-entry.js +8 -6
  42. package/dist/residency/omp-entry.js.map +1 -1
  43. package/dist/storage/lcm-identity.d.ts +43 -0
  44. package/dist/storage/lcm-identity.d.ts.map +1 -0
  45. package/dist/storage/lcm-ledger.d.ts +7 -46
  46. package/dist/storage/lcm-ledger.d.ts.map +1 -1
  47. package/dist/storage/sqlite.d.ts +19 -0
  48. package/dist/storage/sqlite.d.ts.map +1 -0
  49. package/dist/ui/dashboard.js +3 -3
  50. package/dist/ui/settings.js +4 -4
  51. package/docs/compaction.md +8 -1
  52. package/package.json +1 -1
  53. package/dist/chunks/chunk-2J2GOB4A.js.map +0 -7
  54. package/dist/chunks/chunk-LB2EC7FZ.js.map +0 -7
  55. package/dist/chunks/chunk-MBOXJKF6.js +0 -311
  56. package/dist/chunks/chunk-MBOXJKF6.js.map +0 -7
  57. package/dist/chunks/chunk-SG4GVSWX.js.map +0 -7
  58. /package/dist/chunks/{chunk-TR3LQHO7.js.map → chunk-2ZEMWIHZ.js.map} +0 -0
  59. /package/dist/chunks/{chunk-SZWF6C4V.js.map → chunk-4MVTUMKO.js.map} +0 -0
  60. /package/dist/chunks/{chunk-OW2LSF5R.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,
@@ -202,20 +199,17 @@ import {
202
199
  loadFabricConfigForScope,
203
200
  resolveAgentDir,
204
201
  saveFabricConfig
205
- } from "./chunks/chunk-2J2GOB4A.js";
202
+ } from "./chunks/chunk-VDLZAORG.js";
206
203
  import {
207
204
  FABRIC_ACTOR_OMP_HOST_EVENTS
208
205
  } from "./chunks/chunk-3LI7YO4M.js";
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,885 +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 budget = Math.max(1, this.options.maxCondenseChildren ?? 4);
2611
- const sessionId = this.activeSessionId;
2612
- const raw = this.ledger.readRaw(this.projectKey, sessionId).filter(
2613
- (entry) => this.activeSources.has(`${entry.sessionId}:${entry.entryId}:${entry.revision}`)
2614
- );
2615
- const inputFor = (node) => {
2616
- if (node.kind === "condensed") return node.children.map((id) => this.maintenance.getNode(id)?.text ?? "").filter(Boolean).join("\n\n");
2617
- return node.sources.map((source) => {
2618
- const entry = this.ledger.readRawEntry(this.projectKey, source.sessionId, source.entryId, source.revision);
2619
- if (!entry || entry.payloadHash !== source.payloadHash) throw new Error("stale source");
2620
- return entry.payloadJson;
2621
- }).join("\n");
2622
- };
2623
- const runJob = async (job, node) => {
2624
- try {
2625
- await this.maintenance.run(job, model, inputFor(node), this.signal);
2626
- } catch {
2627
- }
2628
- };
2629
- for (let pass = 0; pass < budget && !this.closed; pass += 1) {
2630
- const leafCandidate = this.maintenance.createLeaf(this.maintenance.selectLeaf(raw, this.context.sessionManager.getLeafId()));
2631
- const leaf = leafCandidate ? this.maintenance.getNode(leafCandidate.nodeId) : void 0;
2632
- if (leaf?.state === "pending") {
2633
- const job = this.maintenance.listJobs().find((item) => item.nodeId === leaf.nodeId);
2634
- if (job) await runJob(job, leaf);
2635
- }
2636
- const branch = this.context.sessionManager.getLeafId();
2637
- const pending = this.maintenance.listJobs().filter((job) => job.state === "pending").flatMap((job) => {
2638
- const node = this.maintenance.getNode(job.nodeId);
2639
- 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 [];
2640
- return [{ job, node }];
2641
- }).slice(0, budget);
2642
- for (const item of pending) await runJob(item.job, item.node);
2643
- const children = this.maintenance.selectCondensation(sessionId, this.activeSources, this.context.sessionManager.getLeafId());
2644
- if (children.length >= budget) {
2645
- const node = this.maintenance.createCondensed(children);
2646
- if (node) {
2647
- const job = this.maintenance.listJobs().find((item) => item.nodeId === node.nodeId);
2648
- if (job) await runJob(job, node);
2649
- }
2650
- }
2651
- if (!leaf && pending.length === 0 && children.length < budget) break;
2652
- }
2653
- }
2654
- compact(input) {
2655
- const persistedEntries = /* @__PURE__ */ new Map();
2656
- for (const entry of input.branchEntries) {
2657
- const message = entry.type === "message" ? entry.message : void 0;
2658
- const payloadJson = canonicalLcmPayload(entry);
2659
- const stored = this.ledger.appendRaw({
2660
- projectKey: this.projectKey,
2661
- sessionId: input.sessionId,
2662
- entryId: entry.id,
2663
- role: message?.role ?? entry.type,
2664
- content: message ? JSON.stringify(message.content ?? "") : payloadJson,
2665
- payloadJson,
2666
- parentEntryId: entry.parentId ?? null,
2667
- branch: input.branch
2668
- });
2669
- persistedEntries.set(`${entry.id}:${stored.payloadHash}`, stored);
2670
- }
2671
- const firstKeptIndex = input.firstKeptEntryId ? input.branchEntries.findIndex((entry) => entry.id === input.firstKeptEntryId) : input.branchEntries.length;
2672
- const sourceEntries = input.branchEntries.slice(0, firstKeptIndex < 0 ? input.branchEntries.length : firstKeptIndex);
2673
- const selectedStored = sourceEntries.map((entry) => {
2674
- const payloadHash = hashLcmPayload(JSON.parse(canonicalLcmPayload(entry)));
2675
- const stored = persistedEntries.get(`${entry.id}:${payloadHash}`);
2676
- if (!stored) throw new Error("compaction source was not persisted");
2677
- return stored;
2678
- });
2679
- const activeSources = new Set(selectedStored.map((entry) => `${entry.sessionId}:${entry.entryId}:${entry.revision}`));
2680
- const frontier = this.maintenance.getFrontier(input.sessionId, input.branch, activeSources);
2681
- const coveredSources = new Set(frontier.flatMap((node) => node.sources.map((source) => `${source.sessionId}:${source.entryId}:${source.revision}`)));
2682
- const summary = frontier.map((node) => node.text ?? "").filter(Boolean).join("\n\n");
2683
- 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 };
2684
- if (selectedStored.length === 0) throw new Error("LCM emergency fallback has no persisted sources");
2685
- const payloads = sourceEntries.map((entry) => canonicalLcmPayload(entry)).join("\n");
2686
- const sources = selectedStored.map((stored) => ({ sessionId: stored.sessionId, entryId: stored.entryId, revision: stored.revision, payloadHash: stored.payloadHash }));
2687
- const fallback = emergencyReduce(payloads, this.options.lcmMaxOutputChars ?? 4096, sources);
2688
- const leaf = this.maintenance.createLeaf(selectedStored);
2689
- if (!leaf) throw new Error("LCM emergency fallback node was not created");
2690
- const persisted = this.maintenance.getNode(leaf.nodeId);
2691
- if (persisted?.state === "ready") {
2692
- if (persisted.branch !== input.branch || JSON.stringify(persisted.sources) !== JSON.stringify(sources)) throw new Error("LCM emergency fallback provenance mismatch");
2693
- if (!persisted.text) throw new Error("LCM emergency fallback text is missing");
2694
- return { summary: persisted.text, firstKeptEntryId: input.firstKeptEntryId, tokensBefore: input.tokensBefore, source: "emergency", branch: input.branch };
2695
- }
2696
- const job = this.maintenance.listJobs().find((item) => item.nodeId === leaf.nodeId);
2697
- if (!job) throw new Error("LCM emergency fallback job was not created");
2698
- const completed = this.maintenance.completeEmergency(this.maintenance.claimEmergency(job.jobId), fallback);
2699
- return { summary: completed.text ?? fallback, firstKeptEntryId: input.firstKeptEntryId, tokensBefore: input.tokensBefore, source: "emergency", branch: input.branch };
2700
- }
2701
- memoryContext() {
2702
- return {
2703
- ledger: {
2704
- projectKey: this.projectKey,
2705
- readRaw: (sessionId) => this.ledger.readRaw(this.projectKey, sessionId),
2706
- readRawPage: (sessionId, offset, limit) => this.ledger.readRawPage(this.projectKey, sessionId, offset, limit),
2707
- readRawEntry: (sessionId, entryId, revision) => this.ledger.readRawEntry(this.projectKey, sessionId, entryId, revision)
2708
- },
2709
- ...this.activeSessionId === void 0 ? {} : { currentSessionId: this.activeSessionId },
2710
- summaries: {
2711
- 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 })) })),
2712
- getNode: (nodeId) => {
2713
- const node = this.maintenance.getNode(nodeId);
2714
- return node ? { ...node, text: node.text ?? "", sources: node.sources.map((source) => ({ entryId: source.entryId, revision: source.revision, contentHash: source.payloadHash })) } : void 0;
2715
- }
2716
- },
2717
- branchForSession: (sessionId) => sessionId === this.activeSessionId ? { branch: this.context.sessionManager.getLeafId(), activeSourceKeys: [...this.activeSources], ready: this.status === "healthy" } : void 0
2718
- };
2719
- }
2720
- frontier(sessionId, branch) {
2721
- return this.maintenance.getFrontier(sessionId, branch, sessionId === this.activeSessionId ? this.activeSources : void 0);
2722
- }
2723
- raw(sessionId) {
2724
- return this.ledger.readRaw(this.projectKey, sessionId);
2725
- }
2726
- async shutdown() {
2727
- if (this.closed) return;
2728
- this.closed = true;
2729
- this.abort.abort();
2730
- await this.writePending.catch(() => void 0);
2731
- await this.maintenancePending.catch(() => void 0);
2732
- this.ledger.close();
2733
- }
2734
1877
  };
2735
1878
 
2736
1879
  // src/core/tool-ownership.ts
2737
- import path4 from "node:path";
1880
+ import path3 from "node:path";
2738
1881
 
2739
1882
  // src/audit/details.ts
2740
1883
  var FABRIC_EXECUTION_DETAILS_MAX_BYTES = 512 * 1024;
@@ -2836,7 +1979,7 @@ var readFabricExecutionRenderDetails = (value) => {
2836
1979
  var FABRIC_TOOL_NAME = "fabric_exec";
2837
1980
  var TOP_LEVEL_SCHEMA_REF_PREFIX = "schema.top_level_tool.";
2838
1981
  var ownsFabricToolSource = (tools, extensionEntryPath) => tools.some(
2839
- (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)
2840
1983
  );
2841
1984
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2842
1985
  var finalFabricDetailsFailed = (details) => {
@@ -2999,7 +2142,7 @@ ${sections.join("\n")}` : "";
2999
2142
 
3000
2143
  // src/core/system-guidance.ts
3001
2144
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
3002
- import * as path5 from "node:path";
2145
+ import * as path4 from "node:path";
3003
2146
  var fabricExecutionKernelGuidance = (fullCodeMode) => [
3004
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.",
3005
2148
  // Files the model has not opened (images in particular) must be read before
@@ -3011,10 +2154,10 @@ var defaultFabricExecutionGuidance = (fullCodeMode) => fullCodeMode ? "Examples
3011
2154
  var manifestNameCache = /* @__PURE__ */ new Map();
3012
2155
  var packageNameFromManifest = (startPath) => {
3013
2156
  if (!startPath) return void 0;
3014
- let directory = path5.dirname(path5.resolve(startPath));
2157
+ let directory = path4.dirname(path4.resolve(startPath));
3015
2158
  while (true) {
3016
2159
  if (manifestNameCache.has(directory)) return manifestNameCache.get(directory);
3017
- const manifestPath = path5.join(directory, "package.json");
2160
+ const manifestPath = path4.join(directory, "package.json");
3018
2161
  let name;
3019
2162
  if (existsSync2(manifestPath)) {
3020
2163
  try {
@@ -3025,7 +2168,7 @@ var packageNameFromManifest = (startPath) => {
3025
2168
  }
3026
2169
  manifestNameCache.set(directory, name);
3027
2170
  if (name) return name;
3028
- const parent = path5.dirname(directory);
2171
+ const parent = path4.dirname(directory);
3029
2172
  if (parent === directory) return void 0;
3030
2173
  directory = parent;
3031
2174
  }
@@ -3293,8 +2436,8 @@ var formatFailureProgress = (trace) => {
3293
2436
  );
3294
2437
  if (completed.length === 0) return void 0;
3295
2438
  const summaries = completed.slice(0, MAX_COMPLETED_CALLS).map((operation) => {
3296
- const path12 = operation.args.path;
3297
- 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;
3298
2441
  });
3299
2442
  const omitted = completed.length - summaries.length;
3300
2443
  return [
@@ -3603,12 +2746,12 @@ var PartialResultObserver = class {
3603
2746
  // src/output-budget.ts
3604
2747
  import { mkdtemp, writeFile } from "node:fs/promises";
3605
2748
  import { tmpdir } from "node:os";
3606
- import path6 from "node:path";
2749
+ import path5 from "node:path";
3607
2750
  var MAX_FAILURE_MODEL_OUTPUT_CHARS = 2e4;
3608
2751
  var modelOutputBudget = (configuredMaxChars, success) => success ? configuredMaxChars : Math.min(configuredMaxChars, MAX_FAILURE_MODEL_OUTPUT_CHARS);
3609
2752
  var writeOutputArtifact = async (content) => {
3610
- const directory = await mkdtemp(path6.join(tmpdir(), "omp-fabric-output-"));
3611
- 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");
3612
2755
  await writeFile(artifactPath, content, { encoding: "utf8", mode: 384 });
3613
2756
  return artifactPath;
3614
2757
  };
@@ -3644,20 +2787,20 @@ var normalizeFabricExecParams = (input) => {
3644
2787
  if (typeof prepared !== "object" || prepared === null || Array.isArray(prepared)) {
3645
2788
  throw new Error("fabric_exec arguments must be an object");
3646
2789
  }
3647
- const record2 = prepared;
3648
- const code = record2.code;
2790
+ const record = prepared;
2791
+ const code = record.code;
3649
2792
  if (typeof code !== "string" && !(Array.isArray(code) && code.every((line) => typeof line === "string"))) {
3650
2793
  throw new Error("fabric_exec code must be a string or string array");
3651
2794
  }
3652
- const resultFormat = RESULT_FORMATS.includes(record2.resultFormat) ? record2.resultFormat : void 0;
2795
+ const resultFormat = RESULT_FORMATS.includes(record.resultFormat) ? record.resultFormat : void 0;
3653
2796
  return {
3654
2797
  code,
3655
- ...record2.payloads !== void 0 ? { payloads: record2.payloads } : {},
3656
- ...record2.display !== void 0 ? { display: record2.display } : {},
2798
+ ...record.payloads !== void 0 ? { payloads: record.payloads } : {},
2799
+ ...record.display !== void 0 ? { display: record.display } : {},
3657
2800
  ...resultFormat !== void 0 ? { resultFormat } : {},
3658
- ...typeof record2.tokenBudget === "number" ? { tokenBudget: record2.tokenBudget } : {},
3659
- ...typeof record2.agentBudget === "number" ? { agentBudget: record2.agentBudget } : {},
3660
- ...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 } : {}
3661
2804
  };
3662
2805
  };
3663
2806
  var toolDisplayMode = (state) => state.bootstrapped ? state.config.ui.toolDisplay : "full";
@@ -4392,8 +3535,8 @@ ${recoveryHint}` : ""}`,
4392
3535
  );
4393
3536
 
4394
3537
  // src/fabric-state.ts
4395
- import fs3 from "node:fs";
4396
- import path7 from "node:path";
3538
+ import fs2 from "node:fs";
3539
+ import path6 from "node:path";
4397
3540
  var FabricState = class {
4398
3541
  constructor(omp, capturedTools, options = {}) {
4399
3542
  this.omp = omp;
@@ -4532,15 +3675,15 @@ var FabricState = class {
4532
3675
  const sessionId = context.sessionManager.getSessionId();
4533
3676
  if (resolveFabricIdentity(sessionId).identity.kind !== "main") return false;
4534
3677
  const projectRoot = process.env.OMP_FABRIC_PROJECT_ROOT ?? context.cwd;
4535
- 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"));
4536
3679
  const fabricSessionId = process.env.OMP_FABRIC_SESSION_ID?.trim() || sessionId;
4537
3680
  const actorRoots = [
4538
- path7.join(meshRoot, "actors"),
4539
- path7.join(meshRoot, "actors", fabricSessionId)
3681
+ path6.join(meshRoot, "actors"),
3682
+ path6.join(meshRoot, "actors", fabricSessionId)
4540
3683
  ];
4541
3684
  return actorRoots.some((actorRoot) => {
4542
3685
  try {
4543
- 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"));
4544
3687
  if (typeof registry !== "object" || registry === null || Array.isArray(registry)) return false;
4545
3688
  const actors = registry.actors;
4546
3689
  return Array.isArray(actors) && actors.some((value) => {
@@ -4943,7 +4086,7 @@ var classifyToolResult = (input) => {
4943
4086
 
4944
4087
  // src/host-compatibility.ts
4945
4088
  import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
4946
- import path8 from "node:path";
4089
+ import path7 from "node:path";
4947
4090
  var MINIMUM_OMP_HOST_VERSION = "18.1.10";
4948
4091
  var OMP_HOST_PACKAGE_NAMES = {
4949
4092
  "@oh-my-pi/pi-coding-agent": true
@@ -4973,12 +4116,12 @@ var detectOmpHostVersion = (cliPath = process.argv[1]) => {
4973
4116
  if (!cliPath) return void 0;
4974
4117
  let directory;
4975
4118
  try {
4976
- directory = path8.dirname(realpathSync2(cliPath));
4119
+ directory = path7.dirname(realpathSync2(cliPath));
4977
4120
  } catch {
4978
4121
  return void 0;
4979
4122
  }
4980
4123
  while (true) {
4981
- const manifestPath = path8.join(directory, "package.json");
4124
+ const manifestPath = path7.join(directory, "package.json");
4982
4125
  if (existsSync3(manifestPath)) {
4983
4126
  try {
4984
4127
  const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
@@ -4988,7 +4131,7 @@ var detectOmpHostVersion = (cliPath = process.argv[1]) => {
4988
4131
  } catch {
4989
4132
  }
4990
4133
  }
4991
- const parent = path8.dirname(directory);
4134
+ const parent = path7.dirname(directory);
4992
4135
  if (parent === directory) return void 0;
4993
4136
  directory = parent;
4994
4137
  }
@@ -5001,8 +4144,8 @@ var ompHostCompatibilityWarning = (version = detectOmpHostVersion()) => {
5001
4144
  };
5002
4145
 
5003
4146
  // src/update/check.ts
5004
- import fs4 from "node:fs";
5005
- import path9 from "node:path";
4147
+ import fs3 from "node:fs";
4148
+ import path8 from "node:path";
5006
4149
  import { fileURLToPath } from "node:url";
5007
4150
  var PACKAGE_NAME = "omp-fabric";
5008
4151
  var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
@@ -5010,27 +4153,27 @@ var UPGRADE_COMMAND = `omp plugin install ${PACKAGE_NAME}`;
5010
4153
  var REQUEST_TIMEOUT_MS = 4e3;
5011
4154
  var UPDATE_STATUS_KEY = "fabric-update";
5012
4155
  var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
5013
- var updateCheckCachePath = (agentDir) => path9.join(agentDir, "fabric", "update-check.json");
4156
+ var updateCheckCachePath = (agentDir) => path8.join(agentDir, "fabric", "update-check.json");
5014
4157
  var installedVersionFromManifest = () => {
5015
- let directory = path9.dirname(fileURLToPath(import.meta.url));
4158
+ let directory = path8.dirname(fileURLToPath(import.meta.url));
5016
4159
  while (true) {
5017
4160
  try {
5018
4161
  const manifest = JSON.parse(
5019
- fs4.readFileSync(path9.join(directory, "package.json"), "utf8")
4162
+ fs3.readFileSync(path8.join(directory, "package.json"), "utf8")
5020
4163
  );
5021
4164
  if (manifest.name === PACKAGE_NAME && typeof manifest.version === "string") {
5022
4165
  return manifest.version;
5023
4166
  }
5024
4167
  } catch {
5025
4168
  }
5026
- const parent = path9.dirname(directory);
4169
+ const parent = path8.dirname(directory);
5027
4170
  if (parent === directory) return void 0;
5028
4171
  directory = parent;
5029
4172
  }
5030
4173
  };
5031
4174
  var readCache = (cachePath) => {
5032
4175
  try {
5033
- const parsed = JSON.parse(fs4.readFileSync(cachePath, "utf8"));
4176
+ const parsed = JSON.parse(fs3.readFileSync(cachePath, "utf8"));
5034
4177
  if (typeof parsed.checkedAt !== "number" || !Number.isFinite(parsed.checkedAt)) return void 0;
5035
4178
  if (typeof parsed.latest !== "string" || parsed.latest.length === 0) return void 0;
5036
4179
  return {
@@ -5077,32 +4220,32 @@ var checkFabricUpdate = async (options) => {
5077
4220
  const cached = readCache(cachePath);
5078
4221
  const age = cached === void 0 ? void 0 : now - cached.checkedAt;
5079
4222
  const fresh = cached !== void 0 && age !== void 0 && age >= 0 && age < UPDATE_CHECK_TTL_MS;
5080
- let record2;
4223
+ let record;
5081
4224
  if (fresh && cached !== void 0) {
5082
- record2 = cached;
4225
+ record = cached;
5083
4226
  } else {
5084
4227
  const latest = await fetchLatestVersion(
5085
4228
  options.fetchImpl ?? globalThis.fetch,
5086
4229
  options.timeoutMs ?? REQUEST_TIMEOUT_MS
5087
4230
  );
5088
4231
  if (latest === void 0) return void 0;
5089
- record2 = {
4232
+ record = {
5090
4233
  format: 1,
5091
4234
  checkedAt: now,
5092
4235
  latest,
5093
4236
  ...cached?.notified === void 0 ? {} : { notified: cached.notified }
5094
4237
  };
5095
4238
  }
5096
- const behind = compareVersions(record2.latest, installed) === 1;
5097
- const announce = behind && record2.notified !== record2.latest;
4239
+ const behind = compareVersions(record.latest, installed) === 1;
4240
+ const announce = behind && record.notified !== record.latest;
5098
4241
  if (!fresh || announce) {
5099
- writeCache(cachePath, announce ? { ...record2, notified: record2.latest } : record2);
4242
+ writeCache(cachePath, announce ? { ...record, notified: record.latest } : record);
5100
4243
  }
5101
4244
  if (!announce) return void 0;
5102
4245
  return {
5103
4246
  installed,
5104
- latest: record2.latest,
5105
- 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}`
5106
4249
  };
5107
4250
  } catch {
5108
4251
  return void 0;
@@ -5131,7 +4274,7 @@ var scheduleFabricUpdateCheck = async (context, options) => {
5131
4274
  };
5132
4275
 
5133
4276
  // src/ui/controller.ts
5134
- import path10 from "node:path";
4277
+ import path9 from "node:path";
5135
4278
 
5136
4279
  // src/ui/snapshot.ts
5137
4280
  var MAX_UI_AGENTS = 240;
@@ -5188,57 +4331,57 @@ var createDashboardSnapshot = (state, events, context, activityRuns) => {
5188
4331
  if (leftLaunch !== rightLaunch) return leftLaunch ? -1 : 1;
5189
4332
  return left.call.startedAt - right.call.startedAt;
5190
4333
  });
5191
- const agentFromRecord = (record2, nestingDepth, parentId, parent) => {
4334
+ const agentFromRecord = (record, nestingDepth, parentId, parent) => {
5192
4335
  const linked = parentId ? void 0 : agentLinks.find(
5193
- ({ 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))
5194
4337
  );
5195
4338
  const base = {
5196
- id: record2.id,
5197
- name: record2.name,
5198
- status: record2.status,
5199
- runner: record2.runner,
5200
- transport: record2.transport,
5201
- cwd: record2.cwd,
5202
- ...!isRunRecord(record2) && linked ? { startedAt: linked.call.startedAt } : {},
5203
- ...record2.model ? { model: record2.model } : {},
5204
- ...record2.thinking ? { thinking: record2.thinking } : {},
5205
- ...record2.attachCommand ? { attachCommand: record2.attachCommand } : {},
5206
- ...isRunRecord(record2) && record2.logFile ? { logFile: record2.logFile } : {},
5207
- ...record2.branch ? { branch: record2.branch } : {},
5208
- ...record2.worktree ? { worktree: record2.worktree } : {},
5209
- ...record2.actorId ? { actorId: record2.actorId } : {},
5210
- ...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 } : {},
5211
4354
  ...parentId ? { parentId } : {},
5212
4355
  ...nestingDepth > 0 ? { nestingDepth } : {},
5213
4356
  ...linked ? { runId: linked.runId } : parent?.runId ? { runId: parent.runId } : {},
5214
4357
  ...linked?.call.phaseId ? { phaseId: linked.call.phaseId } : parent?.phaseId ? { phaseId: parent.phaseId } : {}
5215
4358
  };
5216
- if (!isRunRecord(record2)) return base;
4359
+ if (!isRunRecord(record)) return base;
5217
4360
  return {
5218
4361
  ...base,
5219
- task: record2.task,
5220
- startedAt: record2.startedAt,
5221
- updatedAt: record2.updatedAt,
5222
- ...record2.finishedAt !== void 0 ? { finishedAt: record2.finishedAt } : {},
5223
- ...record2.currentTool ? { currentTool: record2.currentTool } : {},
5224
- turns: record2.turns,
5225
- toolCalls: record2.toolCalls,
5226
- usage: { ...record2.usage },
5227
- ...record2.text ? { text: record2.text } : {},
5228
- ...record2.value !== void 0 ? { value: structuredClone(record2.value) } : {},
5229
- ...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 } : {}
5230
4373
  };
5231
4374
  };
5232
4375
  const allAgents = [];
5233
- const appendAgent = (record2, nestingDepth, parentId, parent) => {
5234
- const agent = agentFromRecord(record2, nestingDepth, parentId, parent);
4376
+ const appendAgent = (record, nestingDepth, parentId, parent) => {
4377
+ const agent = agentFromRecord(record, nestingDepth, parentId, parent);
5235
4378
  allAgents.push(agent);
5236
- if (!isRunRecord(record2)) return;
5237
- for (const nested of record2.nestedAgents ?? []) {
5238
- 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);
5239
4382
  }
5240
4383
  };
5241
- for (const record2 of agentRecords) appendAgent(record2, 0);
4384
+ for (const record of agentRecords) appendAgent(record, 0);
5242
4385
  const participants = typeof state.participantInfos === "function" ? state.participantInfos({ scope: "project" }) : [];
5243
4386
  const participantById = new Map(participants.map((participant) => [participant.id, participant]));
5244
4387
  const actors = state.actors.list().map((actor) => {
@@ -5988,7 +5131,7 @@ var FabricUiController = class {
5988
5131
  logFile: actor.worker.logFile
5989
5132
  };
5990
5133
  }
5991
- 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;
5992
5135
  if (retained) return { id: actor.id, status: actor.status, logFile: retained };
5993
5136
  if (actor.sessionFile) {
5994
5137
  return { id: actor.id, status: actor.status, logFile: actor.sessionFile };
@@ -6147,15 +5290,15 @@ var REFRESH_CARDS_PER_TICK = 3;
6147
5290
 
6148
5291
  // src/index.ts
6149
5292
  import { existsSync as existsSync4 } from "node:fs";
6150
- import path11 from "node:path";
5293
+ import path10 from "node:path";
6151
5294
  import { fileURLToPath as fileURLToPath2 } from "node:url";
6152
- var FABRIC_EXTENSION_ENTRY_PATH = path11.resolve(fileURLToPath2(import.meta.url));
6153
- 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);
6154
5297
  var FABRIC_RUNTIME_PATHS = {
6155
5298
  extension: FABRIC_EXTENSION_ENTRY_PATH,
6156
- worker: path11.join(FABRIC_ENTRY_DIR, "worker.js"),
6157
- residentHost: path11.join(FABRIC_ENTRY_DIR, "residency", "launcher.js"),
6158
- 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")
6159
5302
  };
6160
5303
  var FABRIC_SKILLS_DIR = FABRIC_RUNTIME_PATHS.skills;
6161
5304
  var componentRegistrationFrom = (value) => {
@@ -6199,6 +5342,7 @@ async function ompFabric(omp) {
6199
5342
  );
6200
5343
  const pendingHandoffs = /* @__PURE__ */ new Map();
6201
5344
  let lcmRuntime;
5345
+ const lcmLoader = createLcmRuntimeLoader();
6202
5346
  let lcmCwd;
6203
5347
  let lcmProjectKey;
6204
5348
  const toolOwnership = new FabricToolOwnership(omp);
@@ -6474,16 +5618,18 @@ async function ompFabric(omp) {
6474
5618
  lcmProjectKey = void 0;
6475
5619
  }
6476
5620
  if (state.config.compaction.engine === "lcm" && !lcmRuntime) {
6477
- lcmRuntime = new LcmRuntime(context, () => ({
5621
+ const LcmRuntimeClass = await lcmLoader.load(context);
5622
+ if (LcmRuntimeClass) lcmRuntime = new LcmRuntimeClass(context, () => ({
6478
5623
  ...state.config.compaction.summaryModel ? { summaryModel: state.config.compaction.summaryModel } : {},
6479
5624
  maxLeafEntries: state.config.compaction.lcmMaxLeafEntries,
6480
5625
  maxCondenseChildren: state.config.compaction.lcmMaxCondenseChildren,
5626
+ maxMaintenancePasses: state.config.compaction.lcmMaintenancePasses,
6481
5627
  lcmMaxInputChars: state.config.compaction.lcmMaxInputChars,
6482
5628
  lcmMaxOutputTokens: state.config.compaction.lcmMaxOutputTokens,
6483
5629
  lcmMaxOutputChars: state.config.compaction.lcmMaxOutputChars
6484
5630
  }));
6485
- lcmCwd = context.cwd;
6486
- lcmProjectKey = lcmRuntime.projectKey;
5631
+ lcmCwd = lcmRuntime ? context.cwd : void 0;
5632
+ lcmProjectKey = lcmRuntime?.projectKey;
6487
5633
  }
6488
5634
  if (state.config.compaction.engine === "lcm" && lcmRuntime) await lcmRuntime.reconcileSelectedSession();
6489
5635
  refreshCodePreviewSettings();
@@ -6633,7 +5779,7 @@ async function ompFabric(omp) {
6633
5779
  await state.publishHostLifecycle("omp.session_compact", event);
6634
5780
  });
6635
5781
  registerCompactionHook(omp, {
6636
- 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,
6637
5783
  getTargetContextRatio: () => state.cwd ? state.config.compaction.targetContextRatio : DEFAULT_FABRIC_CONFIG.compaction.targetContextRatio,
6638
5784
  lcm: {
6639
5785
  compact: (input) => lcmRuntime?.compact(input)