omp-fabric 1.3.3 → 1.4.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 (73) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/agents/handoff.d.ts.map +1 -1
  3. package/dist/chunks/{chunk-WZAH7BO2.js → chunk-2J2GOB4A.js} +30 -11
  4. package/dist/chunks/chunk-2J2GOB4A.js.map +7 -0
  5. package/dist/chunks/{chunk-2WDZT27Q.js → chunk-3H5LZWAG.js} +253 -4
  6. package/dist/chunks/chunk-3H5LZWAG.js.map +7 -0
  7. package/dist/chunks/{chunk-PYXBHMSL.js → chunk-IMSGR6OW.js} +3 -3
  8. package/dist/chunks/chunk-IY3IQPRQ.js +308 -0
  9. package/dist/chunks/chunk-IY3IQPRQ.js.map +7 -0
  10. package/dist/chunks/{chunk-VSSC4IDU.js → chunk-RTDJFHGH.js} +2 -2
  11. package/dist/chunks/{chunk-45URY66G.js → chunk-S4I7RMR5.js} +3 -3
  12. package/dist/chunks/{chunk-ZP3S74LW.js → chunk-SG4GVSWX.js} +49 -26
  13. package/dist/chunks/chunk-SG4GVSWX.js.map +7 -0
  14. package/dist/chunks/{chunk-JOWMM3EX.js → chunk-WPU4PKXQ.js} +135 -24
  15. package/dist/chunks/chunk-WPU4PKXQ.js.map +7 -0
  16. package/dist/chunks/{chunk-Z5AP4M6K.js → chunk-YOAZ5SO7.js} +12 -2
  17. package/dist/chunks/{chunk-Z5AP4M6K.js.map → chunk-YOAZ5SO7.js.map} +2 -2
  18. package/dist/compaction/branch-summary.d.ts +1 -1
  19. package/dist/compaction/branch-summary.d.ts.map +1 -1
  20. package/dist/compaction/hook.d.ts +21 -1
  21. package/dist/compaction/hook.d.ts.map +1 -1
  22. package/dist/compaction/hook.js +1 -1
  23. package/dist/compaction/lcm-maintenance.d.ts +103 -0
  24. package/dist/compaction/lcm-maintenance.d.ts.map +1 -0
  25. package/dist/compaction/lcm-model.d.ts +41 -0
  26. package/dist/compaction/lcm-model.d.ts.map +1 -0
  27. package/dist/compaction/lcm-runtime.d.ts +106 -0
  28. package/dist/compaction/lcm-runtime.d.ts.map +1 -0
  29. package/dist/config-migrations.d.ts +1 -1
  30. package/dist/config-migrations.d.ts.map +1 -1
  31. package/dist/config.d.ts +7 -1
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/fabric-runtime-state.d.ts +2 -0
  34. package/dist/fabric-runtime-state.d.ts.map +1 -1
  35. package/dist/fabric-runtime-state.js +12 -7
  36. package/dist/fabric-runtime-state.js.map +2 -2
  37. package/dist/fabric-state.d.ts +2 -1
  38. package/dist/fabric-state.d.ts.map +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1030 -108
  41. package/dist/index.js.map +4 -4
  42. package/dist/memory/lcm-adapter.d.ts +97 -0
  43. package/dist/memory/lcm-adapter.d.ts.map +1 -0
  44. package/dist/providers/memory-provider.d.ts +8 -0
  45. package/dist/providers/memory-provider.d.ts.map +1 -1
  46. package/dist/providers/memory-provider.js +2 -1
  47. package/dist/residency/host.js +6 -5
  48. package/dist/residency/omp-entry.js +6 -5
  49. package/dist/residency/omp-entry.js.map +1 -1
  50. package/dist/runtime/core-override-guest-types.js +1 -1
  51. package/dist/runtime/guest-types.d.ts +1 -1
  52. package/dist/runtime/guest-types.d.ts.map +1 -1
  53. package/dist/runtime/guest-types.js +1 -1
  54. package/dist/storage/lcm-ledger.d.ts +107 -0
  55. package/dist/storage/lcm-ledger.d.ts.map +1 -0
  56. package/dist/storage/lcm-migration.d.ts +67 -0
  57. package/dist/storage/lcm-migration.d.ts.map +1 -0
  58. package/dist/ui/settings.js +5 -5
  59. package/dist/ui/settings.js.map +2 -2
  60. package/docs/adr/0001-lcm-durable-context-ledger.md +6 -0
  61. package/docs/agents/domain.md +25 -0
  62. package/docs/agents/issue-tracker.md +25 -0
  63. package/docs/agents/triage-labels.md +11 -0
  64. package/docs/compaction.md +78 -312
  65. package/docs/configuration.md +3 -3
  66. package/package.json +1 -1
  67. package/dist/chunks/chunk-2WDZT27Q.js.map +0 -7
  68. package/dist/chunks/chunk-JOWMM3EX.js.map +0 -7
  69. package/dist/chunks/chunk-WZAH7BO2.js.map +0 -7
  70. package/dist/chunks/chunk-ZP3S74LW.js.map +0 -7
  71. /package/dist/chunks/{chunk-PYXBHMSL.js.map → chunk-IMSGR6OW.js.map} +0 -0
  72. /package/dist/chunks/{chunk-VSSC4IDU.js.map → chunk-RTDJFHGH.js.map} +0 -0
  73. /package/dist/chunks/{chunk-45URY66G.js.map → chunk-S4I7RMR5.js.map} +0 -0
package/dist/index.js CHANGED
@@ -67,7 +67,7 @@ import {
67
67
  resolveFabricIdentity,
68
68
  settleInPlacePrewalk,
69
69
  streamedToolCallName
70
- } from "./chunks/chunk-VSSC4IDU.js";
70
+ } from "./chunks/chunk-RTDJFHGH.js";
71
71
  import "./chunks/chunk-7VIQNUPS.js";
