omp-fabric 1.25.28 → 1.25.34
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 +54 -0
- package/dist/actors/manager.d.ts.map +1 -1
- package/dist/chunks/{chunk-TDO2BRZM.js → chunk-3FCNSDDD.js} +2 -2
- package/dist/chunks/{chunk-KW6EUFD2.js → chunk-HOKJTRI4.js} +243 -67
- package/dist/chunks/chunk-HOKJTRI4.js.map +7 -0
- package/dist/compaction/lcm-maintenance.d.ts +6 -3
- package/dist/compaction/lcm-maintenance.d.ts.map +1 -1
- package/dist/compaction/lcm-runtime.d.ts +7 -1
- package/dist/compaction/lcm-runtime.d.ts.map +1 -1
- package/dist/compaction/lcm-runtime.js +296 -59
- package/dist/compaction/lcm-runtime.js.map +4 -4
- package/dist/core/event-loop-slice.d.ts +11 -0
- package/dist/core/event-loop-slice.d.ts.map +1 -0
- package/dist/fabric-runtime-state.d.ts.map +1 -1
- package/dist/fabric-runtime-state.js +65 -13
- package/dist/fabric-runtime-state.js.map +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -9
- package/dist/index.js.map +2 -2
- package/dist/mesh/store.d.ts +3 -0
- package/dist/mesh/store.d.ts.map +1 -1
- package/dist/providers/omp-tools-provider.d.ts +11 -1
- package/dist/providers/omp-tools-provider.d.ts.map +1 -1
- package/dist/residency/host.js +2 -2
- package/dist/residency/omp-entry.js +2 -2
- package/dist/storage/lcm-directory.d.ts +1 -0
- package/dist/storage/lcm-directory.d.ts.map +1 -1
- package/dist/storage/lcm-ledger.d.ts +9 -1
- package/dist/storage/lcm-ledger.d.ts.map +1 -1
- package/dist/storage/lcm-migration.d.ts +6 -2
- package/dist/storage/lcm-migration.d.ts.map +1 -1
- package/dist/storage/lcm-offthread.d.ts +9 -0
- package/dist/storage/lcm-offthread.d.ts.map +1 -0
- package/dist/topology/control-plane.d.ts.map +1 -1
- package/dist/topology/participant-directory.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/chunk-KW6EUFD2.js.map +0 -7
- /package/dist/chunks/{chunk-TDO2BRZM.js.map → chunk-3FCNSDDD.js.map} +0 -0
|
@@ -57,13 +57,119 @@ import fs2 from "node:fs";
|
|
|
57
57
|
import path2 from "node:path";
|
|
58
58
|
import crypto from "node:crypto";
|
|
59
59
|
|
|
60
|
+
// src/storage/lcm-offthread.ts
|
|
61
|
+
import { Worker } from "node:worker_threads";
|
|
62
|
+
var WORKER_SOURCE = String.raw`
|
|
63
|
+
const { parentPort, workerData } = require("node:worker_threads");
|
|
64
|
+
const openReadOnly = (file) => {
|
|
65
|
+
try {
|
|
66
|
+
const { DatabaseSync } = require("node:sqlite");
|
|
67
|
+
return new DatabaseSync(file, { readOnly: true });
|
|
68
|
+
} catch {}
|
|
69
|
+
const { Database } = require("bun:sqlite");
|
|
70
|
+
return new Database(file, { readonly: true });
|
|
71
|
+
};
|
|
72
|
+
let db;
|
|
73
|
+
try {
|
|
74
|
+
db = openReadOnly(workerData.file);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
parentPort.postMessage({ unavailable: error instanceof Error ? error.message : String(error) });
|
|
77
|
+
}
|
|
78
|
+
if (db) {
|
|
79
|
+
try {
|
|
80
|
+
if (workerData.snapshot) db.exec("BEGIN");
|
|
81
|
+
const rows = workerData.statements.map((sql) => ({ ...(db.prepare(sql).get() ?? {}) }));
|
|
82
|
+
if (workerData.snapshot) db.exec("COMMIT");
|
|
83
|
+
parentPort.postMessage({ rows });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
parentPort.postMessage({ error: error instanceof Error ? error.message : String(error) });
|
|
86
|
+
} finally {
|
|
87
|
+
try { db.close(); } catch {}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
var queryLedgerOffThread = (query) => new Promise((resolve, reject) => {
|
|
92
|
+
if (query.signal?.aborted) {
|
|
93
|
+
reject(new Error("ledger closed"));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
let worker;
|
|
97
|
+
try {
|
|
98
|
+
worker = new Worker(WORKER_SOURCE, {
|
|
99
|
+
eval: true,
|
|
100
|
+
workerData: { file: query.file, statements: [...query.statements], snapshot: query.snapshot === true }
|
|
101
|
+
});
|
|
102
|
+
} catch {
|
|
103
|
+
resolve(void 0);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
let settled = false;
|
|
107
|
+
const finish = (settle) => {
|
|
108
|
+
if (settled) return;
|
|
109
|
+
settled = true;
|
|
110
|
+
query.signal?.removeEventListener("abort", onAbort);
|
|
111
|
+
void worker.terminate();
|
|
112
|
+
settle();
|
|
113
|
+
};
|
|
114
|
+
const onAbort = () => finish(() => reject(new Error("ledger closed")));
|
|
115
|
+
query.signal?.addEventListener("abort", onAbort, { once: true });
|
|
116
|
+
worker.once("message", (message) => {
|
|
117
|
+
if (message.unavailable !== void 0) finish(() => resolve(void 0));
|
|
118
|
+
else if (message.error !== void 0) finish(() => reject(new Error(message.error)));
|
|
119
|
+
else if (Array.isArray(message.rows) && message.rows.length === query.statements.length) finish(() => resolve(message.rows));
|
|
120
|
+
else finish(() => resolve(void 0));
|
|
121
|
+
});
|
|
122
|
+
worker.once("error", () => finish(() => resolve(void 0)));
|
|
123
|
+
worker.once("exit", () => finish(() => resolve(void 0)));
|
|
124
|
+
});
|
|
125
|
+
|
|
60
126
|
// src/storage/lcm-directory.ts
|
|
61
127
|
import fs from "node:fs";
|
|
128
|
+
import os from "node:os";
|
|
62
129
|
import path from "node:path";
|
|
63
130
|
var DIRECTORY_FILE = "projects.json";
|
|
64
131
|
var DIRECTORY_VERSION = 1;
|
|
65
132
|
var SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
66
133
|
var RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
134
|
+
var TEMP_RETENTION_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
135
|
+
var tempRootCandidates = () => {
|
|
136
|
+
const roots = /* @__PURE__ */ new Set();
|
|
137
|
+
for (const root of [os.tmpdir(), "/tmp", "/var/tmp"]) {
|
|
138
|
+
const resolved = path.resolve(root);
|
|
139
|
+
roots.add(resolved);
|
|
140
|
+
try {
|
|
141
|
+
roots.add(fs.realpathSync.native(resolved));
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return [...roots];
|
|
146
|
+
};
|
|
147
|
+
var isUnderTempRoot = (target) => {
|
|
148
|
+
const resolved = path.resolve(target);
|
|
149
|
+
const candidates = /* @__PURE__ */ new Set([resolved]);
|
|
150
|
+
try {
|
|
151
|
+
candidates.add(fs.realpathSync.native(resolved));
|
|
152
|
+
} catch {
|
|
153
|
+
let probe = resolved;
|
|
154
|
+
while (true) {
|
|
155
|
+
const parent = path.dirname(probe);
|
|
156
|
+
if (parent === probe) break;
|
|
157
|
+
probe = parent;
|
|
158
|
+
try {
|
|
159
|
+
const real = fs.realpathSync.native(probe);
|
|
160
|
+
candidates.add(path.join(real, path.relative(probe, resolved)));
|
|
161
|
+
break;
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const normalize = (value) => process.platform === "win32" ? value.toLowerCase() : value;
|
|
167
|
+
const normalized = [...candidates].map(normalize);
|
|
168
|
+
return tempRootCandidates().some((root) => {
|
|
169
|
+
const base = normalize(root.endsWith(path.sep) ? root.slice(0, -1) : root);
|
|
170
|
+
return normalized.some((candidate) => candidate === base || candidate.startsWith(base + path.sep));
|
|
171
|
+
});
|
|
172
|
+
};
|
|
67
173
|
var empty = () => ({ version: DIRECTORY_VERSION, sweptAt: 0, projects: [] });
|
|
68
174
|
var directoryFile = (rootDir) => path.join(rootDir, DIRECTORY_FILE);
|
|
69
175
|
var read = (rootDir) => {
|
|
@@ -112,6 +218,7 @@ var stableProjectKey = (rootDir, identity, now = Date.now()) => {
|
|
|
112
218
|
var sweepLedgers = (rootDir, options) => {
|
|
113
219
|
const now = options.now ?? Date.now();
|
|
114
220
|
const retentionMs = options.retentionMs ?? RETENTION_MS;
|
|
221
|
+
const tempRetentionMs = options.tempRetentionMs ?? TEMP_RETENTION_MS;
|
|
115
222
|
const file = read(rootDir);
|
|
116
223
|
if (!options.force && now - file.sweptAt < SWEEP_INTERVAL_MS) return { removed: [], bytes: 0, skipped: true };
|
|
117
224
|
const removed = [];
|
|
@@ -123,9 +230,12 @@ var sweepLedgers = (rootDir, options) => {
|
|
|
123
230
|
try {
|
|
124
231
|
stats = fs.statSync(ledger);
|
|
125
232
|
} catch {
|
|
233
|
+
if (record2.key !== options.keepKey) continue;
|
|
234
|
+
kept.push(record2);
|
|
126
235
|
continue;
|
|
127
236
|
}
|
|
128
|
-
const
|
|
237
|
+
const retention = isUnderTempRoot(record2.path) ? tempRetentionMs : retentionMs;
|
|
238
|
+
const abandoned = record2.key !== options.keepKey && !fs.existsSync(record2.path) && now - Math.max(stats.mtimeMs, record2.updatedAt) >= retention;
|
|
129
239
|
if (!abandoned) {
|
|
130
240
|
kept.push(record2);
|
|
131
241
|
continue;
|
|
@@ -260,6 +370,8 @@ var LcmLedger = class {
|
|
|
260
370
|
closed = false;
|
|
261
371
|
fts = false;
|
|
262
372
|
startupChecksRun = false;
|
|
373
|
+
startupChecksPending;
|
|
374
|
+
closing = new AbortController();
|
|
263
375
|
migrations = [];
|
|
264
376
|
constructor(options = {}) {
|
|
265
377
|
this.now = options.now ?? Date.now;
|
|
@@ -295,18 +407,36 @@ var LcmLedger = class {
|
|
|
295
407
|
get startupChecksComplete() {
|
|
296
408
|
return this.startupChecksRun;
|
|
297
409
|
}
|
|
410
|
+
/** Whole-file scans run on a worker thread so a multi-gigabyte ledger never stalls the host event loop. */
|
|
298
411
|
runStartupChecks() {
|
|
299
|
-
if (this.startupChecksRun) return;
|
|
412
|
+
if (this.startupChecksRun) return Promise.resolve();
|
|
413
|
+
this.startupChecksPending ??= this.checkStartup().finally(() => {
|
|
414
|
+
this.startupChecksPending = void 0;
|
|
415
|
+
});
|
|
416
|
+
return this.startupChecksPending;
|
|
417
|
+
}
|
|
418
|
+
async checkStartup() {
|
|
300
419
|
try {
|
|
301
|
-
this.migrateSchema();
|
|
302
|
-
const result = this.
|
|
303
|
-
if (result
|
|
420
|
+
await this.migrateSchema();
|
|
421
|
+
const [result] = await this.scan(["PRAGMA integrity_check"]);
|
|
422
|
+
if (result?.integrity_check !== "ok") throw new Error(String(result?.integrity_check));
|
|
304
423
|
} catch (error) {
|
|
305
424
|
this.degraded = true;
|
|
306
425
|
throw new LedgerDegradedError("ledger startup checks failed", error);
|
|
307
426
|
}
|
|
308
427
|
this.startupChecksRun = true;
|
|
309
428
|
}
|
|
429
|
+
async scan(statements, snapshot = false) {
|
|
430
|
+
const offThread = await queryLedgerOffThread({ file: this.dbPath, statements, snapshot, signal: this.closing.signal });
|
|
431
|
+
if (offThread) return offThread;
|
|
432
|
+
this.guard();
|
|
433
|
+
if (snapshot && this.transactionDepth === 0) this.db.exec("BEGIN");
|
|
434
|
+
try {
|
|
435
|
+
return statements.map((sql) => ({ ...this.db.prepare(sql).get() ?? {} }));
|
|
436
|
+
} finally {
|
|
437
|
+
if (snapshot && this.transactionDepth === 0) this.db.exec("COMMIT");
|
|
438
|
+
}
|
|
439
|
+
}
|
|
310
440
|
readRepairLadder(projectKey = this.project.key) {
|
|
311
441
|
return this.readOnly((db) => new Map(db.prepare("SELECT fault,attempts,next_at,detail,updated_at FROM repair_ladder WHERE project_key=?").all(projectKey).map((row) => [row.fault, { attempts: Number(row.attempts), nextAt: Number(row.next_at), detail: row.detail, updatedAt: Number(row.updated_at) }])));
|
|
312
442
|
}
|
|
@@ -363,15 +493,16 @@ var LcmLedger = class {
|
|
|
363
493
|
hasObject(type, name) {
|
|
364
494
|
return this.db.prepare("SELECT 1 FROM sqlite_master WHERE type=? AND name=?").get(type, name) !== void 0;
|
|
365
495
|
}
|
|
366
|
-
indexOutOfStep() {
|
|
496
|
+
async indexOutOfStep() {
|
|
367
497
|
if (this.fts !== this.hasObject("table", "raw_entries_fts")) return true;
|
|
368
498
|
if (!this.fts) return false;
|
|
369
499
|
if (!this.hasObject("trigger", "raw_entries_fts_insert")) return true;
|
|
370
|
-
|
|
500
|
+
const [indexed, stored] = await this.scan(["SELECT count(*) n FROM raw_entries_fts", "SELECT count(*) n FROM raw_entries"], true);
|
|
501
|
+
return Number(indexed?.n) !== Number(stored?.n);
|
|
371
502
|
}
|
|
372
|
-
migrateSchema() {
|
|
503
|
+
async migrateSchema() {
|
|
373
504
|
const version = this.schemaVersion();
|
|
374
|
-
if (version < 2 || this.indexOutOfStep()) this.migrations.push(this.migrateFullTextIndex());
|
|
505
|
+
if (version < 2 || await this.indexOutOfStep()) this.migrations.push(this.migrateFullTextIndex());
|
|
375
506
|
if (version < 3 || this.db.prepare("PRAGMA foreign_key_list(summary_edges)").all().length === 0) this.migrations.push(this.migrateDerivedForeignKeys());
|
|
376
507
|
if (version !== LEDGER_SCHEMA_VERSION) this.db.prepare("INSERT INTO schema_metadata(key,value) VALUES('version',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(String(LEDGER_SCHEMA_VERSION));
|
|
377
508
|
}
|
|
@@ -507,6 +638,20 @@ ALTER TABLE summary_node_revisions_next RENAME TO summary_node_revisions;`);
|
|
|
507
638
|
const rows = sessionId ? this.db.prepare("SELECT * FROM raw_entries WHERE project_key=? AND session_id=? ORDER BY created_at,revision,rowid").all(projectKey, sessionId) : this.db.prepare("SELECT * FROM raw_entries WHERE project_key=? ORDER BY created_at,session_id,entry_id,revision").all(projectKey);
|
|
508
639
|
return toRawEntries(rows);
|
|
509
640
|
}
|
|
641
|
+
/** The same rows in the same order as readRaw for one session, read in keyset pages that yield between batches. */
|
|
642
|
+
async readSessionRaw(sessionId, slice) {
|
|
643
|
+
const entries = [];
|
|
644
|
+
let cursor;
|
|
645
|
+
for (; ; ) {
|
|
646
|
+
this.guard();
|
|
647
|
+
const rows = cursor ? this.db.prepare("SELECT rowid AS lcm_rowid,* FROM raw_entries WHERE project_key=? AND session_id=? AND (created_at,revision,rowid)>(?,?,?) ORDER BY created_at,revision,rowid LIMIT ?").all(this.project.key, sessionId, cursor.createdAt, cursor.revision, cursor.rowid, LCM_SCAN_BATCH) : this.db.prepare("SELECT rowid AS lcm_rowid,* FROM raw_entries WHERE project_key=? AND session_id=? ORDER BY created_at,revision,rowid LIMIT ?").all(this.project.key, sessionId, LCM_SCAN_BATCH);
|
|
648
|
+
for (const row of rows) entries.push(toRawEntry(row));
|
|
649
|
+
const last = rows.at(-1);
|
|
650
|
+
if (!last || rows.length < LCM_SCAN_BATCH) return entries;
|
|
651
|
+
cursor = { createdAt: Number(last.created_at), revision: Number(last.revision), rowid: Number(last.lcm_rowid) };
|
|
652
|
+
await slice.pause();
|
|
653
|
+
}
|
|
654
|
+
}
|
|
510
655
|
readRawPage(projectKey = this.project.key, sessionId, offset = 0, limit = 100) {
|
|
511
656
|
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1) throw new Error("invalid raw page");
|
|
512
657
|
this.guard();
|
|
@@ -729,6 +874,7 @@ ALTER TABLE summary_node_revisions_next RENAME TO summary_node_revisions;`);
|
|
|
729
874
|
}
|
|
730
875
|
close() {
|
|
731
876
|
if (this.closed) return;
|
|
877
|
+
this.closing.abort();
|
|
732
878
|
this.db.close();
|
|
733
879
|
this.closed = true;
|
|
734
880
|
}
|
|
@@ -738,6 +884,33 @@ ALTER TABLE summary_node_revisions_next RENAME TO summary_node_revisions;`);
|
|
|
738
884
|
import crypto2 from "node:crypto";
|
|
739
885
|
import fs3 from "node:fs";
|
|
740
886
|
import path3 from "node:path";
|
|
887
|
+
|
|
888
|
+
// src/core/event-loop-slice.ts
|
|
889
|
+
var EVENT_LOOP_SLICE_MS = 20;
|
|
890
|
+
var yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
|
|
891
|
+
var EventLoopSlice = class {
|
|
892
|
+
constructor(budgetMs = EVENT_LOOP_SLICE_MS) {
|
|
893
|
+
this.budgetMs = budgetMs;
|
|
894
|
+
}
|
|
895
|
+
#started = performance.now();
|
|
896
|
+
#yields = 0;
|
|
897
|
+
get due() {
|
|
898
|
+
return performance.now() - this.#started >= this.budgetMs;
|
|
899
|
+
}
|
|
900
|
+
get yields() {
|
|
901
|
+
return this.#yields;
|
|
902
|
+
}
|
|
903
|
+
async yield() {
|
|
904
|
+
this.#yields += 1;
|
|
905
|
+
await yieldToEventLoop();
|
|
906
|
+
this.#started = performance.now();
|
|
907
|
+
}
|
|
908
|
+
async pause() {
|
|
909
|
+
if (this.due) await this.yield();
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
// src/storage/lcm-migration.ts
|
|
741
914
|
var plural = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
742
915
|
var megabytes = (bytes) => `${(bytes / 1024 ** 2).toFixed(1)} MB`;
|
|
743
916
|
var migrationDropReasons = (drops) => {
|
|
@@ -879,7 +1052,8 @@ var CHECKPOINT_SCHEMA = "CREATE TABLE IF NOT EXISTS migration_checkpoints (proje
|
|
|
879
1052
|
function reconcileSession(options) {
|
|
880
1053
|
return migrateSessions({ ...options, onDemand: true });
|
|
881
1054
|
}
|
|
882
|
-
function migrateSessions(options) {
|
|
1055
|
+
async function migrateSessions(options) {
|
|
1056
|
+
const slice = options.slice ?? new EventLoopSlice();
|
|
883
1057
|
const window = migrationWindow(options);
|
|
884
1058
|
const maxFileBytes = positive(options.maxFileBytes, 64 * 1024 ** 2);
|
|
885
1059
|
const maxLineBytes = positive(options.maxLineBytes, 8 * 1024 ** 2);
|
|
@@ -901,6 +1075,8 @@ function migrateSessions(options) {
|
|
|
901
1075
|
const drops = result.drops;
|
|
902
1076
|
const drySeen = /* @__PURE__ */ new Set();
|
|
903
1077
|
files: for (const [position, file] of discovery.files.entries()) {
|
|
1078
|
+
await slice.pause();
|
|
1079
|
+
if (options.signal?.aborted) break;
|
|
904
1080
|
const read2 = readSource(file, maxFileBytes, maxTotalBytes - result.bytesScanned);
|
|
905
1081
|
counts.raced += read2.raced;
|
|
906
1082
|
if (read2.kind !== "data") {
|
|
@@ -944,6 +1120,8 @@ function migrateSessions(options) {
|
|
|
944
1120
|
let ordinal = 0;
|
|
945
1121
|
try {
|
|
946
1122
|
for (let start = 0; start < data.length; ) {
|
|
1123
|
+
if (slice.due) await slice.yield();
|
|
1124
|
+
if (options.signal?.aborted) break;
|
|
947
1125
|
ordinal++;
|
|
948
1126
|
const newline = data.indexOf(10, start);
|
|
949
1127
|
const end = newline < 0 ? data.length : newline;
|
|
@@ -996,7 +1174,7 @@ function migrateSessions(options) {
|
|
|
996
1174
|
if (options.ledger) ledger = options.ledger;
|
|
997
1175
|
else if (options.ledgerRoot) ledger = new LcmLedger({ rootDir: options.ledgerRoot, project: { recordedCwd: cwd } });
|
|
998
1176
|
else ledger = new LcmLedger({ project: { recordedCwd: cwd } });
|
|
999
|
-
ledger.runStartupChecks();
|
|
1177
|
+
await ledger.runStartupChecks();
|
|
1000
1178
|
database = ledger.db;
|
|
1001
1179
|
database.exec(CHECKPOINT_SCHEMA);
|
|
1002
1180
|
} else if (options.ledger) database = options.ledger.db;
|
|
@@ -1074,6 +1252,8 @@ function migrateSessions(options) {
|
|
|
1074
1252
|
import crypto3 from "node:crypto";
|
|
1075
1253
|
var DEFAULT_LCM_BUDGET = { calls: Number.POSITIVE_INFINITY, inputTokens: Number.POSITIVE_INFINITY, outputTokens: Number.POSITIVE_INFINITY, cost: Number.POSITIVE_INFINITY, wallMs: Number.POSITIVE_INFINITY, sessionCalls: Number.POSITIVE_INFINITY };
|
|
1076
1254
|
var DEFAULT_LEAF_ENTRIES = 32;
|
|
1255
|
+
var NODE_SCAN_LIMIT = 1e5;
|
|
1256
|
+
var NODE_PAGE_SIZE = 500;
|
|
1077
1257
|
var EMPTY_TAINT = /* @__PURE__ */ new Set();
|
|
1078
1258
|
var DEFAULT_MAINTENANCE_CONCURRENCY = 3;
|
|
1079
1259
|
var LEASE_MS = 3e4;
|
|
@@ -1161,8 +1341,7 @@ var LcmMaintenance = class {
|
|
|
1161
1341
|
revisionsOf(nodeId) {
|
|
1162
1342
|
return this.ledger.readOnly((db) => db.prepare("SELECT revision, text, model_hash, created_at FROM summary_node_revisions WHERE node_id=? AND project_key=? ORDER BY revision").all(nodeId, this.projectKey).map((row) => ({ revision: Number(row.revision), text: row.text, modelHash: row.model_hash, createdAt: Number(row.created_at) })));
|
|
1163
1343
|
}
|
|
1164
|
-
selectUpgrades(sessionId, activeSources, limit = 1) {
|
|
1165
|
-
const all = this.listNodes(1e5);
|
|
1344
|
+
selectUpgrades(sessionId, activeSources, limit = 1, all = this.listNodes(NODE_SCAN_LIMIT)) {
|
|
1166
1345
|
const taint = this.tainted(all);
|
|
1167
1346
|
return all.filter((node) => !taint.has(node.nodeId) && node.state === "ready" && node.modelHash === "emergency" && node.policyHash === this.policyHash && (!sessionId || node.sessionId === sessionId) && (!activeSources || node.sources.every((source) => activeSources.has(this.sourceKey(source))))).sort((left, right) => left.depth - right.depth || left.nodeId.localeCompare(right.nodeId)).slice(0, limit);
|
|
1168
1347
|
}
|
|
@@ -1173,7 +1352,7 @@ var LcmMaintenance = class {
|
|
|
1173
1352
|
const order = [];
|
|
1174
1353
|
while (stack.length > 0) {
|
|
1175
1354
|
const current = stack.pop();
|
|
1176
|
-
const parents = db.prepare("SELECT e.parent_id FROM summary_edges e JOIN summary_nodes parent ON parent.node_id=e.parent_id WHERE e.child_id=? AND parent.project_key=?").all(current, this.projectKey);
|
|
1355
|
+
const parents = db.prepare("SELECT e.parent_id FROM summary_edges e CROSS JOIN summary_nodes parent ON parent.node_id=e.parent_id WHERE e.child_id=? AND parent.project_key=?").all(current, this.projectKey);
|
|
1177
1356
|
for (const { parent_id: parent } of parents) {
|
|
1178
1357
|
if (seen.has(parent)) continue;
|
|
1179
1358
|
seen.add(parent);
|
|
@@ -1204,6 +1383,25 @@ var LcmMaintenance = class {
|
|
|
1204
1383
|
return node;
|
|
1205
1384
|
}));
|
|
1206
1385
|
}
|
|
1386
|
+
/** The same nodes in the same order as listNodes(NODE_SCAN_LIMIT), read in keyset pages that yield between batches. */
|
|
1387
|
+
async loadNodes(slice) {
|
|
1388
|
+
const nodes = [];
|
|
1389
|
+
let cursor;
|
|
1390
|
+
while (nodes.length < NODE_SCAN_LIMIT) {
|
|
1391
|
+
const limit = Math.min(NODE_PAGE_SIZE, NODE_SCAN_LIMIT - nodes.length);
|
|
1392
|
+
const rows = this.ledger.readOnly((db) => cursor ? db.prepare("SELECT payload,created_at,node_id FROM summary_nodes WHERE project_key=? AND (created_at,node_id)>(?,?) ORDER BY created_at,node_id LIMIT ?").all(this.projectKey, cursor.createdAt, cursor.nodeId, limit) : db.prepare("SELECT payload,created_at,node_id FROM summary_nodes WHERE project_key=? ORDER BY created_at,node_id LIMIT ?").all(this.projectKey, limit));
|
|
1393
|
+
for (const row of rows) {
|
|
1394
|
+
const node = parse(row.payload);
|
|
1395
|
+
if (node.projectKey !== this.projectKey) throw new Error("node project does not match ledger project");
|
|
1396
|
+
nodes.push(node);
|
|
1397
|
+
}
|
|
1398
|
+
const last = rows.at(-1);
|
|
1399
|
+
if (!last || rows.length < limit) break;
|
|
1400
|
+
cursor = { createdAt: last.created_at, nodeId: last.node_id };
|
|
1401
|
+
await slice.pause();
|
|
1402
|
+
}
|
|
1403
|
+
return nodes;
|
|
1404
|
+
}
|
|
1207
1405
|
getNode(nodeId) {
|
|
1208
1406
|
return this.ledger.readOnly((db) => {
|
|
1209
1407
|
const row = db.prepare("SELECT payload FROM summary_nodes WHERE node_id=? AND project_key=?").get(nodeId, this.projectKey);
|
|
@@ -1231,7 +1429,7 @@ var LcmMaintenance = class {
|
|
|
1231
1429
|
}
|
|
1232
1430
|
/** Scoped by the live source set when given; drops a condensed child and a contained node. */
|
|
1233
1431
|
getFrontier(sessionId, activeSources) {
|
|
1234
|
-
const all = this.listNodes(
|
|
1432
|
+
const all = this.listNodes(NODE_SCAN_LIMIT);
|
|
1235
1433
|
const taint = this.tainted(all);
|
|
1236
1434
|
const nodes = all.filter((n) => !taint.has(n.nodeId) && n.state === "ready" && n.policyHash === this.policyHash && (!sessionId || activeSources !== void 0 || n.sessionId === sessionId) && (!activeSources || n.sources.every((s) => activeSources.has(this.sourceKey(s)))));
|
|
1237
1435
|
const condensed = new Set(nodes.flatMap((n) => n.children));
|
|
@@ -1363,10 +1561,9 @@ var LcmMaintenance = class {
|
|
|
1363
1561
|
return next;
|
|
1364
1562
|
});
|
|
1365
1563
|
}
|
|
1366
|
-
selectLeaf(entries = this.ledger.readRaw(this.projectKey), activeSources) {
|
|
1564
|
+
selectLeaf(entries = this.ledger.readRaw(this.projectKey), activeSources, all = this.listNodes(NODE_SCAN_LIMIT)) {
|
|
1367
1565
|
const first = entries[0];
|
|
1368
1566
|
if (!first) return [];
|
|
1369
|
-
const all = this.listNodes(1e5);
|
|
1370
1567
|
const taint = this.tainted(all);
|
|
1371
1568
|
const nodes = all.filter((n) => !taint.has(n.nodeId) && n.state !== "failed" && n.policyHash === this.policyHash && (n.sessionId === first.sessionId || activeSources !== void 0 && n.state === "ready") && (!activeSources || n.sources.every((s) => activeSources.has(this.sourceKey(s)))));
|
|
1372
1569
|
const covered = new Set(nodes.flatMap((n) => n.sources.map((s) => this.sourceKey(s))));
|
|
@@ -1382,12 +1579,11 @@ var LcmMaintenance = class {
|
|
|
1382
1579
|
}
|
|
1383
1580
|
return picked;
|
|
1384
1581
|
}
|
|
1385
|
-
selectCondensation(sessionId, activeSources) {
|
|
1386
|
-
const all = this.listNodes(1e5);
|
|
1582
|
+
selectCondensation(sessionId, activeSources, all = this.listNodes(NODE_SCAN_LIMIT)) {
|
|
1387
1583
|
const taint = this.tainted(all);
|
|
1388
1584
|
const nodes = all.filter((n) => !taint.has(n.nodeId) && n.state === "ready" && n.policyHash === this.policyHash && (!sessionId || n.sessionId === sessionId) && (!activeSources || n.sources.every((s) => activeSources.has(this.sourceKey(s)))));
|
|
1389
1585
|
const eligible = new Set(nodes.map((n) => n.nodeId));
|
|
1390
|
-
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));
|
|
1586
|
+
const edges = this.ledger.readOnly((db) => db.prepare("SELECT e.parent_id,e.child_id FROM summary_edges e CROSS JOIN summary_nodes parent ON parent.node_id=e.parent_id AND parent.project_key=? CROSS 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));
|
|
1391
1587
|
const consumed = new Set(edges.filter((e) => eligible.has(e.parent_id)).map((e) => e.child_id));
|
|
1392
1588
|
const candidates = nodes.filter((n) => !consumed.has(n.nodeId));
|
|
1393
1589
|
const first = candidates[0];
|
|
@@ -1801,8 +1997,13 @@ var LcmRuntime = class {
|
|
|
1801
1997
|
persisted = /* @__PURE__ */ new WeakMap();
|
|
1802
1998
|
persistedSessionId;
|
|
1803
1999
|
lastReconciliation;
|
|
2000
|
+
reconciled = false;
|
|
2001
|
+
/** Subagent sessions share the parent's ledger; only the top-level session migrates and maintains it. */
|
|
2002
|
+
owner;
|
|
1804
2003
|
constructor(context, options = {}) {
|
|
1805
2004
|
this.context = context;
|
|
2005
|
+
const agent = context.agent;
|
|
2006
|
+
this.owner = agent?.kind !== "sub" && (agent?.depth ?? 0) === 0;
|
|
1806
2007
|
this.resolveOptions = typeof options === "function" ? options : () => options;
|
|
1807
2008
|
const initial = this.resolveOptions();
|
|
1808
2009
|
const recorded = context.sessionManager.getRecordedCwd?.();
|
|
@@ -1824,10 +2025,13 @@ var LcmRuntime = class {
|
|
|
1824
2025
|
});
|
|
1825
2026
|
}
|
|
1826
2027
|
get ready() {
|
|
1827
|
-
return this.ledger.startupChecksComplete;
|
|
2028
|
+
return this.owner && this.reconciled && this.ledger.startupChecksComplete;
|
|
1828
2029
|
}
|
|
1829
2030
|
open() {
|
|
1830
|
-
this.ledger.runStartupChecks();
|
|
2031
|
+
return this.owner ? this.ledger.runStartupChecks() : Promise.resolve();
|
|
2032
|
+
}
|
|
2033
|
+
slice() {
|
|
2034
|
+
return new EventLoopSlice(this.options.sliceMs);
|
|
1831
2035
|
}
|
|
1832
2036
|
get options() {
|
|
1833
2037
|
return this.resolveOptions();
|
|
@@ -1893,39 +2097,50 @@ var LcmRuntime = class {
|
|
|
1893
2097
|
this.frontierCache = void 0;
|
|
1894
2098
|
}
|
|
1895
2099
|
reconcileSelectedSession() {
|
|
1896
|
-
if (this.closed) return Promise.resolve();
|
|
1897
|
-
return this.enqueueWrite(() => {
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
this.refreshActiveSources(sessionId, this.context.sessionManager.getBranch());
|
|
2100
|
+
if (this.closed || !this.owner) return Promise.resolve();
|
|
2101
|
+
return this.enqueueWrite(async () => {
|
|
2102
|
+
try {
|
|
2103
|
+
await this.reconcileSessionFile();
|
|
2104
|
+
} finally {
|
|
2105
|
+
this.reconciled = true;
|
|
2106
|
+
}
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
async reconcileSessionFile() {
|
|
2110
|
+
const getSessionFile = this.context.sessionManager.getSessionFile;
|
|
2111
|
+
if (typeof getSessionFile !== "function") return;
|
|
2112
|
+
const sessionFile = getSessionFile.call(this.context.sessionManager);
|
|
2113
|
+
if (!sessionFile) return;
|
|
2114
|
+
const recorded = this.context.sessionManager.getRecordedCwd?.();
|
|
2115
|
+
const cwd = recorded || this.context.cwd;
|
|
2116
|
+
const result = await reconcileSession({
|
|
2117
|
+
agentDir: this.options.rootDir ?? process.env.PI_CODING_AGENT_DIR ?? `${process.env.HOME ?? "."}/.omp/agent`,
|
|
2118
|
+
ledger: this.ledger,
|
|
2119
|
+
files: [sessionFile],
|
|
2120
|
+
liveCwd: cwd,
|
|
2121
|
+
projectCwd: cwd,
|
|
2122
|
+
apply: true,
|
|
2123
|
+
slice: this.slice(),
|
|
2124
|
+
signal: this.signal
|
|
1922
2125
|
});
|
|
2126
|
+
if (this.closed) return;
|
|
2127
|
+
this.lastReconciliation = {
|
|
2128
|
+
degraded: result.degraded,
|
|
2129
|
+
errors: result.counts.errors,
|
|
2130
|
+
raced: result.counts.raced,
|
|
2131
|
+
absent: result.counts.absent,
|
|
2132
|
+
drops: result.drops,
|
|
2133
|
+
reasons: migrationDropReasons(result.drops)
|
|
2134
|
+
};
|
|
2135
|
+
const sessionId = this.context.sessionManager.getSessionId();
|
|
2136
|
+
this.refreshActiveSources(sessionId, this.context.sessionManager.getBranch());
|
|
1923
2137
|
}
|
|
1924
2138
|
/** Appends only the entries this session has not stored yet; the rest are already addressed. */
|
|
1925
2139
|
readback() {
|
|
1926
|
-
if (this.closed) return Promise.resolve();
|
|
1927
|
-
return this.enqueueWrite(() => {
|
|
2140
|
+
if (this.closed || !this.owner) return Promise.resolve();
|
|
2141
|
+
return this.enqueueWrite(async () => {
|
|
1928
2142
|
if (this.closed) return;
|
|
2143
|
+
const slice = this.slice();
|
|
1929
2144
|
const entries = this.context.sessionManager.getBranch();
|
|
1930
2145
|
const sessionId = this.context.sessionManager.getSessionId();
|
|
1931
2146
|
const recorded = this.context.sessionManager.getRecordedCwd?.();
|
|
@@ -1936,15 +2151,20 @@ var LcmRuntime = class {
|
|
|
1936
2151
|
const active = /* @__PURE__ */ new Set();
|
|
1937
2152
|
const pending = [];
|
|
1938
2153
|
for (const entry of entries) {
|
|
2154
|
+
if (slice.due) await slice.yield();
|
|
1939
2155
|
const payloadJson = canonicalLcmPayload(entry);
|
|
1940
2156
|
const contentHash = hash(canonicalLcmEntry(entry));
|
|
1941
2157
|
const known = this.persisted.get(entry);
|
|
1942
2158
|
if (known?.contentHash === contentHash) active.add(known.key);
|
|
1943
2159
|
else pending.push({ entry, payloadJson, contentHash });
|
|
1944
2160
|
}
|
|
1945
|
-
|
|
2161
|
+
for (let next = 0; next < pending.length; ) {
|
|
2162
|
+
if (slice.due) await slice.yield();
|
|
2163
|
+
if (this.closed) return;
|
|
2164
|
+
const first = next;
|
|
1946
2165
|
this.ledger.transaction(() => {
|
|
1947
|
-
for (
|
|
2166
|
+
for (; next < pending.length && (next === first || !slice.due); next += 1) {
|
|
2167
|
+
const { entry, payloadJson, contentHash } = pending[next];
|
|
1948
2168
|
const message = entry.type === "message" ? entry.message : void 0;
|
|
1949
2169
|
const stored = this.ledger.appendRaw({
|
|
1950
2170
|
projectKey: this.projectKey,
|
|
@@ -1976,7 +2196,7 @@ var LcmRuntime = class {
|
|
|
1976
2196
|
});
|
|
1977
2197
|
}
|
|
1978
2198
|
async syncAndSchedule() {
|
|
1979
|
-
if (this.closed) return;
|
|
2199
|
+
if (this.closed || !this.owner) return;
|
|
1980
2200
|
const sessionId = this.context.sessionManager.getSessionId();
|
|
1981
2201
|
if (this.dirty || !this.activeSessionId || this.activeSessionId !== sessionId || this.activeSources.size === 0) {
|
|
1982
2202
|
await this.readback();
|
|
@@ -2022,7 +2242,7 @@ var LcmRuntime = class {
|
|
|
2022
2242
|
}
|
|
2023
2243
|
/** Reclaims ledgers whose project directory is gone; bounded to one sweep a day. */
|
|
2024
2244
|
sweepLedgers() {
|
|
2025
|
-
if (this.closed) return { removed: [], bytes: 0, skipped: true };
|
|
2245
|
+
if (this.closed || !this.owner) return { removed: [], bytes: 0, skipped: true };
|
|
2026
2246
|
try {
|
|
2027
2247
|
return sweepLedgers(this.options.rootDir ?? defaultLedgerRoot(), { keepKey: this.projectKey });
|
|
2028
2248
|
} catch {
|
|
@@ -2070,7 +2290,7 @@ var LcmRuntime = class {
|
|
|
2070
2290
|
return this.syncAndSchedule();
|
|
2071
2291
|
}
|
|
2072
2292
|
scheduleMaintenance() {
|
|
2073
|
-
if (this.closed) return;
|
|
2293
|
+
if (this.closed || !this.owner) return;
|
|
2074
2294
|
if (this.maintenanceRunning) {
|
|
2075
2295
|
this.maintenanceQueued = true;
|
|
2076
2296
|
return;
|
|
@@ -2135,9 +2355,13 @@ var LcmRuntime = class {
|
|
|
2135
2355
|
const fanIn = Math.max(2, this.options.maxCondenseChildren ?? 4);
|
|
2136
2356
|
const passes = Math.max(1, this.options.maxMaintenancePasses ?? 4);
|
|
2137
2357
|
const runDeadline = Date.now() + Math.max(1, this.options.maintenanceRunSeconds ?? 60) * 1e3;
|
|
2138
|
-
const
|
|
2358
|
+
const slice = this.slice();
|
|
2359
|
+
await slice.pause();
|
|
2360
|
+
if (this.closed) return;
|
|
2361
|
+
const raw = (await this.ledger.readSessionRaw(sessionId, slice)).filter(
|
|
2139
2362
|
(entry) => this.activeSources.has(this.sourceKey(entry))
|
|
2140
2363
|
);
|
|
2364
|
+
if (this.closed) return;
|
|
2141
2365
|
const inputFor = (node) => {
|
|
2142
2366
|
if (node.kind === "condensed") return node.children.map((id) => this.maintenance.getNode(id)?.text ?? "").filter(Boolean).join("\n\n");
|
|
2143
2367
|
return node.sources.map((source) => {
|
|
@@ -2166,6 +2390,8 @@ var LcmRuntime = class {
|
|
|
2166
2390
|
let next = 0;
|
|
2167
2391
|
const worker = async (slot) => {
|
|
2168
2392
|
while (!this.closed && Date.now() < runDeadline && slot < this.maintenance.concurrencyLimit) {
|
|
2393
|
+
await slice.pause();
|
|
2394
|
+
if (this.closed) return;
|
|
2169
2395
|
const item = items[next];
|
|
2170
2396
|
if (!item) return;
|
|
2171
2397
|
next += 1;
|
|
@@ -2188,7 +2414,10 @@ var LcmRuntime = class {
|
|
|
2188
2414
|
const leaves = [];
|
|
2189
2415
|
const seen = /* @__PURE__ */ new Set();
|
|
2190
2416
|
while (leaves.length < this.maintenance.concurrencyLimit) {
|
|
2191
|
-
const
|
|
2417
|
+
const nodes2 = await this.maintenance.loadNodes(slice);
|
|
2418
|
+
await slice.pause();
|
|
2419
|
+
if (this.closed) return;
|
|
2420
|
+
const rows = this.maintenance.selectLeaf(raw, this.activeSources, nodes2);
|
|
2192
2421
|
if (rows.length === 0) break;
|
|
2193
2422
|
const created = this.maintenance.createLeaf(rows);
|
|
2194
2423
|
const node = created ? this.maintenance.getNode(created.nodeId) : void 0;
|
|
@@ -2206,13 +2435,18 @@ var LcmRuntime = class {
|
|
|
2206
2435
|
queued.add(job.jobId);
|
|
2207
2436
|
}
|
|
2208
2437
|
}
|
|
2438
|
+
await slice.pause();
|
|
2439
|
+
if (this.closed) return;
|
|
2209
2440
|
const pending = this.actionableJobs(sessionId, passes);
|
|
2210
2441
|
for (const item of pending) if (!queued.has(item.job.jobId)) {
|
|
2211
2442
|
queued.add(item.job.jobId);
|
|
2212
2443
|
batch.push(item);
|
|
2213
2444
|
}
|
|
2214
2445
|
await dispatch(batch);
|
|
2215
|
-
const
|
|
2446
|
+
const nodes = await this.maintenance.loadNodes(slice);
|
|
2447
|
+
await slice.pause();
|
|
2448
|
+
if (this.closed) return;
|
|
2449
|
+
const children = this.maintenance.selectCondensation(sessionId, this.activeSources, nodes);
|
|
2216
2450
|
if (children.length >= fanIn) {
|
|
2217
2451
|
const node = this.maintenance.createCondensed(children);
|
|
2218
2452
|
if (node) {
|
|
@@ -2221,7 +2455,10 @@ var LcmRuntime = class {
|
|
|
2221
2455
|
}
|
|
2222
2456
|
}
|
|
2223
2457
|
if (leaves.length === 0 && pending.length === 0 && children.length < fanIn) {
|
|
2224
|
-
const
|
|
2458
|
+
const nodes2 = await this.maintenance.loadNodes(slice);
|
|
2459
|
+
await slice.pause();
|
|
2460
|
+
if (this.closed) return;
|
|
2461
|
+
const upgrade = this.maintenance.selectUpgrades(sessionId, this.activeSources, UPGRADE_CANDIDATES, nodes2).find(fitsBound);
|
|
2225
2462
|
if (!upgrade) break;
|
|
2226
2463
|
const job = this.maintenance.reopen(upgrade.nodeId);
|
|
2227
2464
|
await runJob(job, upgrade);
|