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.
- package/CHANGELOG.md +19 -0
- package/dist/agents/handoff.d.ts.map +1 -1
- package/dist/chunks/{chunk-WZAH7BO2.js → chunk-2J2GOB4A.js} +30 -11
- package/dist/chunks/chunk-2J2GOB4A.js.map +7 -0
- package/dist/chunks/{chunk-2WDZT27Q.js → chunk-3H5LZWAG.js} +253 -4
- package/dist/chunks/chunk-3H5LZWAG.js.map +7 -0
- package/dist/chunks/{chunk-PYXBHMSL.js → chunk-IMSGR6OW.js} +3 -3
- package/dist/chunks/chunk-IY3IQPRQ.js +308 -0
- package/dist/chunks/chunk-IY3IQPRQ.js.map +7 -0
- package/dist/chunks/{chunk-VSSC4IDU.js → chunk-RTDJFHGH.js} +2 -2
- package/dist/chunks/{chunk-45URY66G.js → chunk-S4I7RMR5.js} +3 -3
- package/dist/chunks/{chunk-ZP3S74LW.js → chunk-SG4GVSWX.js} +49 -26
- package/dist/chunks/chunk-SG4GVSWX.js.map +7 -0
- package/dist/chunks/{chunk-JOWMM3EX.js → chunk-WPU4PKXQ.js} +135 -24
- package/dist/chunks/chunk-WPU4PKXQ.js.map +7 -0
- package/dist/chunks/{chunk-Z5AP4M6K.js → chunk-YOAZ5SO7.js} +12 -2
- package/dist/chunks/{chunk-Z5AP4M6K.js.map → chunk-YOAZ5SO7.js.map} +2 -2
- package/dist/compaction/branch-summary.d.ts +1 -1
- package/dist/compaction/branch-summary.d.ts.map +1 -1
- package/dist/compaction/hook.d.ts +21 -1
- package/dist/compaction/hook.d.ts.map +1 -1
- package/dist/compaction/hook.js +1 -1
- package/dist/compaction/lcm-maintenance.d.ts +103 -0
- package/dist/compaction/lcm-maintenance.d.ts.map +1 -0
- package/dist/compaction/lcm-model.d.ts +41 -0
- package/dist/compaction/lcm-model.d.ts.map +1 -0
- package/dist/compaction/lcm-runtime.d.ts +106 -0
- package/dist/compaction/lcm-runtime.d.ts.map +1 -0
- package/dist/config-migrations.d.ts +1 -1
- package/dist/config-migrations.d.ts.map +1 -1
- package/dist/config.d.ts +7 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/fabric-runtime-state.d.ts +2 -0
- package/dist/fabric-runtime-state.d.ts.map +1 -1
- package/dist/fabric-runtime-state.js +12 -7
- package/dist/fabric-runtime-state.js.map +2 -2
- package/dist/fabric-state.d.ts +2 -1
- package/dist/fabric-state.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1030 -108
- package/dist/index.js.map +4 -4
- package/dist/memory/lcm-adapter.d.ts +97 -0
- package/dist/memory/lcm-adapter.d.ts.map +1 -0
- package/dist/providers/memory-provider.d.ts +8 -0
- package/dist/providers/memory-provider.d.ts.map +1 -1
- package/dist/providers/memory-provider.js +2 -1
- package/dist/residency/host.js +6 -5
- package/dist/residency/omp-entry.js +6 -5
- package/dist/residency/omp-entry.js.map +1 -1
- package/dist/runtime/core-override-guest-types.js +1 -1
- package/dist/runtime/guest-types.d.ts +1 -1
- package/dist/runtime/guest-types.d.ts.map +1 -1
- package/dist/runtime/guest-types.js +1 -1
- package/dist/storage/lcm-ledger.d.ts +107 -0
- package/dist/storage/lcm-ledger.d.ts.map +1 -0
- package/dist/storage/lcm-migration.d.ts +67 -0
- package/dist/storage/lcm-migration.d.ts.map +1 -0
- package/dist/ui/settings.js +5 -5
- package/dist/ui/settings.js.map +2 -2
- package/docs/adr/0001-lcm-durable-context-ledger.md +6 -0
- package/docs/agents/domain.md +25 -0
- package/docs/agents/issue-tracker.md +25 -0
- package/docs/agents/triage-labels.md +11 -0
- package/docs/compaction.md +78 -312
- package/docs/configuration.md +3 -3
- package/package.json +1 -1
- package/dist/chunks/chunk-2WDZT27Q.js.map +0 -7
- package/dist/chunks/chunk-JOWMM3EX.js.map +0 -7
- package/dist/chunks/chunk-WZAH7BO2.js.map +0 -7
- package/dist/chunks/chunk-ZP3S74LW.js.map +0 -7
- /package/dist/chunks/{chunk-PYXBHMSL.js.map → chunk-IMSGR6OW.js.map} +0 -0
- /package/dist/chunks/{chunk-VSSC4IDU.js.map → chunk-RTDJFHGH.js.map} +0 -0
- /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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
191
|
-
|
|
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-
|
|
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-
|
|
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
|
|
267
|
-
state.codePreviewTimingOnlyRenderToken =
|
|
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 ===
|
|
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
|
|
809
|
-
const type = typeof
|
|
810
|
-
const tool = typeof
|
|
811
|
-
const msg =
|
|
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
|
|
824
|
-
const provider = typeof
|
|
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(
|
|
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/
|
|
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 &&
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
2407
|
-
return typeof
|
|
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
|
|
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(
|
|
2721
|
-
const artifactPath =
|
|
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
|
|
2758
|
-
const 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(
|
|
3644
|
+
const resultFormat = RESULT_FORMATS.includes(record2.resultFormat) ? record2.resultFormat : void 0;
|
|
2763
3645
|
return {
|
|
2764
3646
|
code,
|
|
2765
|
-
...
|
|
2766
|
-
...
|
|
3647
|
+
...record2.payloads !== void 0 ? { payloads: record2.payloads } : {},
|
|
3648
|
+
...record2.display !== void 0 ? { display: record2.display } : {},
|
|
2767
3649
|
...resultFormat !== void 0 ? { resultFormat } : {},
|
|
2768
|
-
...typeof
|
|
2769
|
-
...typeof
|
|
2770
|
-
...typeof
|
|
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
|
|
3506
|
-
import
|
|
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 ?
|
|
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
|
-
|
|
3649
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
4968
|
+
directory = path8.dirname(realpathSync2(cliPath));
|
|
4086
4969
|
} catch {
|
|
4087
4970
|
return void 0;
|
|
4088
4971
|
}
|
|
4089
4972
|
while (true) {
|
|
4090
|
-
const manifestPath =
|
|
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 =
|
|
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
|
|
4114
|
-
import
|
|
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) =>
|
|
5005
|
+
var updateCheckCachePath = (agentDir) => path9.join(agentDir, "fabric", "update-check.json");
|
|
4123
5006
|
var installedVersionFromManifest = () => {
|
|
4124
|
-
let directory =
|
|
5007
|
+
let directory = path9.dirname(fileURLToPath(import.meta.url));
|
|
4125
5008
|
while (true) {
|
|
4126
5009
|
try {
|
|
4127
5010
|
const manifest = JSON.parse(
|
|
4128
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
5072
|
+
let record2;
|
|
4190
5073
|
if (fresh && cached !== void 0) {
|
|
4191
|
-
|
|
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
|
-
|
|
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(
|
|
4206
|
-
const announce = behind &&
|
|
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 ? { ...
|
|
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:
|
|
4214
|
-
message: `${PACKAGE_NAME} ${installed} \u2192 ${
|
|
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
|
|
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 = (
|
|
5183
|
+
const agentFromRecord = (record2, nestingDepth, parentId, parent) => {
|
|
4301
5184
|
const linked = parentId ? void 0 : agentLinks.find(
|
|
4302
|
-
({ call }) => call.entityId && (
|
|
5185
|
+
({ call }) => call.entityId && (record2.id.startsWith(call.entityId) || call.entityId.startsWith(record2.id))
|
|
4303
5186
|
);
|
|
4304
5187
|
const base = {
|
|
4305
|
-
id:
|
|
4306
|
-
name:
|
|
4307
|
-
status:
|
|
4308
|
-
runner:
|
|
4309
|
-
transport:
|
|
4310
|
-
cwd:
|
|
4311
|
-
...!isRunRecord(
|
|
4312
|
-
...
|
|
4313
|
-
...
|
|
4314
|
-
...
|
|
4315
|
-
...isRunRecord(
|
|
4316
|
-
...
|
|
4317
|
-
...
|
|
4318
|
-
...
|
|
4319
|
-
...
|
|
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(
|
|
5208
|
+
if (!isRunRecord(record2)) return base;
|
|
4326
5209
|
return {
|
|
4327
5210
|
...base,
|
|
4328
|
-
task:
|
|
4329
|
-
startedAt:
|
|
4330
|
-
updatedAt:
|
|
4331
|
-
...
|
|
4332
|
-
...
|
|
4333
|
-
turns:
|
|
4334
|
-
toolCalls:
|
|
4335
|
-
usage: { ...
|
|
4336
|
-
...
|
|
4337
|
-
...
|
|
4338
|
-
...
|
|
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 = (
|
|
4343
|
-
const agent = agentFromRecord(
|
|
5225
|
+
const appendAgent = (record2, nestingDepth, parentId, parent) => {
|
|
5226
|
+
const agent = agentFromRecord(record2, nestingDepth, parentId, parent);
|
|
4344
5227
|
allAgents.push(agent);
|
|
4345
|
-
if (!isRunRecord(
|
|
4346
|
-
for (const nested of
|
|
4347
|
-
appendAgent(nested, nestingDepth + 1,
|
|
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
|
|
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 ?
|
|
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
|
|
6142
|
+
import path11 from "node:path";
|
|
5260
6143
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5261
|
-
var FABRIC_EXTENSION_ENTRY_PATH =
|
|
5262
|
-
var FABRIC_ENTRY_DIR =
|
|
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:
|
|
5266
|
-
residentHost:
|
|
5267
|
-
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, {
|
|
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
|
|
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();
|