72
72
  import {
73
73
  AgentTranscriptReader
@@ -76,7 +76,7 @@ import "./chunks/chunk-DLXPTBPL.js";
76
76
  import {
77
77
  GUEST_TYPE_DECLARATIONS,
78
78
  OMP_CORE_COMPATIBILITY_ARGUMENT_TYPE_NAMES
79
- } from "./chunks/chunk-Z5AP4M6K.js";
79
+ } from "./chunks/chunk-YOAZ5SO7.js";
80
80
  import {
81
81
  NESTED_TOOL_CALL_ID_PREFIX,
82
82
  getActiveRepairCompiler,
@@ -168,7 +168,10 @@ import {
168
168
  replaySuccessfulCallsAsync,
169
169
  schemaDigest
170
170
  } from "./chunks/chunk-4A4MVZQW.js";
171
- import "./chunks/chunk-FX3XLQYI.js";
171
+ import {
172
+ sessionDirNamesForCwd,
173
+ sessionsDirRoot
174
+ } from "./chunks/chunk-FX3XLQYI.js";
172
175
  import "./chunks/chunk-6QBPSZJ5.js";
173
176
  import {
174
177
  FABRIC_COMPONENT_DISCOVER_EVENT,
@@ -186,9 +189,12 @@ import {
186
189
  readFabricToolResultProxyDetailsV1
187
190
  } from "./chunks/chunk-RBA54WC7.js";
188
191
  import {
192
+ LcmModelAdapter,
189
193
  awaitPeerSettle,
190
- buildPeerCards
191
- } from "./chunks/chunk-JOWMM3EX.js";
194
+ buildLcmPrompt,
195
+ buildPeerCards,
196
+ emergencyReduce
197
+ } from "./chunks/chunk-WPU4PKXQ.js";
192
198
  import {
193
199
  DEFAULT_FABRIC_CONFIG,
194
200
  effectiveToolCaptureConfig,
@@ -196,13 +202,20 @@ import {
196
202
  loadFabricConfigForScope,
197
203
  resolveAgentDir,
198
204
  saveFabricConfig
199
- } from "./chunks/chunk-WZAH7BO2.js";
205
+ } from "./chunks/chunk-2J2GOB4A.js";
200
206
  import {
201
207
  FABRIC_ACTOR_OMP_HOST_EVENTS
202
208
  } from "./chunks/chunk-3LI7YO4M.js";
203
209
  import {
204
210
  defaultCodePreviewSettings
205
211
  } from "./chunks/chunk-JSBTB3P6.js";
212
+ import {
213
+ LcmLedger,
214
+ canonicalLcmPayload,
215
+ canonicalProjectIdentity,
216
+ defaultLedgerPath,
217
+ hashLcmPayload
218
+ } from "./chunks/chunk-IY3IQPRQ.js";
206
219
  import {
207
220
  FABRIC_EXECUTION_GUIDANCE_SLOT,
208
221
  MAX_FABRIC_MODEL_GUIDANCE_CONTENT_CHARS,
@@ -221,7 +234,7 @@ import {
221
234
  fabricExecTitleHintCached,
222
235
  normalizeRunDisplay,
223
236
  registerCompactionHook
224
- } from "./chunks/chunk-ZP3S74LW.js";
237
+ } from "./chunks/chunk-SG4GVSWX.js";
225
238
  import {
226
239
  fabricWriteBindings
227
240
  } from "./chunks/chunk-3J7BKUNG.js";
@@ -263,13 +276,13 @@ var clearTimingInterval = (state) => {
263
276
  };
264
277
  var ensureTimingInterval = (state, invalidate) => {
265
278
  state.codePreviewTimingInterval ??= setInterval(() => {
266
- const token = (state.codePreviewTimingOnlyRenderToken ?? 0) + 1;
267
- state.codePreviewTimingOnlyRenderToken = token;
279
+ const token2 = (state.codePreviewTimingOnlyRenderToken ?? 0) + 1;
280
+ state.codePreviewTimingOnlyRenderToken = token2;
268
281
  try {
269
282
  invalidate();
270
283
  } finally {
271
284
  queueMicrotask(() => {
272
- if (state.codePreviewTimingOnlyRenderToken === token) {
285
+ if (state.codePreviewTimingOnlyRenderToken === token2) {
273
286
  delete state.codePreviewTimingOnlyRenderToken;
274
287
  }
275
288
  });
@@ -805,10 +818,10 @@ var extractContentText = (content) => {
805
818
  };
806
819
  var summarizeLogLine = (entry) => {
807
820
  if (typeof entry !== "object" || entry === null) return truncateMiddle(String(entry), 200);
808
- const record = entry;
809
- const type = typeof record.type === "string" ? record.type : void 0;
810
- const tool = typeof record.toolName === "string" ? record.toolName : void 0;
811
- const msg = record.message;
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;
812
825
  if (typeof msg === "object" && msg !== null && !Array.isArray(msg)) {
813
826
  const m = msg;
814
827
  const role = typeof m.role === "string" ? m.role : "message";
@@ -820,13 +833,13 @@ var summarizeLogLine = (entry) => {
820
833
  if (type) {
821
834
  const bits = [type];
822
835
  if (tool) bits.push(tool);
823
- const model = typeof record.modelId === "string" ? record.modelId : void 0;
824
- const provider = typeof record.provider === "string" && !model ? record.provider : void 0;
836
+ const model = typeof record2.modelId === "string" ? record2.modelId : void 0;
837
+ const provider = typeof record2.provider === "string" && !model ? record2.provider : void 0;
825
838
  if (provider) bits.push(provider);
826
839
  if (model) bits.push(model);
827
840
  return bits.join(" ");
828
841
  }
829
- return truncateMiddle(JSON.stringify(record), 160);
842
+ return truncateMiddle(JSON.stringify(record2), 160);
830
843
  };
831
844
  var resolvePrewalkModel = async (state, context) => {
832
845
  const configured = state.config.prewalk.model?.trim();
@@ -1843,8 +1856,877 @@ var FabricSpeculationWarmup = class {
1843
1856
  }
1844
1857
  };
1845
1858
 
1846
- // src/core/tool-ownership.ts
1859
+ // src/storage/lcm-migration.ts
1860
+ import crypto from "node:crypto";
1861
+ import fs2 from "node:fs";
1847
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);
1939
+ try {
1940
+ if (fs2.statSync(dir).isDirectory()) {
1941
+ found = true;
1942
+ scan(dir);
1943
+ }
1944
+ } catch (error) {
1945
+ if (error.code !== "ENOENT") incomplete++;
1946
+ }
1947
+ }
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
+ };
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 (ordinal !== 1 || row.type !== "session" || !nonblank(row.id)) {
2053
+ counts.malformed++;
2054
+ break;
2055
+ }
2056
+ header = row;
2057
+ sessionId = row.id;
2058
+ cwd = nonblank(row.cwd) ? row.cwd : options.liveCwd;
2059
+ if (!cwd) {
2060
+ counts.malformed++;
2061
+ break;
2062
+ }
2063
+ const projectInput = options.liveCwd ? { recordedCwd: cwd, liveCwd: options.liveCwd } : { recordedCwd: cwd };
2064
+ projectKey = canonicalProjectIdentity(projectInput).key;
2065
+ if (options.ledger && options.ledger.project.key !== projectKey) {
2066
+ counts.errors++;
2067
+ break;
2068
+ }
2069
+ if (options.apply) {
2070
+ if (options.ledger) ledger = options.ledger;
2071
+ else if (options.ledgerRoot) ledger = new LcmLedger({ rootDir: options.ledgerRoot, project: { recordedCwd: cwd } });
2072
+ else ledger = new LcmLedger({ project: { recordedCwd: cwd } });
2073
+ database = ledger.db;
2074
+ database.exec(CHECKPOINT_SCHEMA);
2075
+ } else if (options.ledger) database = options.ledger.db;
2076
+ else {
2077
+ const dbPath = defaultLedgerPath(options.ledgerRoot, projectKey);
2078
+ if (fs2.existsSync(dbPath)) database = new DatabaseSync(dbPath, { readOnly: true });
2079
+ }
2080
+ continue;
2081
+ }
2082
+ if (row.type === "session" || !nonblank(row.type) || !nonblank(row.id) || row.parentId !== null && row.parentId !== void 0 && typeof row.parentId !== "string") {
2083
+ counts.malformed++;
2084
+ continue;
2085
+ }
2086
+ const message = record(row.message);
2087
+ if (row.type === "message" && (!message || !nonblank(message.role))) {
2088
+ counts.malformed++;
2089
+ continue;
2090
+ }
2091
+ let at;
2092
+ try {
2093
+ at = typeof row.timestamp === "string" ? utc(row.timestamp) : NaN;
2094
+ } catch {
2095
+ at = NaN;
2096
+ }
2097
+ if (!Number.isFinite(at)) {
2098
+ counts.malformed++;
2099
+ continue;
2100
+ }
2101
+ if (!options.onDemand && (at < window.since || at > window.until)) {
2102
+ counts.skippedOutOfWindow++;
2103
+ continue;
2104
+ }
2105
+ counts.eligible++;
2106
+ const payloadJson = canonicalLcmPayload(row);
2107
+ const contentHash = hashLcmPayload(row);
2108
+ const identity = JSON.stringify([projectKey, sessionId, row.id, contentHash]);
2109
+ const progress = { projectKey, sessionPath: file, sourceHash, lineOrdinal: ordinal, entryId: row.id, contentHash };
2110
+ 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;
2111
+ if (duplicate) counts.skippedDuplicate++;
2112
+ else if (ledger) {
2113
+ ledger.appendRaw({
2114
+ projectKey,
2115
+ sessionId,
2116
+ entryId: row.id,
2117
+ role: message && typeof message.role === "string" ? message.role : row.type,
2118
+ content,
2119
+ payloadJson,
2120
+ parentEntryId: typeof row.parentId === "string" ? row.parentId : null,
2121
+ branch: typeof row.branch === "string" ? row.branch : typeof row.branchId === "string" ? row.branchId : null,
2122
+ ...cwd ? { recordedCwd: cwd } : {},
2123
+ createdAt: at
2124
+ });
2125
+ counts.imported++;
2126
+ } else if (!duplicate) counts.imported++;
2127
+ drySeen.add(identity);
2128
+ 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);
2129
+ options.onProgress?.(progress);
2130
+ }
2131
+ if (!header && data.length === 0) counts.malformed++;
2132
+ } catch (error) {
2133
+ counts.errors++;
2134
+ if (options.onProgress) throw error;
2135
+ } finally {
2136
+ if (ledger && ledger !== options.ledger) ledger.close();
2137
+ else if (!ledger && database && database !== options.ledger?.db) database.close();
2138
+ }
2139
+ }
2140
+ result.exitCode = counts.errors || counts.malformed || counts.oversized || counts.incompleteDiscovery && !options.allowIncompleteDiscovery ? 1 : 0;
2141
+ return result;
2142
+ }
2143
+
2144
+ // src/compaction/lcm-maintenance.ts
2145
+ import crypto2 from "node:crypto";
2146
+ var DEFAULT_LCM_BUDGET = { calls: 32, inputTokens: 2e6, outputTokens: 128e3, cost: Number.POSITIVE_INFINITY, wallMs: 6e4, sessionCalls: 16 };
2147
+ var hash2 = (v) => hashLcmPayload(v);
2148
+ var parse = (v) => JSON.parse(String(v));
2149
+ var rawHash = (e) => e.payloadHash;
2150
+ var token = () => crypto2.randomUUID();
2151
+ var LcmMaintenance = class {
2152
+ constructor(ledger, options = {}) {
2153
+ this.ledger = ledger;
2154
+ this.projectKey = ledger.project.key;
2155
+ this.now = options.now ?? Date.now;
2156
+ this.ownerId = options.ownerId ?? crypto2.randomUUID();
2157
+ this.policyHash = options.policyHash ?? hash2("lcm-policy-v1");
2158
+ this.maxLeaf = options.maxLeafEntries ?? 8;
2159
+ this.maxChildren = options.maxCondenseChildren ?? 4;
2160
+ this.maxInputChars = options.maxInputChars ?? 48e3;
2161
+ this.maxOutputChars = options.maxOutputChars ?? 4096;
2162
+ this.budget = { ...DEFAULT_LCM_BUDGET, ...options.budget };
2163
+ }
2164
+ projectKey;
2165
+ now;
2166
+ ownerId;
2167
+ policyHash;
2168
+ maxLeaf;
2169
+ maxChildren;
2170
+ maxInputChars;
2171
+ maxOutputChars;
2172
+ budget;
2173
+ listNodes(limit = 100) {
2174
+ 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) => {
2175
+ const node = parse(r.payload);
2176
+ if (node.projectKey !== this.projectKey) throw new Error("node project does not match ledger project");
2177
+ return node;
2178
+ }));
2179
+ }
2180
+ getNode(nodeId) {
2181
+ return this.ledger.readOnly((db) => {
2182
+ const row = db.prepare("SELECT payload FROM summary_nodes WHERE node_id=? AND project_key=?").get(nodeId, this.projectKey);
2183
+ if (!row?.payload) return void 0;
2184
+ const node = parse(row.payload);
2185
+ if (node.projectKey !== this.projectKey) throw new Error("node project does not match ledger project");
2186
+ return node;
2187
+ });
2188
+ }
2189
+ getFrontier(sessionId, branch, activeSources) {
2190
+ 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)))));
2191
+ const covered = new Set(nodes.flatMap((n) => n.children));
2192
+ return nodes.filter((n) => !covered.has(n.nodeId));
2193
+ }
2194
+ listJobs(limit = 100) {
2195
+ 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) => {
2196
+ const job = parse(r.payload);
2197
+ if (job.projectKey !== this.projectKey) throw new Error("job project does not match ledger project");
2198
+ return job;
2199
+ }));
2200
+ }
2201
+ selectLeaf(entries = this.ledger.readRaw(this.projectKey), branch) {
2202
+ const first = entries[0];
2203
+ if (!first) return [];
2204
+ 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));
2205
+ const covered = new Set(nodes.flatMap((n) => n.sources.map((s) => this.sourceKey(s))));
2206
+ 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);
2207
+ }
2208
+ selectCondensation(sessionId, activeSources, branch) {
2209
+ 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));
2210
+ const eligible = new Set(nodes.map((n) => n.nodeId));
2211
+ 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));
2212
+ const consumed = new Set(edges.filter((e) => eligible.has(e.parent_id)).map((e) => e.child_id));
2213
+ const candidates = nodes.filter((n) => !consumed.has(n.nodeId));
2214
+ const first = candidates[0];
2215
+ if (!first) return [];
2216
+ const depth = Math.min(...candidates.map((n) => n.depth));
2217
+ const sameDepth = candidates.filter((n) => n.depth === depth);
2218
+ if (depth > 0 && sameDepth.length < 2) return [];
2219
+ 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);
2220
+ }
2221
+ createLeaf(entries = this.selectLeaf()) {
2222
+ if (!entries.length) return void 0;
2223
+ const branches = new Set(entries.map((entry) => entry.branch).filter((value) => typeof value === "string"));
2224
+ if (branches.size > 1) throw new Error("cross-branch ranges");
2225
+ const branch = [...branches][0] ?? null;
2226
+ const source = entries.map((e, index) => {
2227
+ if (e.projectKey !== this.projectKey) throw new Error("entry project does not match ledger project");
2228
+ const payload = parse(e.payloadJson);
2229
+ if (typeof payload.type !== "string" || !payload.type || payload.id !== e.entryId) throw new Error("invalid raw payload");
2230
+ if (hash2(payload) !== rawHash(e)) throw new Error("payload hash mismatch");
2231
+ return { sessionId: e.sessionId, entryId: e.entryId, revision: e.revision, payloadHash: rawHash(e) };
2232
+ });
2233
+ this.validateSources(source);
2234
+ 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() };
2235
+ this.publish(node);
2236
+ return node;
2237
+ }
2238
+ createCondensed(children = this.selectCondensation()) {
2239
+ if (!children.length) return void 0;
2240
+ if (children.some((c) => c.state !== "ready")) throw new Error("condensation requires ready children");
2241
+ 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");
2242
+ const storedChildren = children.map((child) => this.getNode(child.nodeId));
2243
+ if (storedChildren.some((child) => !child || child.projectKey !== this.projectKey)) throw new Error("child project does not match ledger project");
2244
+ if (storedChildren.some((child, index) => JSON.stringify(child) !== JSON.stringify(children[index]))) throw new Error("child payload changed");
2245
+ const concreteBranches = new Set(storedChildren.map((c) => c.branch).filter((value) => typeof value === "string"));
2246
+ if (concreteBranches.size > 1) throw new Error("cross-branch ranges");
2247
+ const branch = [...concreteBranches][0] ?? null;
2248
+ const source = [...new Map(storedChildren.flatMap((c) => c.sources).map((value) => [this.sourceKey(value), value])).values()];
2249
+ this.validateSources(source);
2250
+ 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() };
2251
+ this.publish(node);
2252
+ return node;
2253
+ }
2254
+ validateSources(source) {
2255
+ const seen = /* @__PURE__ */ new Set();
2256
+ for (const s of source) {
2257
+ if (seen.has(this.sourceKey(s))) throw new Error("duplicate source identity");
2258
+ seen.add(this.sourceKey(s));
2259
+ }
2260
+ if (new Set(source.map((s) => s.sessionId)).size !== 1) throw new Error("cross-session ranges");
2261
+ }
2262
+ sourceKey(source) {
2263
+ return `${source.sessionId}:${source.entryId}:${source.revision}`;
2264
+ }
2265
+ publish(node) {
2266
+ this.ledger.transaction((db) => {
2267
+ if (node.children.includes(node.nodeId)) throw new Error("cycle");
2268
+ 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");
2269
+ 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);
2270
+ for (const child of node.children) db.prepare("INSERT OR IGNORE INTO summary_edges(parent_id,child_id) VALUES(?,?)").run(node.nodeId, child);
2271
+ const job = this.job(node);
2272
+ 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);
2273
+ });
2274
+ }
2275
+ job(node) {
2276
+ const t = this.now();
2277
+ 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 };
2278
+ }
2279
+ claim(jobId, ownerId = this.ownerId) {
2280
+ return this.ledger.transaction((db) => {
2281
+ const row = db.prepare("SELECT payload FROM maintenance_jobs WHERE job_id=? AND project_key=?").get(jobId, this.projectKey);
2282
+ if (!row?.payload) throw new Error("job not found");
2283
+ const old = parse(row.payload);
2284
+ if (old.projectKey !== this.projectKey) throw new Error("job project does not match ledger project");
2285
+ const t = this.now();
2286
+ const expired = old.state === "running" && (old.leaseUntil ?? 0) <= t;
2287
+ if (old.state === "running" && !expired) throw new Error("job lease held");
2288
+ if (old.state !== "pending" && !expired || old.eligibleAt > t || old.nextRetryAt > t) throw new Error("job not eligible");
2289
+ 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);
2290
+ if (active.length) throw new Error("project lease held");
2291
+ const nodeRow = db.prepare("SELECT payload FROM summary_nodes WHERE node_id=? AND project_key=?").get(old.nodeId, this.projectKey);
2292
+ const sessionId = nodeRow?.payload ? parse(nodeRow.payload).sessionId : "";
2293
+ const day = this.day(t);
2294
+ const projectUsage = this.usage(db, day);
2295
+ const sessionUsage = this.usage(db, day, sessionId);
2296
+ 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");
2297
+ const out = { ...old, state: "running", ownerId, leaseToken: token(), leaseUntil: t + 3e4, updatedAt: t };
2298
+ 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);
2299
+ return out;
2300
+ });
2301
+ }
2302
+ claimEmergency(jobId, ownerId = this.ownerId) {
2303
+ return this.ledger.transaction((db) => {
2304
+ const row = db.prepare("SELECT payload FROM maintenance_jobs WHERE job_id=? AND project_key=?").get(jobId, this.projectKey);
2305
+ if (!row?.payload) throw new Error("job not found");
2306
+ const old = parse(row.payload);
2307
+ if (old.projectKey !== this.projectKey) throw new Error("job project does not match ledger project");
2308
+ const t = this.now();
2309
+ const expired = old.state === "running" && (old.leaseUntil ?? 0) <= t;
2310
+ if (old.state === "running" && !expired) throw new Error("job lease held");
2311
+ if (old.state === "completed") throw new Error("job already completed");
2312
+ const out = { ...old, state: "running", ownerId, leaseToken: token(), leaseUntil: t + 3e4, updatedAt: t };
2313
+ 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);
2314
+ return out;
2315
+ });
2316
+ }
2317
+ renew(job) {
2318
+ return this.fenced(job, (old) => ({ ...old, leaseUntil: this.now() + 3e4, updatedAt: this.now() }));
2319
+ }
2320
+ validateText(text) {
2321
+ if (typeof text !== "string" || text.length === 0) throw new Error("invalid summary text");
2322
+ let length = 0;
2323
+ for (const _ of text) {
2324
+ length += 1;
2325
+ if (length > this.maxOutputChars) throw new Error("summary exceeds output bound");
2326
+ }
2327
+ }
2328
+ complete(job, result) {
2329
+ this.validateText(result.text);
2330
+ return this.ledger.transaction((db) => {
2331
+ const current = this.readJob(db, job.jobId);
2332
+ this.assertLease(current, job);
2333
+ if (current.nodeId !== job.nodeId) throw new Error("job node mismatch");
2334
+ const row = db.prepare("SELECT payload FROM summary_nodes WHERE node_id=? AND project_key=?").get(job.nodeId, this.projectKey);
2335
+ if (!row?.payload) throw new Error("node not found");
2336
+ const node = parse(row.payload);
2337
+ if (node.projectKey !== this.projectKey || node.nodeId !== current.nodeId) throw new Error("node project does not match ledger project");
2338
+ for (const source of node.sources) {
2339
+ 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);
2340
+ if (!raw?.payload_json || hash2(parse(raw.payload_json)) !== source.payloadHash || raw.session_id !== source.sessionId) throw new Error("stale source");
2341
+ }
2342
+ if (![result.inputTokens, result.outputTokens, result.cost, result.wallMs].every((value) => Number.isFinite(value) && value >= 0)) throw new Error("invalid model usage");
2343
+ const projectUsage = this.usage(db, this.day(this.now()));
2344
+ const sessionUsage = this.usage(db, this.day(this.now()), node.sessionId);
2345
+ 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");
2346
+ const out = { ...node, state: "ready", text: result.text, modelHash: result.modelHash };
2347
+ this.account(db, node.sessionId, result);
2348
+ db.prepare("UPDATE summary_nodes SET payload=? WHERE node_id=? AND project_key=?").run(JSON.stringify(out), node.nodeId, this.projectKey);
2349
+ const { ownerId: _ownerId, leaseToken: _leaseToken, leaseUntil: _leaseUntil, ...base } = current;
2350
+ const done = { ...base, state: "completed", updatedAt: this.now() };
2351
+ 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);
2352
+ return out;
2353
+ });
2354
+ }
2355
+ completeEmergency(job, text) {
2356
+ this.validateText(text);
2357
+ return this.ledger.transaction((db) => {
2358
+ const current = this.readJob(db, job.jobId);
2359
+ this.assertLease(current, job);
2360
+ if (current.nodeId !== job.nodeId) throw new Error("job node mismatch");
2361
+ const row = db.prepare("SELECT payload FROM summary_nodes WHERE node_id=? AND project_key=?").get(job.nodeId, this.projectKey);
2362
+ if (!row?.payload) throw new Error("node not found");
2363
+ const node = parse(row.payload);
2364
+ if (node.projectKey !== this.projectKey || node.nodeId !== current.nodeId) throw new Error("node project does not match ledger project");
2365
+ for (const source of node.sources) {
2366
+ 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);
2367
+ if (!raw?.payload_json || hash2(parse(raw.payload_json)) !== source.payloadHash || raw.session_id !== source.sessionId) throw new Error("stale source");
2368
+ }
2369
+ const out = { ...node, state: "ready", text, modelHash: "emergency" };
2370
+ db.prepare("UPDATE summary_nodes SET payload=? WHERE node_id=? AND project_key=?").run(JSON.stringify(out), node.nodeId, this.projectKey);
2371
+ const { ownerId: _ownerId, leaseToken: _leaseToken, leaseUntil: _leaseUntil, ...base } = current;
2372
+ const done = { ...base, state: "completed", updatedAt: this.now() };
2373
+ 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);
2374
+ return out;
2375
+ });
2376
+ }
2377
+ fail(job, error) {
2378
+ return this.fenced(job, (old) => {
2379
+ const attempts = old.attempts + 1;
2380
+ const delay = Math.min(9e5, 3e4 * 2 ** (attempts - 1));
2381
+ const { ownerId: _ownerId, leaseToken: _leaseToken, leaseUntil: _leaseUntil, ...base } = old;
2382
+ return { ...base, attempts, error, state: attempts >= 3 ? "failed" : "pending", eligibleAt: this.now() + delay, nextRetryAt: this.now() + delay, updatedAt: this.now() };
2383
+ });
2384
+ }
2385
+ async run(job, model, input, signal = new AbortController().signal) {
2386
+ let sources = [];
2387
+ let claimed;
2388
+ const controller = new AbortController();
2389
+ let renew;
2390
+ const timer = setTimeout(() => controller.abort(), 6e4);
2391
+ const abort = () => controller.abort();
2392
+ signal.addEventListener("abort", abort, { once: true });
2393
+ try {
2394
+ claimed = this.claim(job.jobId, job.ownerId ?? this.ownerId);
2395
+ const node = this.getNode(claimed.nodeId);
2396
+ sources = node?.sources ?? [];
2397
+ renew = setInterval(() => {
2398
+ try {
2399
+ if (claimed) claimed = this.renew(claimed);
2400
+ } catch {
2401
+ }
2402
+ }, 1e4);
2403
+ 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 }))]);
2404
+ return this.complete(claimed, result);
2405
+ } catch (error) {
2406
+ if (!claimed) throw error;
2407
+ const fallback = emergencyReduce(input, this.maxOutputChars, sources);
2408
+ try {
2409
+ return this.completeEmergency(claimed, fallback);
2410
+ } catch {
2411
+ try {
2412
+ this.fail(claimed, String(error));
2413
+ } catch {
2414
+ }
2415
+ throw error;
2416
+ }
2417
+ } finally {
2418
+ clearTimeout(timer);
2419
+ if (renew) clearInterval(renew);
2420
+ signal.removeEventListener("abort", abort);
2421
+ }
2422
+ }
2423
+ day(ms) {
2424
+ return new Date(ms).toISOString().slice(0, 10);
2425
+ }
2426
+ usage(db, day, sessionId) {
2427
+ 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=?";
2428
+ const r = sessionId === void 0 ? db.prepare(q).get(this.projectKey, day) : db.prepare(q).get(this.projectKey, day, sessionId);
2429
+ return r;
2430
+ }
2431
+ account(db, sessionId, result) {
2432
+ const d = this.day(this.now());
2433
+ 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);
2434
+ }
2435
+ readJob(db, id) {
2436
+ const row = db.prepare("SELECT payload FROM maintenance_jobs WHERE job_id=? AND project_key=?").get(id, this.projectKey);
2437
+ if (!row) throw new Error("job not found");
2438
+ const job = parse(row.payload);
2439
+ if (job.projectKey !== this.projectKey) throw new Error("job project does not match ledger project");
2440
+ return job;
2441
+ }
2442
+ assertLease(current, expected) {
2443
+ if (current.ownerId !== expected.ownerId || current.leaseToken !== expected.leaseToken || current.state !== "running" || (current.leaseUntil ?? 0) < this.now()) throw new Error("lease fenced");
2444
+ }
2445
+ fenced(job, update) {
2446
+ return this.ledger.transaction((db) => {
2447
+ const old = this.readJob(db, job.jobId);
2448
+ this.assertLease(old, job);
2449
+ const out = update(old);
2450
+ 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);
2451
+ return out;
2452
+ });
2453
+ }
2454
+ };
2455
+
2456
+ // src/compaction/lcm-runtime.ts
2457
+ var LcmRuntime = class {
2458
+ ledger;
2459
+ maintenance;
2460
+ context;
2461
+ options;
2462
+ abort = new AbortController();
2463
+ writePending = Promise.resolve();
2464
+ maintenancePending = Promise.resolve();
2465
+ closed = false;
2466
+ dirty = false;
2467
+ degradedError;
2468
+ activeSources = /* @__PURE__ */ new Set();
2469
+ activeSessionId;
2470
+ constructor(context, options = {}) {
2471
+ this.context = context;
2472
+ this.options = options;
2473
+ const recorded = context.sessionManager.getRecordedCwd?.();
2474
+ const liveCwd = recorded || context.cwd;
2475
+ const project = canonicalProjectIdentity({ liveCwd });
2476
+ const ledgerOptions = options.rootDir === void 0 ? { project: { liveCwd: project.canonicalPath ?? liveCwd } } : { rootDir: options.rootDir, project: { liveCwd: project.canonicalPath ?? liveCwd } };
2477
+ this.ledger = new LcmLedger(ledgerOptions);
2478
+ this.maintenance = new LcmMaintenance(this.ledger, {
2479
+ ...options,
2480
+ ...options.lcmMaxInputChars === void 0 ? {} : { maxInputChars: options.lcmMaxInputChars },
2481
+ ...options.lcmMaxOutputChars === void 0 ? {} : { maxOutputChars: options.lcmMaxOutputChars }
2482
+ });
2483
+ }
2484
+ get projectKey() {
2485
+ return this.ledger.project.key;
2486
+ }
2487
+ get signal() {
2488
+ return this.abort.signal;
2489
+ }
2490
+ get status() {
2491
+ return this.degradedError === void 0 ? "healthy" : "degraded";
2492
+ }
2493
+ get error() {
2494
+ return this.degradedError;
2495
+ }
2496
+ markDirty() {
2497
+ this.dirty = true;
2498
+ }
2499
+ enqueueWrite(operation) {
2500
+ const run = this.writePending.then(operation);
2501
+ this.writePending = run.catch((error) => {
2502
+ this.degradedError = error;
2503
+ });
2504
+ return run;
2505
+ }
2506
+ refreshActiveSources(sessionId, entries) {
2507
+ const ids = new Set(entries.map((entry) => entry.id));
2508
+ const latest = /* @__PURE__ */ new Map();
2509
+ for (const entry of this.ledger.readRaw(this.projectKey, sessionId)) {
2510
+ if (!ids.has(entry.entryId)) continue;
2511
+ const previous = latest.get(entry.entryId);
2512
+ if (!previous || previous.revision < entry.revision) latest.set(entry.entryId, entry);
2513
+ }
2514
+ this.activeSources = new Set(
2515
+ [...latest.values()].map((entry) => `${entry.sessionId}:${entry.entryId}:${entry.revision}`)
2516
+ );
2517
+ this.activeSessionId = sessionId;
2518
+ }
2519
+ reconcileSelectedSession() {
2520
+ if (this.closed) return Promise.resolve();
2521
+ return this.enqueueWrite(() => {
2522
+ const getSessionFile = this.context.sessionManager.getSessionFile;
2523
+ if (typeof getSessionFile !== "function") return;
2524
+ const sessionFile = getSessionFile.call(this.context.sessionManager);
2525
+ if (!sessionFile) return;
2526
+ const recorded = this.context.sessionManager.getRecordedCwd?.();
2527
+ const cwd = recorded || this.context.cwd;
2528
+ const result = reconcileSession({
2529
+ agentDir: this.options.rootDir ?? process.env.PI_CODING_AGENT_DIR ?? `${process.env.HOME ?? "."}/.omp/agent`,
2530
+ ledger: this.ledger,
2531
+ files: [sessionFile],
2532
+ liveCwd: cwd,
2533
+ projectCwd: cwd,
2534
+ apply: true
2535
+ });
2536
+ if (result.exitCode !== 0) throw new Error(`LCM session reconciliation failed with exit code ${result.exitCode}`);
2537
+ const sessionId = this.context.sessionManager.getSessionId();
2538
+ this.refreshActiveSources(sessionId, this.context.sessionManager.getBranch());
2539
+ });
2540
+ }
2541
+ readback() {
2542
+ if (this.closed) return Promise.resolve();
2543
+ return this.enqueueWrite(() => {
2544
+ if (this.closed) return;
2545
+ const entries = this.context.sessionManager.getBranch();
2546
+ const sessionId = this.context.sessionManager.getSessionId();
2547
+ const branch = this.context.sessionManager.getLeafId();
2548
+ const recorded = this.context.sessionManager.getRecordedCwd?.();
2549
+ const active = /* @__PURE__ */ new Set();
2550
+ for (const entry of entries) {
2551
+ const payloadJson = canonicalLcmPayload(entry);
2552
+ const message = entry.type === "message" ? entry.message : void 0;
2553
+ const stored = this.ledger.appendRaw({
2554
+ projectKey: this.projectKey,
2555
+ sessionId,
2556
+ entryId: entry.id,
2557
+ role: message?.role ?? entry.type,
2558
+ content: message ? JSON.stringify(message.content ?? "") : payloadJson,
2559
+ payloadJson,
2560
+ parentEntryId: entry.parentId ?? null,
2561
+ branch,
2562
+ ...recorded || this.context.cwd ? { recordedCwd: recorded || this.context.cwd } : {}
2563
+ });
2564
+ active.add(`${sessionId}:${stored.entryId}:${stored.revision}`);
2565
+ }
2566
+ this.activeSources = active;
2567
+ this.activeSessionId = sessionId;
2568
+ this.dirty = false;
2569
+ this.degradedError = void 0;
2570
+ });
2571
+ }
2572
+ async syncAndSchedule() {
2573
+ if (this.closed) return;
2574
+ await this.readback();
2575
+ this.scheduleMaintenance();
2576
+ }
2577
+ maintain() {
2578
+ return this.syncAndSchedule();
2579
+ }
2580
+ scheduleMaintenance() {
2581
+ if (this.closed) return;
2582
+ const run = this.maintenancePending.then(() => this.runMaintenance());
2583
+ this.maintenancePending = run.catch((error) => {
2584
+ this.degradedError = error;
2585
+ });
2586
+ }
2587
+ async runMaintenance() {
2588
+ if (this.closed || !this.activeSessionId || this.activeSources.size === 0) return;
2589
+ let model;
2590
+ try {
2591
+ model = new LcmModelAdapter(this.context, this.options.summaryModel, true, {
2592
+ ...this.options.lcmMaxInputChars === void 0 ? {} : { maxInputChars: this.options.lcmMaxInputChars },
2593
+ ...this.options.lcmMaxOutputTokens === void 0 ? {} : { maxOutputTokens: this.options.lcmMaxOutputTokens },
2594
+ ...this.options.lcmMaxOutputChars === void 0 ? {} : { maxOutputChars: this.options.lcmMaxOutputChars }
2595
+ });
2596
+ } catch (error) {
2597
+ this.degradedError = error;
2598
+ model = { modelHash: "unavailable", generate: async () => {
2599
+ throw error instanceof Error ? error : new Error(String(error));
2600
+ } };
2601
+ }
2602
+ const budget = Math.max(1, this.options.maxCondenseChildren ?? 4);
2603
+ const sessionId = this.activeSessionId;
2604
+ const raw = this.ledger.readRaw(this.projectKey, sessionId).filter(
2605
+ (entry) => this.activeSources.has(`${entry.sessionId}:${entry.entryId}:${entry.revision}`)
2606
+ );
2607
+ const inputFor = (node) => {
2608
+ if (node.kind === "condensed") return node.children.map((id) => this.maintenance.getNode(id)?.text ?? "").filter(Boolean).join("\n\n");
2609
+ return node.sources.map((source) => {
2610
+ const entry = this.ledger.readRawEntry(this.projectKey, source.sessionId, source.entryId, source.revision);
2611
+ if (!entry || entry.payloadHash !== source.payloadHash) throw new Error("stale source");
2612
+ return entry.payloadJson;
2613
+ }).join("\n");
2614
+ };
2615
+ const runJob = async (job, node) => {
2616
+ try {
2617
+ await this.maintenance.run(job, model, inputFor(node), this.signal);
2618
+ } catch {
2619
+ }
2620
+ };
2621
+ for (let pass = 0; pass < budget && !this.closed; pass += 1) {
2622
+ const leafCandidate = this.maintenance.createLeaf(this.maintenance.selectLeaf(raw, this.context.sessionManager.getLeafId()));
2623
+ const leaf = leafCandidate ? this.maintenance.getNode(leafCandidate.nodeId) : void 0;
2624
+ if (leaf?.state === "pending") {
2625
+ const job = this.maintenance.listJobs().find((item) => item.nodeId === leaf.nodeId);
2626
+ if (job) await runJob(job, leaf);
2627
+ }
2628
+ const branch = this.context.sessionManager.getLeafId();
2629
+ const pending = this.maintenance.listJobs().filter((job) => job.state === "pending").flatMap((job) => {
2630
+ const node = this.maintenance.getNode(job.nodeId);
2631
+ 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 [];
2632
+ return [{ job, node }];
2633
+ }).slice(0, budget);
2634
+ for (const item of pending) await runJob(item.job, item.node);
2635
+ const children = this.maintenance.selectCondensation(sessionId, this.activeSources, this.context.sessionManager.getLeafId());
2636
+ if (children.length >= budget) {
2637
+ const node = this.maintenance.createCondensed(children);
2638
+ if (node) {
2639
+ const job = this.maintenance.listJobs().find((item) => item.nodeId === node.nodeId);
2640
+ if (job) await runJob(job, node);
2641
+ }
2642
+ }
2643
+ if (!leaf && pending.length === 0 && children.length < budget) break;
2644
+ }
2645
+ }
2646
+ compact(input) {
2647
+ const persistedEntries = /* @__PURE__ */ new Map();
2648
+ for (const entry of input.branchEntries) {
2649
+ const message = entry.type === "message" ? entry.message : void 0;
2650
+ const payloadJson = canonicalLcmPayload(entry);
2651
+ const stored = this.ledger.appendRaw({
2652
+ projectKey: this.projectKey,
2653
+ sessionId: input.sessionId,
2654
+ entryId: entry.id,
2655
+ role: message?.role ?? entry.type,
2656
+ content: message ? JSON.stringify(message.content ?? "") : payloadJson,
2657
+ payloadJson,
2658
+ parentEntryId: entry.parentId ?? null,
2659
+ branch: input.branch
2660
+ });
2661
+ persistedEntries.set(`${entry.id}:${stored.payloadHash}`, stored);
2662
+ }
2663
+ const firstKeptIndex = input.firstKeptEntryId ? input.branchEntries.findIndex((entry) => entry.id === input.firstKeptEntryId) : input.branchEntries.length;
2664
+ const sourceEntries = input.branchEntries.slice(0, firstKeptIndex < 0 ? input.branchEntries.length : firstKeptIndex);
2665
+ const selectedStored = sourceEntries.map((entry) => {
2666
+ const payloadHash = hashLcmPayload(JSON.parse(canonicalLcmPayload(entry)));
2667
+ const stored = persistedEntries.get(`${entry.id}:${payloadHash}`);
2668
+ if (!stored) throw new Error("compaction source was not persisted");
2669
+ return stored;
2670
+ });
2671
+ const activeSources = new Set(selectedStored.map((entry) => `${entry.sessionId}:${entry.entryId}:${entry.revision}`));
2672
+ const frontier = this.maintenance.getFrontier(input.sessionId, input.branch, activeSources);
2673
+ const coveredSources = new Set(frontier.flatMap((node) => node.sources.map((source) => `${source.sessionId}:${source.entryId}:${source.revision}`)));
2674
+ const summary = frontier.map((node) => node.text ?? "").filter(Boolean).join("\n\n");
2675
+ 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 };
2676
+ if (selectedStored.length === 0) throw new Error("LCM emergency fallback has no persisted sources");
2677
+ const payloads = sourceEntries.map((entry) => canonicalLcmPayload(entry)).join("\n");
2678
+ const sources = selectedStored.map((stored) => ({ sessionId: stored.sessionId, entryId: stored.entryId, revision: stored.revision, payloadHash: stored.payloadHash }));
2679
+ const fallback = emergencyReduce(payloads, this.options.lcmMaxOutputChars ?? 4096, sources);
2680
+ const leaf = this.maintenance.createLeaf(selectedStored);
2681
+ if (!leaf) throw new Error("LCM emergency fallback node was not created");
2682
+ const persisted = this.maintenance.getNode(leaf.nodeId);
2683
+ if (persisted?.state === "ready") {
2684
+ if (persisted.branch !== input.branch || JSON.stringify(persisted.sources) !== JSON.stringify(sources)) throw new Error("LCM emergency fallback provenance mismatch");
2685
+ if (!persisted.text) throw new Error("LCM emergency fallback text is missing");
2686
+ return { summary: persisted.text, firstKeptEntryId: input.firstKeptEntryId, tokensBefore: input.tokensBefore, source: "emergency", branch: input.branch };
2687
+ }
2688
+ const job = this.maintenance.listJobs().find((item) => item.nodeId === leaf.nodeId);
2689
+ if (!job) throw new Error("LCM emergency fallback job was not created");
2690
+ const completed = this.maintenance.completeEmergency(this.maintenance.claimEmergency(job.jobId), fallback);
2691
+ return { summary: completed.text ?? fallback, firstKeptEntryId: input.firstKeptEntryId, tokensBefore: input.tokensBefore, source: "emergency", branch: input.branch };
2692
+ }
2693
+ memoryContext() {
2694
+ return {
2695
+ ledger: {
2696
+ projectKey: this.projectKey,
2697
+ readRaw: (sessionId) => this.ledger.readRaw(this.projectKey, sessionId),
2698
+ readRawPage: (sessionId, offset, limit) => this.ledger.readRawPage(this.projectKey, sessionId, offset, limit),
2699
+ readRawEntry: (sessionId, entryId, revision) => this.ledger.readRawEntry(this.projectKey, sessionId, entryId, revision)
2700
+ },
2701
+ ...this.activeSessionId === void 0 ? {} : { currentSessionId: this.activeSessionId },
2702
+ summaries: {
2703
+ 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 })) })),
2704
+ getNode: (nodeId) => {
2705
+ const node = this.maintenance.getNode(nodeId);
2706
+ return node ? { ...node, text: node.text ?? "", sources: node.sources.map((source) => ({ entryId: source.entryId, revision: source.revision, contentHash: source.payloadHash })) } : void 0;
2707
+ }
2708
+ },
2709
+ branchForSession: (sessionId) => sessionId === this.activeSessionId ? { branch: this.context.sessionManager.getLeafId(), activeSourceKeys: [...this.activeSources], ready: this.status === "healthy" } : void 0
2710
+ };
2711
+ }
2712
+ frontier(sessionId, branch) {
2713
+ return this.maintenance.getFrontier(sessionId, branch, sessionId === this.activeSessionId ? this.activeSources : void 0);
2714
+ }
2715
+ raw(sessionId) {
2716
+ return this.ledger.readRaw(this.projectKey, sessionId);
2717
+ }
2718
+ async shutdown() {
2719
+ if (this.closed) return;
2720
+ this.closed = true;
2721
+ this.abort.abort();
2722
+ await this.writePending.catch(() => void 0);
2723
+ await this.maintenancePending.catch(() => void 0);
2724
+ this.ledger.close();
2725
+ }
2726
+ };
2727
+
2728
+ // src/core/tool-ownership.ts
2729
+ import path4 from "node:path";
1848
2730
 
1849
2731
  // src/audit/details.ts
1850
2732
  var FABRIC_EXECUTION_DETAILS_MAX_BYTES = 512 * 1024;
@@ -1946,7 +2828,7 @@ var readFabricExecutionRenderDetails = (value) => {
1946
2828
  var FABRIC_TOOL_NAME = "fabric_exec";
1947
2829
  var TOP_LEVEL_SCHEMA_REF_PREFIX = "schema.top_level_tool.";
1948
2830
  var ownsFabricToolSource = (tools, extensionEntryPath) => tools.some(
1949
- (tool) => tool.name === FABRIC_TOOL_NAME && path3.resolve(tool.sourceInfo.path) === path3.resolve(extensionEntryPath)
2831
+ (tool) => tool.name === FABRIC_TOOL_NAME && path4.resolve(tool.sourceInfo.path) === path4.resolve(extensionEntryPath)
1950
2832
  );
1951
2833
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1952
2834
  var finalFabricDetailsFailed = (details) => {
@@ -2109,7 +2991,7 @@ ${sections.join("\n")}` : "";
2109
2991
 
2110
2992
  // src/core/system-guidance.ts
2111
2993
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
2112
- import * as path4 from "node:path";
2994
+ import * as path5 from "node:path";
2113
2995
  var fabricExecutionKernelGuidance = (fullCodeMode) => [
2114
2996
  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.",
2115
2997
  // Files the model has not opened (images in particular) must be read before
@@ -2121,10 +3003,10 @@ var defaultFabricExecutionGuidance = (fullCodeMode) => fullCodeMode ? "Examples
2121
3003
  var manifestNameCache = /* @__PURE__ */ new Map();
2122
3004
  var packageNameFromManifest = (startPath) => {
2123
3005
  if (!startPath) return void 0;
2124
- let directory = path4.dirname(path4.resolve(startPath));
3006
+ let directory = path5.dirname(path5.resolve(startPath));
2125
3007
  while (true) {
2126
3008
  if (manifestNameCache.has(directory)) return manifestNameCache.get(directory);
2127
- const manifestPath = path4.join(directory, "package.json");
3009
+ const manifestPath = path5.join(directory, "package.json");
2128
3010
  let name;
2129
3011
  if (existsSync2(manifestPath)) {
2130
3012
  try {
@@ -2135,7 +3017,7 @@ var packageNameFromManifest = (startPath) => {
2135
3017
  }
2136
3018
  manifestNameCache.set(directory, name);
2137
3019
  if (name) return name;
2138
- const parent = path4.dirname(directory);
3020
+ const parent = path5.dirname(directory);
2139
3021
  if (parent === directory) return void 0;
2140
3022
  directory = parent;
2141
3023
  }
@@ -2403,8 +3285,8 @@ var formatFailureProgress = (trace) => {
2403
3285
  );
2404
3286
  if (completed.length === 0) return void 0;
2405
3287
  const summaries = completed.slice(0, MAX_COMPLETED_CALLS).map((operation) => {
2406
- const path11 = operation.args.path;
2407
- return typeof path11 === "string" ? `${operation.ref}(${compactPath(path11)})` : operation.ref;
3288
+ const path12 = operation.args.path;
3289
+ return typeof path12 === "string" ? `${operation.ref}(${compactPath(path12)})` : operation.ref;
2408
3290
  });
2409
3291
  const omitted = completed.length - summaries.length;
2410
3292
  return [
@@ -2713,12 +3595,12 @@ var PartialResultObserver = class {
2713
3595
  // src/output-budget.ts
2714
3596
  import { mkdtemp, writeFile } from "node:fs/promises";
2715
3597
  import { tmpdir } from "node:os";
2716
- import path5 from "node:path";
3598
+ import path6 from "node:path";
2717
3599
  var MAX_FAILURE_MODEL_OUTPUT_CHARS = 2e4;
2718
3600
  var modelOutputBudget = (configuredMaxChars, success) => success ? configuredMaxChars : Math.min(configuredMaxChars, MAX_FAILURE_MODEL_OUTPUT_CHARS);
2719
3601
  var writeOutputArtifact = async (content) => {
2720
- const directory = await mkdtemp(path5.join(tmpdir(), "omp-fabric-output-"));
2721
- const artifactPath = path5.join(directory, "output.txt");
3602
+ const directory = await mkdtemp(path6.join(tmpdir(), "omp-fabric-output-"));
3603
+ const artifactPath = path6.join(directory, "output.txt");
2722
3604
  await writeFile(artifactPath, content, { encoding: "utf8", mode: 384 });
2723
3605
  return artifactPath;
2724
3606
  };
@@ -2754,20 +3636,20 @@ var normalizeFabricExecParams = (input) => {
2754
3636
  if (typeof prepared !== "object" || prepared === null || Array.isArray(prepared)) {
2755
3637
  throw new Error("fabric_exec arguments must be an object");
2756
3638
  }
2757
- const record = prepared;
2758
- const code = record.code;
3639
+ const record2 = prepared;
3640
+ const code = record2.code;
2759
3641
  if (typeof code !== "string" && !(Array.isArray(code) && code.every((line) => typeof line === "string"))) {
2760
3642
  throw new Error("fabric_exec code must be a string or string array");
2761
3643
  }
2762
- const resultFormat = RESULT_FORMATS.includes(record.resultFormat) ? record.resultFormat : void 0;
3644
+ const resultFormat = RESULT_FORMATS.includes(record2.resultFormat) ? record2.resultFormat : void 0;
2763
3645
  return {
2764
3646
  code,
2765
- ...record.payloads !== void 0 ? { payloads: record.payloads } : {},
2766
- ...record.display !== void 0 ? { display: record.display } : {},
3647
+ ...record2.payloads !== void 0 ? { payloads: record2.payloads } : {},
3648
+ ...record2.display !== void 0 ? { display: record2.display } : {},
2767
3649
  ...resultFormat !== void 0 ? { resultFormat } : {},
2768
- ...typeof record.tokenBudget === "number" ? { tokenBudget: record.tokenBudget } : {},
2769
- ...typeof record.agentBudget === "number" ? { agentBudget: record.agentBudget } : {},
2770
- ...typeof record.timeoutMs === "number" ? { timeoutMs: record.timeoutMs } : {}
3650
+ ...typeof record2.tokenBudget === "number" ? { tokenBudget: record2.tokenBudget } : {},
3651
+ ...typeof record2.agentBudget === "number" ? { agentBudget: record2.agentBudget } : {},
3652
+ ...typeof record2.timeoutMs === "number" ? { timeoutMs: record2.timeoutMs } : {}
2771
3653
  };
2772
3654
  };
2773
3655
  var toolDisplayMode = (state) => state.bootstrapped ? state.config.ui.toolDisplay : "full";
@@ -3502,8 +4384,8 @@ ${recoveryHint}` : ""}`,
3502
4384
  );
3503
4385
 
3504
4386
  // src/fabric-state.ts
3505
- import fs2 from "node:fs";
3506
- import path6 from "node:path";
4387
+ import fs3 from "node:fs";
4388
+ import path7 from "node:path";
3507
4389
  var FabricState = class {
3508
4390
  constructor(omp, capturedTools, options = {}) {
3509
4391
  this.omp = omp;
@@ -3642,15 +4524,15 @@ var FabricState = class {
3642
4524
  const sessionId = context.sessionManager.getSessionId();
3643
4525
  if (resolveFabricIdentity(sessionId).identity.kind !== "main") return false;
3644
4526
  const projectRoot = process.env.OMP_FABRIC_PROJECT_ROOT ?? context.cwd;
3645
- 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"));
4527
+ 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"));
3646
4528
  const fabricSessionId = process.env.OMP_FABRIC_SESSION_ID?.trim() || sessionId;
3647
4529
  const actorRoots = [
3648
- path6.join(meshRoot, "actors"),
3649
- path6.join(meshRoot, "actors", fabricSessionId)
4530
+ path7.join(meshRoot, "actors"),
4531
+ path7.join(meshRoot, "actors", fabricSessionId)
3650
4532
  ];
3651
4533
  return actorRoots.some((actorRoot) => {
3652
4534
  try {
3653
- const registry = JSON.parse(fs2.readFileSync(path6.join(actorRoot, "actors.json"), "utf8"));
4535
+ const registry = JSON.parse(fs3.readFileSync(path7.join(actorRoot, "actors.json"), "utf8"));
3654
4536
  if (typeof registry !== "object" || registry === null || Array.isArray(registry)) return false;
3655
4537
  const actors = registry.actors;
3656
4538
  return Array.isArray(actors) && actors.some((value) => {
@@ -3865,7 +4747,8 @@ var FabricState = class {
3865
4747
  prewalk: this.prewalk,
3866
4748
  prewalkDrift: this.prewalkDrift,
3867
4749
  sessionApprovals: this.sessionApprovals,
3868
- ...this.#options.paths ? { paths: this.#options.paths } : {}
4750
+ ...this.#options.paths ? { paths: this.#options.paths } : {},
4751
+ ...this.#options.lcmContext ? { lcmContext: this.#options.lcmContext } : {}
3869
4752
  }
3870
4753
  );
3871
4754
  }
@@ -4052,7 +4935,7 @@ var classifyToolResult = (input) => {
4052
4935
 
4053
4936
  // src/host-compatibility.ts
4054
4937
  import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
4055
- import path7 from "node:path";
4938
+ import path8 from "node:path";
4056
4939
  var MINIMUM_OMP_HOST_VERSION = "18.1.10";
4057
4940
  var OMP_HOST_PACKAGE_NAMES = {
4058
4941
  "@oh-my-pi/pi-coding-agent": true
@@ -4082,12 +4965,12 @@ var detectOmpHostVersion = (cliPath = process.argv[1]) => {
4082
4965
  if (!cliPath) return void 0;
4083
4966
  let directory;
4084
4967
  try {
4085
- directory = path7.dirname(realpathSync2(cliPath));
4968
+ directory = path8.dirname(realpathSync2(cliPath));
4086
4969
  } catch {
4087
4970
  return void 0;
4088
4971
  }
4089
4972
  while (true) {
4090
- const manifestPath = path7.join(directory, "package.json");
4973
+ const manifestPath = path8.join(directory, "package.json");
4091
4974
  if (existsSync3(manifestPath)) {
4092
4975
  try {
4093
4976
  const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
@@ -4097,7 +4980,7 @@ var detectOmpHostVersion = (cliPath = process.argv[1]) => {
4097
4980
  } catch {
4098
4981
  }
4099
4982
  }
4100
- const parent = path7.dirname(directory);
4983
+ const parent = path8.dirname(directory);
4101
4984
  if (parent === directory) return void 0;
4102
4985
  directory = parent;
4103
4986
  }
@@ -4110,8 +4993,8 @@ var ompHostCompatibilityWarning = (version = detectOmpHostVersion()) => {
4110
4993
  };
4111
4994
 
4112
4995
  // src/update/check.ts
4113
- import fs3 from "node:fs";
4114
- import path8 from "node:path";
4996
+ import fs4 from "node:fs";
4997
+ import path9 from "node:path";
4115
4998
  import { fileURLToPath } from "node:url";
4116
4999
  var PACKAGE_NAME = "omp-fabric";
4117
5000
  var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
@@ -4119,27 +5002,27 @@ var UPGRADE_COMMAND = `omp plugin install ${PACKAGE_NAME}`;
4119
5002
  var REQUEST_TIMEOUT_MS = 4e3;
4120
5003
  var UPDATE_STATUS_KEY = "fabric-update";
4121
5004
  var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
4122
- var updateCheckCachePath = (agentDir) => path8.join(agentDir, "fabric", "update-check.json");
5005
+ var updateCheckCachePath = (agentDir) => path9.join(agentDir, "fabric", "update-check.json");
4123
5006
  var installedVersionFromManifest = () => {
4124
- let directory = path8.dirname(fileURLToPath(import.meta.url));
5007
+ let directory = path9.dirname(fileURLToPath(import.meta.url));
4125
5008
  while (true) {
4126
5009
  try {
4127
5010
  const manifest = JSON.parse(
4128
- fs3.readFileSync(path8.join(directory, "package.json"), "utf8")
5011
+ fs4.readFileSync(path9.join(directory, "package.json"), "utf8")
4129
5012
  );
4130
5013
  if (manifest.name === PACKAGE_NAME && typeof manifest.version === "string") {
4131
5014
  return manifest.version;
4132
5015
  }
4133
5016
  } catch {
4134
5017
  }
4135
- const parent = path8.dirname(directory);
5018
+ const parent = path9.dirname(directory);
4136
5019
  if (parent === directory) return void 0;
4137
5020
  directory = parent;
4138
5021
  }
4139
5022
  };
4140
5023
  var readCache = (cachePath) => {
4141
5024
  try {
4142
- const parsed = JSON.parse(fs3.readFileSync(cachePath, "utf8"));
5025
+ const parsed = JSON.parse(fs4.readFileSync(cachePath, "utf8"));
4143
5026
  if (typeof parsed.checkedAt !== "number" || !Number.isFinite(parsed.checkedAt)) return void 0;
4144
5027
  if (typeof parsed.latest !== "string" || parsed.latest.length === 0) return void 0;
4145
5028
  return {
@@ -4186,32 +5069,32 @@ var checkFabricUpdate = async (options) => {
4186
5069
  const cached = readCache(cachePath);
4187
5070
  const age = cached === void 0 ? void 0 : now - cached.checkedAt;
4188
5071
  const fresh = cached !== void 0 && age !== void 0 && age >= 0 && age < UPDATE_CHECK_TTL_MS;
4189
- let record;
5072
+ let record2;
4190
5073
  if (fresh && cached !== void 0) {
4191
- record = cached;
5074
+ record2 = cached;
4192
5075
  } else {
4193
5076
  const latest = await fetchLatestVersion(
4194
5077
  options.fetchImpl ?? globalThis.fetch,
4195
5078
  options.timeoutMs ?? REQUEST_TIMEOUT_MS
4196
5079
  );
4197
5080
  if (latest === void 0) return void 0;
4198
- record = {
5081
+ record2 = {
4199
5082
  format: 1,
4200
5083
  checkedAt: now,
4201
5084
  latest,
4202
5085
  ...cached?.notified === void 0 ? {} : { notified: cached.notified }
4203
5086
  };
4204
5087
  }
4205
- const behind = compareVersions(record.latest, installed) === 1;
4206
- const announce = behind && record.notified !== record.latest;
5088
+ const behind = compareVersions(record2.latest, installed) === 1;
5089
+ const announce = behind && record2.notified !== record2.latest;
4207
5090
  if (!fresh || announce) {
4208
- writeCache(cachePath, announce ? { ...record, notified: record.latest } : record);
5091
+ writeCache(cachePath, announce ? { ...record2, notified: record2.latest } : record2);
4209
5092
  }
4210
5093
  if (!announce) return void 0;
4211
5094
  return {
4212
5095
  installed,
4213
- latest: record.latest,
4214
- message: `${PACKAGE_NAME} ${installed} \u2192 ${record.latest} \xB7 ${UPGRADE_COMMAND}`
5096
+ latest: record2.latest,
5097
+ message: `${PACKAGE_NAME} ${installed} \u2192 ${record2.latest} \xB7 ${UPGRADE_COMMAND}`
4215
5098
  };
4216
5099
  } catch {
4217
5100
  return void 0;
@@ -4240,7 +5123,7 @@ var scheduleFabricUpdateCheck = async (context, options) => {
4240
5123
  };
4241
5124
 
4242
5125
  // src/ui/controller.ts
4243
- import path9 from "node:path";
5126
+ import path10 from "node:path";
4244
5127
 
4245
5128
  // src/ui/snapshot.ts
4246
5129
  var MAX_UI_AGENTS = 240;
@@ -4297,57 +5180,57 @@ var createDashboardSnapshot = (state, events, context, activityRuns) => {
4297
5180
  if (leftLaunch !== rightLaunch) return leftLaunch ? -1 : 1;
4298
5181
  return left.call.startedAt - right.call.startedAt;
4299
5182
  });
4300
- const agentFromRecord = (record, nestingDepth, parentId, parent) => {
5183
+ const agentFromRecord = (record2, nestingDepth, parentId, parent) => {
4301
5184
  const linked = parentId ? void 0 : agentLinks.find(
4302
- ({ call }) => call.entityId && (record.id.startsWith(call.entityId) || call.entityId.startsWith(record.id))
5185
+ ({ call }) => call.entityId && (record2.id.startsWith(call.entityId) || call.entityId.startsWith(record2.id))
4303
5186
  );
4304
5187
  const base = {
4305
- id: record.id,
4306
- name: record.name,
4307
- status: record.status,
4308
- runner: record.runner,
4309
- transport: record.transport,
4310
- cwd: record.cwd,
4311
- ...!isRunRecord(record) && linked ? { startedAt: linked.call.startedAt } : {},
4312
- ...record.model ? { model: record.model } : {},
4313
- ...record.thinking ? { thinking: record.thinking } : {},
4314
- ...record.attachCommand ? { attachCommand: record.attachCommand } : {},
4315
- ...isRunRecord(record) && record.logFile ? { logFile: record.logFile } : {},
4316
- ...record.branch ? { branch: record.branch } : {},
4317
- ...record.worktree ? { worktree: record.worktree } : {},
4318
- ...record.actorId ? { actorId: record.actorId } : {},
4319
- ...record.actorName ? { actorName: record.actorName } : {},
5188
+ id: record2.id,
5189
+ name: record2.name,
5190
+ status: record2.status,
5191
+ runner: record2.runner,
5192
+ transport: record2.transport,
5193
+ cwd: record2.cwd,
5194
+ ...!isRunRecord(record2) && linked ? { startedAt: linked.call.startedAt } : {},
5195
+ ...record2.model ? { model: record2.model } : {},
5196
+ ...record2.thinking ? { thinking: record2.thinking } : {},
5197
+ ...record2.attachCommand ? { attachCommand: record2.attachCommand } : {},
5198
+ ...isRunRecord(record2) && record2.logFile ? { logFile: record2.logFile } : {},
5199
+ ...record2.branch ? { branch: record2.branch } : {},
5200
+ ...record2.worktree ? { worktree: record2.worktree } : {},
5201
+ ...record2.actorId ? { actorId: record2.actorId } : {},
5202
+ ...record2.actorName ? { actorName: record2.actorName } : {},
4320
5203
  ...parentId ? { parentId } : {},
4321
5204
  ...nestingDepth > 0 ? { nestingDepth } : {},
4322
5205
  ...linked ? { runId: linked.runId } : parent?.runId ? { runId: parent.runId } : {},
4323
5206
  ...linked?.call.phaseId ? { phaseId: linked.call.phaseId } : parent?.phaseId ? { phaseId: parent.phaseId } : {}
4324
5207
  };
4325
- if (!isRunRecord(record)) return base;
5208
+ if (!isRunRecord(record2)) return base;
4326
5209
  return {
4327
5210
  ...base,
4328
- task: record.task,
4329
- startedAt: record.startedAt,
4330
- updatedAt: record.updatedAt,
4331
- ...record.finishedAt !== void 0 ? { finishedAt: record.finishedAt } : {},
4332
- ...record.currentTool ? { currentTool: record.currentTool } : {},
4333
- turns: record.turns,
4334
- toolCalls: record.toolCalls,
4335
- usage: { ...record.usage },
4336
- ...record.text ? { text: record.text } : {},
4337
- ...record.value !== void 0 ? { value: structuredClone(record.value) } : {},
4338
- ...record.error ? { error: record.error } : {}
5211
+ task: record2.task,
5212
+ startedAt: record2.startedAt,
5213
+ updatedAt: record2.updatedAt,
5214
+ ...record2.finishedAt !== void 0 ? { finishedAt: record2.finishedAt } : {},
5215
+ ...record2.currentTool ? { currentTool: record2.currentTool } : {},
5216
+ turns: record2.turns,
5217
+ toolCalls: record2.toolCalls,
5218
+ usage: { ...record2.usage },
5219
+ ...record2.text ? { text: record2.text } : {},
5220
+ ...record2.value !== void 0 ? { value: structuredClone(record2.value) } : {},
5221
+ ...record2.error ? { error: record2.error } : {}
4339
5222
  };
4340
5223
  };
4341
5224
  const allAgents = [];
4342
- const appendAgent = (record, nestingDepth, parentId, parent) => {
4343
- const agent = agentFromRecord(record, nestingDepth, parentId, parent);
5225
+ const appendAgent = (record2, nestingDepth, parentId, parent) => {
5226
+ const agent = agentFromRecord(record2, nestingDepth, parentId, parent);
4344
5227
  allAgents.push(agent);
4345
- if (!isRunRecord(record)) return;
4346
- for (const nested of record.nestedAgents ?? []) {
4347
- appendAgent(nested, nestingDepth + 1, record.id, agent);
5228
+ if (!isRunRecord(record2)) return;
5229
+ for (const nested of record2.nestedAgents ?? []) {
5230
+ appendAgent(nested, nestingDepth + 1, record2.id, agent);
4348
5231
  }
4349
5232
  };
4350
- for (const record of agentRecords) appendAgent(record, 0);
5233
+ for (const record2 of agentRecords) appendAgent(record2, 0);
4351
5234
  const participants = typeof state.participantInfos === "function" ? state.participantInfos({ scope: "project" }) : [];
4352
5235
  const participantById = new Map(participants.map((participant) => [participant.id, participant]));
4353
5236
  const actors = state.actors.list().map((actor) => {
@@ -5097,7 +5980,7 @@ var FabricUiController = class {
5097
5980
  logFile: actor.worker.logFile
5098
5981
  };
5099
5982
  }
5100
- const retained = actor.lastRunId && actor.logDir ? path9.join(actor.logDir, actor.lastRunId, "events.jsonl") : void 0;
5983
+ const retained = actor.lastRunId && actor.logDir ? path10.join(actor.logDir, actor.lastRunId, "events.jsonl") : void 0;
5101
5984
  if (retained) return { id: actor.id, status: actor.status, logFile: retained };
5102
5985
  if (actor.sessionFile) {
5103
5986
  return { id: actor.id, status: actor.status, logFile: actor.sessionFile };
@@ -5256,15 +6139,15 @@ var REFRESH_CARDS_PER_TICK = 3;
5256
6139
 
5257
6140
  // src/index.ts
5258
6141
  import { existsSync as existsSync4 } from "node:fs";
5259
- import path10 from "node:path";
6142
+ import path11 from "node:path";
5260
6143
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5261
- var FABRIC_EXTENSION_ENTRY_PATH = path10.resolve(fileURLToPath2(import.meta.url));
5262
- var FABRIC_ENTRY_DIR = path10.dirname(FABRIC_EXTENSION_ENTRY_PATH);
6144
+ var FABRIC_EXTENSION_ENTRY_PATH = path11.resolve(fileURLToPath2(import.meta.url));
6145
+ var FABRIC_ENTRY_DIR = path11.dirname(FABRIC_EXTENSION_ENTRY_PATH);
5263
6146
  var FABRIC_RUNTIME_PATHS = {
5264
6147
  extension: FABRIC_EXTENSION_ENTRY_PATH,
5265
- worker: path10.join(FABRIC_ENTRY_DIR, "worker.js"),
5266
- residentHost: path10.join(FABRIC_ENTRY_DIR, "residency", "launcher.js"),
5267
- skills: path10.resolve(FABRIC_ENTRY_DIR, "..", "skills")
6148
+ worker: path11.join(FABRIC_ENTRY_DIR, "worker.js"),
6149
+ residentHost: path11.join(FABRIC_ENTRY_DIR, "residency", "launcher.js"),
6150
+ skills: path11.resolve(FABRIC_ENTRY_DIR, "..", "skills")
5268
6151
  };
5269
6152
  var FABRIC_SKILLS_DIR = FABRIC_RUNTIME_PATHS.skills;
5270
6153
  var componentRegistrationFrom = (value) => {
@@ -5297,13 +6180,19 @@ async function ompFabric(omp) {
5297
6180
  );
5298
6181
  const capturedTools = new CapturedToolCatalog();
5299
6182
  const proxyContract = new ProxyContractLedger();
5300
- const state = new FabricState(omp, capturedTools, { paths: FABRIC_RUNTIME_PATHS });
6183
+ const state = new FabricState(omp, capturedTools, {
6184
+ paths: FABRIC_RUNTIME_PATHS,
6185
+ lcmContext: () => lcmRuntime?.memoryContext()
6186
+ });
5301
6187
  const directToolApproval = new FabricDirectToolApproval(
5302
6188
  omp,
5303
6189
  () => state.config,
5304
6190
  state.sessionApprovals
5305
6191
  );
5306
6192
  const pendingHandoffs = /* @__PURE__ */ new Map();
6193
+ let lcmRuntime;
6194
+ let lcmCwd;
6195
+ let lcmProjectKey;
5307
6196
  const toolOwnership = new FabricToolOwnership(omp);
5308
6197
  const fabricUi = new FabricUiController(state, codePreviewSettings, {
5309
6198
  getToolDefinition: (name) => name === "fabric_exec" ? fabricTool : capturedTools.get(name)?.definition,
@@ -5568,6 +6457,27 @@ async function ompFabric(omp) {
5568
6457
  }
5569
6458
  }
5570
6459
  await state.bootstrap(context);
6460
+ const recordedCwd = context.sessionManager.getRecordedCwd?.() || context.cwd;
6461
+ const projectKey = canonicalProjectIdentity({ liveCwd: recordedCwd }).key;
6462
+ if (lcmRuntime && (lcmCwd !== context.cwd || lcmProjectKey !== projectKey || state.config.compaction.engine !== "lcm")) {
6463
+ await lcmRuntime.shutdown();
6464
+ lcmRuntime = void 0;
6465
+ lcmCwd = void 0;
6466
+ lcmProjectKey = void 0;
6467
+ }
6468
+ if (state.config.compaction.engine === "lcm" && !lcmRuntime) {
6469
+ lcmRuntime = new LcmRuntime(context, {
6470
+ ...state.config.compaction.summaryModel ? { summaryModel: state.config.compaction.summaryModel } : {},
6471
+ maxLeafEntries: state.config.compaction.lcmMaxLeafEntries,
6472
+ maxCondenseChildren: state.config.compaction.lcmMaxCondenseChildren,
6473
+ lcmMaxInputChars: state.config.compaction.lcmMaxInputChars,
6474
+ lcmMaxOutputTokens: state.config.compaction.lcmMaxOutputTokens,
6475
+ lcmMaxOutputChars: state.config.compaction.lcmMaxOutputChars
6476
+ });
6477
+ lcmCwd = context.cwd;
6478
+ lcmProjectKey = lcmRuntime.projectKey;
6479
+ }
6480
+ if (state.config.compaction.engine === "lcm" && lcmRuntime) await lcmRuntime.reconcileSelectedSession();
5571
6481
  refreshCodePreviewSettings();
5572
6482
  applyFabricMode();
5573
6483
  if (!updateCheckStarted) {
@@ -5606,6 +6516,7 @@ async function ompFabric(omp) {
5606
6516
  });
5607
6517
  omp.on("agent_end", async (event, context) => {
5608
6518
  if (event.willContinue === true) return;
6519
+ await lcmRuntime?.syncAndSchedule();
5609
6520
  if (!state.initialized) {
5610
6521
  await compactAtConfiguredThreshold(context, state.config);
5611
6522
  return;
@@ -5667,6 +6578,9 @@ async function ompFabric(omp) {
5667
6578
  });
5668
6579
  return changed ? { content } : void 0;
5669
6580
  });
6581
+ omp.on("message_end", () => {
6582
+ lcmRuntime?.markDirty();
6583
+ });
5670
6584
  omp.on("message_end", (event) => {
5671
6585
  if (event.message.role !== "toolResult") return;
5672
6586
  const message = event.message;
@@ -5706,12 +6620,16 @@ async function ompFabric(omp) {
5706
6620
  }
5707
6621
  });
5708
6622
  omp.on("session_compact", async (event, context) => {
6623
+ await lcmRuntime?.syncAndSchedule();
5709
6624
  if (!state.initialized) return;
5710
6625
  await state.publishHostLifecycle("omp.session_compact", event);
5711
6626
  });
5712
6627
  registerCompactionHook(omp, {
5713
- getEngine: () => state.cwd ? state.config.compaction.engine === "omp" ? "omp" : "fabric" : DEFAULT_FABRIC_CONFIG.compaction.engine === "omp" ? "omp" : "fabric",
5714
- getTargetContextRatio: () => state.cwd ? state.config.compaction.targetContextRatio : DEFAULT_FABRIC_CONFIG.compaction.targetContextRatio
6628
+ getEngine: () => state.cwd ? state.config.compaction.engine : DEFAULT_FABRIC_CONFIG.compaction.engine,
6629
+ getTargetContextRatio: () => state.cwd ? state.config.compaction.targetContextRatio : DEFAULT_FABRIC_CONFIG.compaction.targetContextRatio,
6630
+ lcm: {
6631
+ compact: (input) => lcmRuntime?.compact(input)
6632
+ }
5715
6633
  });
5716
6634
  omp.on("context", (event, context) => {
5717
6635
  const sessionId = context.sessionManager.getSessionId();
@@ -5832,6 +6750,10 @@ ${guidance}`],
5832
6750
  directToolApproval.clear();
5833
6751
  toolDisplay.clear();
5834
6752
  try {
6753
+ await lcmRuntime?.shutdown();
6754
+ lcmRuntime = void 0;
6755
+ lcmCwd = void 0;
6756
+ lcmProjectKey = void 0;
5835
6757
  await state.shutdown();
5836
6758
  } finally {
5837
6759
  uninstallHaltOnEscape();