omp-fabric 1.23.0 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/capture/wrapper.d.ts.map +1 -1
  3. package/dist/chunks/{chunk-SDZXNTBF.js → chunk-2DY7NDAZ.js} +2 -2
  4. package/dist/chunks/{chunk-DTG3DYVC.js → chunk-2HOIGAHB.js} +3 -3
  5. package/dist/chunks/{chunk-3GMSYFLT.js → chunk-5EZWO4C5.js} +3 -3
  6. package/dist/chunks/{chunk-BPMSLBX4.js → chunk-5LNZXB4X.js} +2 -2
  7. package/dist/chunks/{chunk-RA2IB2QT.js → chunk-5P4VR2IZ.js} +4 -4
  8. package/dist/chunks/{chunk-R4EFMCIY.js → chunk-6DJFJLM6.js} +2 -2
  9. package/dist/chunks/{chunk-ZQ72MAHY.js → chunk-7OEFSXWZ.js} +3 -2
  10. package/dist/chunks/{chunk-ZQ72MAHY.js.map → chunk-7OEFSXWZ.js.map} +2 -2
  11. package/dist/chunks/{chunk-WCV26AOT.js → chunk-CBQOS3IW.js} +112 -4
  12. package/dist/chunks/chunk-CBQOS3IW.js.map +7 -0
  13. package/dist/chunks/{chunk-GUJNTA7Z.js → chunk-H63XBYD3.js} +4 -2
  14. package/dist/chunks/chunk-H63XBYD3.js.map +7 -0
  15. package/dist/chunks/{chunk-4J65SMNI.js → chunk-I5ZKJNKL.js} +173 -71
  16. package/dist/chunks/chunk-I5ZKJNKL.js.map +7 -0
  17. package/dist/chunks/{chunk-YBNCEX5E.js → chunk-IH3JJDYL.js} +2 -2
  18. package/dist/chunks/{chunk-52JBWP2C.js → chunk-K5YUDXMV.js} +2 -2
  19. package/dist/chunks/{chunk-MQSRZC2O.js → chunk-MJOGV6CR.js} +2 -2
  20. package/dist/chunks/chunk-QW5LEEFV.js +130 -0
  21. package/dist/chunks/chunk-QW5LEEFV.js.map +7 -0
  22. package/dist/chunks/{chunk-LNP6JVCZ.js → chunk-QYDOKSGI.js} +5 -4
  23. package/dist/chunks/chunk-QYDOKSGI.js.map +7 -0
  24. package/dist/chunks/{chunk-P4GPBAQS.js → chunk-RT6AUW5S.js} +3 -3
  25. package/dist/chunks/{chunk-ZFMSJLQE.js → chunk-S3CWAHSO.js} +11 -1
  26. package/dist/chunks/chunk-S3CWAHSO.js.map +7 -0
  27. package/dist/compaction/hook.d.ts +1 -0
  28. package/dist/compaction/hook.d.ts.map +1 -1
  29. package/dist/compaction/hook.js +4 -4
  30. package/dist/compaction/lcm-addresses.d.ts.map +1 -1
  31. package/dist/compaction/lcm-maintenance.d.ts +2 -0
  32. package/dist/compaction/lcm-maintenance.d.ts.map +1 -1
  33. package/dist/compaction/lcm-model.d.ts +1 -0
  34. package/dist/compaction/lcm-model.d.ts.map +1 -1
  35. package/dist/compaction/lcm-recovery.d.ts +7 -0
  36. package/dist/compaction/lcm-recovery.d.ts.map +1 -0
  37. package/dist/compaction/lcm-runtime.d.ts +6 -1
  38. package/dist/compaction/lcm-runtime.d.ts.map +1 -1
  39. package/dist/compaction/lcm-runtime.js +281 -93
  40. package/dist/compaction/lcm-runtime.js.map +3 -3
  41. package/dist/config.d.ts +1 -0
  42. package/dist/config.d.ts.map +1 -1
  43. package/dist/core/action-registry.js +3 -3
  44. package/dist/core/omp-tools.d.ts +4 -0
  45. package/dist/core/omp-tools.d.ts.map +1 -1
  46. package/dist/core/system-guidance.d.ts.map +1 -1
  47. package/dist/core/tool-ownership.d.ts.map +1 -1
  48. package/dist/entropy/index.js +6 -6
  49. package/dist/fabric-exec-tool.d.ts.map +1 -1
  50. package/dist/fabric-runtime-state.js +15 -15
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +39 -18
  53. package/dist/index.js.map +3 -3
  54. package/dist/memory/discovery.js +5 -5
  55. package/dist/memory/lcm-adapter.d.ts +7 -2
  56. package/dist/memory/lcm-adapter.d.ts.map +1 -1
  57. package/dist/memory/normalize.js +4 -4
  58. package/dist/memory/search.js +5 -5
  59. package/dist/providers/memory-provider.d.ts +2 -1
  60. package/dist/providers/memory-provider.d.ts.map +1 -1
  61. package/dist/providers/memory-provider.js +8 -8
  62. package/dist/residency/host.js +10 -10
  63. package/dist/residency/omp-entry.js +10 -10
  64. package/dist/runtime/core-override-guest-types.js +1 -1
  65. package/dist/runtime/node-process-runtime.js +1 -1
  66. package/dist/storage/lcm-identity.d.ts +4 -0
  67. package/dist/storage/lcm-identity.d.ts.map +1 -1
  68. package/dist/storage/lcm-ledger.d.ts +11 -2
  69. package/dist/storage/lcm-ledger.d.ts.map +1 -1
  70. package/dist/storage/lcm-migration.d.ts.map +1 -1
  71. package/dist/ui/settings.d.ts.map +1 -1
  72. package/dist/ui/settings.js +22 -2
  73. package/dist/ui/settings.js.map +2 -2
  74. package/docs/compaction.md +16 -4
  75. package/docs/configuration.md +1 -1
  76. package/package.json +1 -1
  77. package/dist/chunks/chunk-4J65SMNI.js.map +0 -7
  78. package/dist/chunks/chunk-GUJNTA7Z.js.map +0 -7
  79. package/dist/chunks/chunk-LNP6JVCZ.js.map +0 -7
  80. package/dist/chunks/chunk-NX2G7BRO.js +0 -87
  81. package/dist/chunks/chunk-NX2G7BRO.js.map +0 -7
  82. package/dist/chunks/chunk-WCV26AOT.js.map +0 -7
  83. package/dist/chunks/chunk-ZFMSJLQE.js.map +0 -7
  84. /package/dist/chunks/{chunk-SDZXNTBF.js.map → chunk-2DY7NDAZ.js.map} +0 -0
  85. /package/dist/chunks/{chunk-DTG3DYVC.js.map → chunk-2HOIGAHB.js.map} +0 -0
  86. /package/dist/chunks/{chunk-3GMSYFLT.js.map → chunk-5EZWO4C5.js.map} +0 -0
  87. /package/dist/chunks/{chunk-BPMSLBX4.js.map → chunk-5LNZXB4X.js.map} +0 -0
  88. /package/dist/chunks/{chunk-RA2IB2QT.js.map → chunk-5P4VR2IZ.js.map} +0 -0
  89. /package/dist/chunks/{chunk-R4EFMCIY.js.map → chunk-6DJFJLM6.js.map} +0 -0
  90. /package/dist/chunks/{chunk-YBNCEX5E.js.map → chunk-IH3JJDYL.js.map} +0 -0
  91. /package/dist/chunks/{chunk-52JBWP2C.js.map → chunk-K5YUDXMV.js.map} +0 -0
  92. /package/dist/chunks/{chunk-MQSRZC2O.js.map → chunk-MJOGV6CR.js.map} +0 -0
  93. /package/dist/chunks/{chunk-P4GPBAQS.js.map → chunk-RT6AUW5S.js.map} +0 -0
@@ -10,24 +10,29 @@ import {
10
10
  import {
11
11
  sessionDirNamesForCwd,
12
12
  sessionsDirRoot
13
- } from "../chunks/chunk-MQSRZC2O.js";
14
- import "../chunks/chunk-BPMSLBX4.js";
13
+ } from "../chunks/chunk-MJOGV6CR.js";
14
+ import "../chunks/chunk-5LNZXB4X.js";
15
15
  import {
16
+ DEFAULT_LCM_MAX_INPUT_CHARS,
16
17
  LcmModelAdapter,
18
+ activeRequestBlock,
17
19
  buildLcmPrompt,
18
- emergencyReduce
19
- } from "../chunks/chunk-WCV26AOT.js";
20
+ emergencyReduce,
21
+ tailDigest
22
+ } from "../chunks/chunk-CBQOS3IW.js";
20
23
  import {
24
+ canonicalLcmEntry,
21
25
  canonicalLcmPayload,
22
26
  canonicalProjectIdentity,
23
27
  defaultLedgerPath,
24
28
  defaultLedgerRoot,
25
29
  hash,
26
30
  hashLcmPayload,
31
+ isPrunedLcmEntry,
27
32
  lcmSummaryAddress,
28
33
  renderLcmChildAddresses,
29
34
  renderLcmSourceAddresses
30
- } from "../chunks/chunk-NX2G7BRO.js";
35
+ } from "../chunks/chunk-QW5LEEFV.js";
31
36
  import {
32
37
  writeJsonAtomic
33
38
  } from "../chunks/chunk-KAD7UWT2.js";
@@ -40,9 +45,9 @@ import {
40
45
  clipUtf8,
41
46
  utf8Bytes
42
47
  } from "../chunks/chunk-3XPR2AZL.js";
43
- import "../chunks/chunk-YBNCEX5E.js";
44
- import "../chunks/chunk-52JBWP2C.js";
45
- import "../chunks/chunk-ZFMSJLQE.js";
48
+ import "../chunks/chunk-IH3JJDYL.js";
49
+ import "../chunks/chunk-K5YUDXMV.js";
50
+ import "../chunks/chunk-S3CWAHSO.js";
46
51
 
47
52
  // src/compaction/lcm-runtime.ts
48
53
  import fs4 from "node:fs";
@@ -141,6 +146,28 @@ var sweepLedgers = (rootDir, options) => {
141
146
  // src/storage/lcm-ledger.ts
142
147
  var LCM_LEDGER_WARNING_BYTES = 8 * 1024 ** 3;
143
148
  var LCM_LEDGER_MAINTENANCE_BYTES = 10 * 1024 ** 3;
149
+ var rowFilter = (options, alias = "") => {
150
+ const column = (name) => alias ? `${alias}.${name}` : name;
151
+ const clauses = [];
152
+ const params = [];
153
+ const list = (values, operator) => {
154
+ if (!values || values.length === 0) return;
155
+ clauses.push(`${column("role")} ${operator} (${values.map(() => "?").join(",")})`);
156
+ params.push(...values);
157
+ };
158
+ list(options.roles, "IN");
159
+ list(options.excludeRoles, "NOT IN");
160
+ if (options.since !== void 0) {
161
+ clauses.push(`${column("created_at")}>=?`);
162
+ params.push(options.since);
163
+ }
164
+ if (options.until !== void 0) {
165
+ clauses.push(`${column("created_at")}<=?`);
166
+ params.push(options.until);
167
+ }
168
+ return { sql: clauses.map((clause) => ` AND ${clause}`).join(""), params };
169
+ };
170
+ var validSearchFilter = (options) => [options.roles, options.excludeRoles].every((values) => values === void 0 || Array.isArray(values) && values.every((value) => typeof value === "string" && value.length > 0)) && [options.since, options.until].every((value) => value === void 0 || Number.isFinite(value));
144
171
  var LEDGER_SCHEMA_VERSION = 4;
145
172
  var BACKUP_MANIFEST_VERSION = 3;
146
173
  var DELETE_MANIFEST_VERSION = 2;
@@ -405,8 +432,8 @@ ALTER TABLE summary_node_revisions_next RENAME TO summary_node_revisions;`);
405
432
  throw new Error("invalid payload JSON");
406
433
  }
407
434
  if (!payload || typeof payload !== "object" || typeof payload.type !== "string" || typeof payload.id !== "string" || payload.id !== entry.entryId) throw new Error("payload must be a full SessionEntry");
408
- const payloadJson = canonicalLcmPayload(payload);
409
- const contentHash = hashLcmPayload(payload);
435
+ const payloadJson = canonicalLcmEntry(payload);
436
+ const contentHash = hash(payloadJson);
410
437
  const ownsTransaction = this.transactionDepth === 0;
411
438
  if (ownsTransaction) this.db.exec("BEGIN IMMEDIATE");
412
439
  try {
@@ -415,6 +442,11 @@ ALTER TABLE summary_node_revisions_next RENAME TO summary_node_revisions;`);
415
442
  if (ownsTransaction) this.db.exec("COMMIT");
416
443
  return { ...entry, payloadJson, payloadHash: contentHash, revision: existing.revision, contentHash, createdAt: existing.created_at };
417
444
  }
445
+ const prior = this.priorRevision(entry.projectKey, entry.sessionId, entry.entryId, contentHash, isPrunedLcmEntry(payload));
446
+ if (prior) {
447
+ if (ownsTransaction) this.db.exec("COMMIT");
448
+ return prior;
449
+ }
418
450
  const latest = this.db.prepare("SELECT COALESCE(MAX(revision),0) revision FROM raw_entries WHERE project_key=? AND session_id=? AND entry_id=?").get(entry.projectKey, entry.sessionId, entry.entryId);
419
451
  const revision = latest.revision + 1;
420
452
  const createdAt = entry.createdAt ?? this.now();
@@ -429,6 +461,28 @@ ALTER TABLE summary_node_revisions_next RENAME TO summary_node_revisions;`);
429
461
  throw new LedgerDegradedError("ledger write failed", error);
430
462
  }
431
463
  }
464
+ identities = /* @__PURE__ */ new Map();
465
+ /** A stored revision the host only rewrote (pruned, re-signed, blob-externalized) stays the entry's source. */
466
+ priorRevision(projectKey, sessionId, entryId, identity, pruned) {
467
+ const rows = this.db.prepare("SELECT * FROM raw_entries WHERE project_key=? AND session_id=? AND entry_id=? ORDER BY revision DESC").all(projectKey, sessionId, entryId);
468
+ if (rows.length === 0) return void 0;
469
+ if (pruned) return toRawEntry(rows[0]);
470
+ const match = rows.find((row) => this.identityOf(row) === identity);
471
+ return match ? toRawEntry(match) : void 0;
472
+ }
473
+ identityOf(row) {
474
+ const stored = row.content_hash;
475
+ const cached = this.identities.get(stored);
476
+ if (cached !== void 0) return cached;
477
+ let identity = stored;
478
+ try {
479
+ identity = hash(canonicalLcmEntry(JSON.parse(row.payload_json)));
480
+ } catch {
481
+ }
482
+ if (this.identities.size >= 5e4) this.identities.clear();
483
+ this.identities.set(stored, identity);
484
+ return identity;
485
+ }
432
486
  readRaw(projectKey = this.project.key, sessionId) {
433
487
  this.guard();
434
488
  this.assertProjectKey(projectKey);
@@ -456,8 +510,9 @@ ALTER TABLE summary_node_revisions_next RENAME TO summary_node_revisions;`);
456
510
  if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1) throw new Error("invalid raw page");
457
511
  const scanLimit = options.scanLimit ?? LCM_SEARCH_SCAN_LIMIT;
458
512
  if (!Number.isSafeInteger(scanLimit) || scanLimit < 1) throw new Error("invalid scan limit");
513
+ if (!validSearchFilter(options)) throw new Error("invalid raw search filter");
459
514
  const query = options.query?.trim() ?? "";
460
- if (query.length === 0) return this.recentPage(key, sessionId, offset, limit);
515
+ if (query.length === 0) return this.recentPage(key, sessionId, offset, limit, rowFilter(options));
461
516
  if (options.mode === "regex") {
462
517
  let pattern;
463
518
  try {
@@ -465,40 +520,43 @@ ALTER TABLE summary_node_revisions_next RENAME TO summary_node_revisions;`);
465
520
  } catch {
466
521
  return { rows: [], total: 0, scanned: 0, complete: false };
467
522
  }
468
- return this.scanPage(key, sessionId, (content) => pattern.test(content), offset, limit, scanLimit, false);
523
+ return this.scanPage(key, sessionId, (content) => pattern.test(content), offset, limit, scanLimit, false, rowFilter(options));
469
524
  }
470
525
  const match = options.match ?? "any";
471
526
  const phrases = searchPhrases(query, options.mode);
472
527
  if (phrases.length === 0) return { rows: [], total: 0, scanned: 0, complete: true };
473
528
  if (this.fts) {
474
529
  try {
475
- return this.indexPage(key, sessionId, ftsExpression(phrases, match), offset, limit);
530
+ return this.indexPage(key, sessionId, ftsExpression(phrases, match), offset, limit, rowFilter(options, "r"), options.order === "relevance");
476
531
  } catch {
477
532
  }
478
533
  }
479
- return this.scanPage(key, sessionId, phrasePredicate(phrases, match), offset, limit, scanLimit, true);
534
+ return this.scanPage(key, sessionId, phrasePredicate(phrases, match), offset, limit, scanLimit, true, rowFilter(options));
480
535
  }
481
- recentPage(key, sessionId, offset, limit) {
482
- const counted = sessionId ? this.db.prepare("SELECT count(*) n FROM raw_entries WHERE project_key=? AND session_id=?").get(key, sessionId) : this.db.prepare("SELECT count(*) n FROM raw_entries WHERE project_key=?").get(key);
483
- const total = Number(counted.n);
484
- const rows = sessionId ? this.db.prepare("SELECT * FROM raw_entries WHERE project_key=? AND session_id=? ORDER BY created_at DESC, revision DESC, rowid DESC LIMIT ? OFFSET ?").all(key, sessionId, limit, offset) : this.db.prepare("SELECT * FROM raw_entries WHERE project_key=? ORDER BY created_at DESC, revision DESC, rowid DESC LIMIT ? OFFSET ?").all(key, limit, offset);
536
+ recentPage(key, sessionId, offset, limit, filter) {
537
+ const scope = `FROM raw_entries WHERE project_key=?${sessionId ? " AND session_id=?" : ""}${filter.sql}`;
538
+ const params = [key, ...sessionId ? [sessionId] : [], ...filter.params];
539
+ const total = Number(this.db.prepare(`SELECT count(*) n ${scope}`).get(...params).n);
540
+ const rows = this.db.prepare(`SELECT * ${scope} ORDER BY created_at DESC, revision DESC, rowid DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
485
541
  return { rows: toRawEntries(rows), total, scanned: total, complete: true };
486
542
  }
487
- indexPage(key, sessionId, expression, offset, limit) {
488
- const source = "FROM raw_entries_fts JOIN raw_entries r ON r.project_key=raw_entries_fts.project_key AND r.session_id=raw_entries_fts.session_id AND r.entry_id=raw_entries_fts.entry_id AND r.revision=CAST(raw_entries_fts.revision AS INTEGER) WHERE raw_entries_fts MATCH ? AND raw_entries_fts.project_key=?" + (sessionId ? " AND raw_entries_fts.session_id=?" : "");
489
- const filters = sessionId ? [expression, key, sessionId] : [expression, key];
543
+ indexPage(key, sessionId, expression, offset, limit, filter, relevance) {
544
+ const source = "FROM raw_entries_fts JOIN raw_entries r ON r.project_key=raw_entries_fts.project_key AND r.session_id=raw_entries_fts.session_id AND r.entry_id=raw_entries_fts.entry_id AND r.revision=CAST(raw_entries_fts.revision AS INTEGER) WHERE raw_entries_fts MATCH ? AND raw_entries_fts.project_key=?" + (sessionId ? " AND raw_entries_fts.session_id=?" : "") + filter.sql;
545
+ const filters = [expression, key, ...sessionId ? [sessionId] : [], ...filter.params];
490
546
  const total = Number(this.db.prepare(`SELECT count(*) n ${source}`).get(...filters).n);
491
- const rows = this.db.prepare(`SELECT r.* ${source} ORDER BY r.created_at DESC, r.revision DESC, r.rowid DESC LIMIT ? OFFSET ?`).all(...filters, limit, offset);
547
+ const order = relevance ? "bm25(raw_entries_fts), r.created_at DESC, r.revision DESC, r.rowid DESC" : "r.created_at DESC, r.revision DESC, r.rowid DESC";
548
+ const rows = this.db.prepare(`SELECT r.* ${source} ORDER BY ${order} LIMIT ? OFFSET ?`).all(...filters, limit, offset);
492
549
  return { rows: toRawEntries(rows), total, scanned: total, complete: true };
493
550
  }
494
- scanPage(key, sessionId, matches, offset, limit, scanLimit, degraded) {
495
- const statement = sessionId ? this.db.prepare("SELECT session_id,entry_id,revision,content FROM raw_entries WHERE project_key=? AND session_id=? ORDER BY created_at DESC, revision DESC, rowid DESC LIMIT ? OFFSET ?") : this.db.prepare("SELECT session_id,entry_id,revision,content FROM raw_entries WHERE project_key=? ORDER BY created_at DESC, revision DESC, rowid DESC LIMIT ? OFFSET ?");
551
+ scanPage(key, sessionId, matches, offset, limit, scanLimit, degraded, filter) {
552
+ const statement = this.db.prepare(`SELECT session_id,entry_id,revision,content FROM raw_entries WHERE project_key=?${sessionId ? " AND session_id=?" : ""}${filter.sql} ORDER BY created_at DESC, revision DESC, rowid DESC LIMIT ? OFFSET ?`);
553
+ const scope = [key, ...sessionId ? [sessionId] : [], ...filter.params];
496
554
  const found = [];
497
555
  let scanned = 0;
498
556
  let exhausted = false;
499
557
  while (scanned < scanLimit) {
500
558
  const size = Math.min(LCM_SCAN_BATCH, scanLimit - scanned);
501
- const batch = sessionId ? statement.all(key, sessionId, size, scanned) : statement.all(key, size, scanned);
559
+ const batch = statement.all(...scope, size, scanned);
502
560
  scanned += batch.length;
503
561
  for (const row of batch) if (matches(row.content)) found.push({ sessionId: row.session_id, entryId: row.entry_id, revision: Number(row.revision) });
504
562
  if (batch.length < size) {
@@ -954,13 +1012,13 @@ function migrateSessions(options) {
954
1012
  }
955
1013
  counts.eligible++;
956
1014
  const payloadJson = canonicalLcmPayload(row);
957
- const contentHash = hashLcmPayload(row);
1015
+ const contentHash = hash2(canonicalLcmEntry(row));
958
1016
  const identity = JSON.stringify([projectKey, sessionId, row.id, contentHash]);
959
1017
  const progress = { projectKey, sessionPath: file, sourceHash, lineOrdinal: ordinal, entryId: row.id, contentHash };
960
1018
  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;
961
1019
  if (duplicate) counts.skippedDuplicate++;
962
1020
  else if (ledger) {
963
- ledger.appendRaw({
1021
+ const persisted = ledger.appendRaw({
964
1022
  projectKey,
965
1023
  sessionId,
966
1024
  entryId: row.id,
@@ -972,7 +1030,8 @@ function migrateSessions(options) {
972
1030
  ...cwd ? { recordedCwd: cwd } : {},
973
1031
  createdAt: at
974
1032
  });
975
- counts.imported++;
1033
+ if (persisted.contentHash === contentHash) counts.imported++;
1034
+ else counts.skippedDuplicate++;
976
1035
  } else if (!duplicate) counts.imported++;
977
1036
  drySeen.add(identity);
978
1037
  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);
@@ -996,6 +1055,7 @@ function migrateSessions(options) {
996
1055
  import crypto3 from "node:crypto";
997
1056
  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 };
998
1057
  var DEFAULT_LEAF_ENTRIES = 32;
1058
+ var EMPTY_TAINT = /* @__PURE__ */ new Set();
999
1059
  var DEFAULT_MAINTENANCE_CONCURRENCY = 3;
1000
1060
  var LEASE_MS = 3e4;
1001
1061
  var LEASE_SWEEP_GRACE_MS = 6e4;
@@ -1133,8 +1193,26 @@ var LcmMaintenance = class {
1133
1193
  });
1134
1194
  }
1135
1195
  /** Scoped by the live source set when given; drops a condensed child and a contained node. */
1196
+ /** The oversized deterministic leaves and every ancestor that inherited one. */
1197
+ tainted(nodes) {
1198
+ const oversized = nodes.filter((node) => node.kind === "leaf" && node.modelHash === "emergency" && node.sources.length > this.maxLeaf).map((node) => node.nodeId);
1199
+ if (oversized.length === 0) return EMPTY_TAINT;
1200
+ const parents = /* @__PURE__ */ new Map();
1201
+ for (const node of nodes) for (const child of node.children) parents.set(child, [...parents.get(child) ?? [], node.nodeId]);
1202
+ const excluded = /* @__PURE__ */ new Set();
1203
+ const queue = [...oversized];
1204
+ while (queue.length > 0) {
1205
+ const nodeId = queue.pop();
1206
+ if (excluded.has(nodeId)) continue;
1207
+ excluded.add(nodeId);
1208
+ for (const parent of parents.get(nodeId) ?? []) queue.push(parent);
1209
+ }
1210
+ return excluded;
1211
+ }
1136
1212
  getFrontier(sessionId, activeSources) {
1137
- const nodes = this.listNodes(1e5).filter((n) => n.state === "ready" && n.policyHash === this.policyHash && (!sessionId || activeSources !== void 0 || n.sessionId === sessionId) && (!activeSources || n.sources.every((s) => activeSources.has(this.sourceKey(s)))));
1213
+ const all = this.listNodes(1e5);
1214
+ const taint = this.tainted(all);
1215
+ 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)))));
1138
1216
  const condensed = new Set(nodes.flatMap((n) => n.children));
1139
1217
  const standing = nodes.filter((n) => !condensed.has(n.nodeId));
1140
1218
  const ranked = [...standing].sort((a, b) => b.sources.length - a.sources.length || a.nodeId.localeCompare(b.nodeId));
@@ -1267,7 +1345,9 @@ var LcmMaintenance = class {
1267
1345
  selectLeaf(entries = this.ledger.readRaw(this.projectKey), activeSources) {
1268
1346
  const first = entries[0];
1269
1347
  if (!first) return [];
1270
- const nodes = this.listNodes(1e5).filter((n) => 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)))));
1348
+ const all = this.listNodes(1e5);
1349
+ const taint = this.tainted(all);
1350
+ 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)))));
1271
1351
  const covered = new Set(nodes.flatMap((n) => n.sources.map((s) => this.sourceKey(s))));
1272
1352
  const candidates = entries.filter((e) => e.sessionId === first.sessionId && !covered.has(this.sourceKey(e)));
1273
1353
  const picked = [];
@@ -1282,7 +1362,9 @@ var LcmMaintenance = class {
1282
1362
  return picked;
1283
1363
  }
1284
1364
  selectCondensation(sessionId, activeSources) {
1285
- 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)))));
1365
+ const all = this.listNodes(1e5);
1366
+ const taint = this.tainted(all);
1367
+ 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)))));
1286
1368
  const eligible = new Set(nodes.map((n) => n.nodeId));
1287
1369
  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));
1288
1370
  const consumed = new Set(edges.filter((e) => eligible.has(e.parent_id)).map((e) => e.child_id));
@@ -1574,6 +1656,11 @@ var MAX_REPAIR_LOG = 8;
1574
1656
  var CLAIMABLE_JOB_LIMIT = 256;
1575
1657
  var FAILED_JOB_WINDOW_MS = 24 * 60 * 60 * 1e3;
1576
1658
  var NODE_ADDRESS_LABEL = "address: ";
1659
+ var TAIL_DIGEST_MAX_BYTES = Math.floor(MAX_SUMMARY_BYTES * 0.2);
1660
+ var FRONTIER_REQUEST_BYTES = 4096;
1661
+ var MIN_SHARED_BLOCK_BYTES = 512;
1662
+ var ACTIVE_REQUEST_SHARE = 0.4;
1663
+ var UPGRADE_CANDIDATES = 256;
1577
1664
  var BLOCK_SEPARATOR = "\n\n";
1578
1665
  var WITHHELD_EXPAND_LABEL = "; expand: ";
1579
1666
  var withheldHeadline = (count) => `\u2026 withheld ${count} frontier node${count === 1 ? "" : "s"} that did not fit`;
@@ -1603,17 +1690,19 @@ ${NODE_ADDRESS_LABEL}${lcmSummaryAddress(node.nodeId)}`;
1603
1690
  const parts = [...requestBlock ? [requestBlock] : [], head, ...notice ? [notice] : []];
1604
1691
  return `${parts.join(BLOCK_SEPARATOR)}${footer}`;
1605
1692
  };
1606
- var renderAddressedFrontier = (frontier, requestLines = []) => {
1693
+ var renderAddressedFrontier = (frontier, requestLines = [], surround = {}) => {
1607
1694
  const blocks = frontier.filter((node) => Boolean(node.text)).map((node) => ({ node, head: `${node.text ?? ""}
1608
1695
  ${NODE_ADDRESS_LABEL}${lcmSummaryAddress(node.nodeId)}` }));
1609
1696
  if (blocks.length === 0) return "";
1610
- const requestBlock = requestLines.length > 0 ? `[Compaction Request]
1611
- ${requestLines.join("\n")}` : "";
1697
+ const leads = [requestLines.length > 0 ? `[Compaction Request]
1698
+ ${requestLines.join("\n")}` : "", surround.lead ?? ""].filter((block) => block.length > 0);
1699
+ const trail = clipUtf8(surround.trail ?? "", TAIL_DIGEST_MAX_BYTES);
1700
+ const requestBlock = leads.join(BLOCK_SEPARATOR);
1612
1701
  const footer = `
1613
1702
 
1614
1703
  ${LCM_RECOVERY_POINTER}`;
1615
1704
  const separator = utf8Bytes(BLOCK_SEPARATOR);
1616
- const footerBytes = utf8Bytes(footer);
1705
+ const footerBytes = utf8Bytes(footer) + (trail ? utf8Bytes(trail) + separator : 0);
1617
1706
  const requestBytes = requestBlock ? utf8Bytes(requestBlock) + separator : 0;
1618
1707
  const headBytes = blocks.map((block) => utf8Bytes(block.head));
1619
1708
  const floorFor = (kept2) => kept2 === blocks.length ? 0 : separator + utf8Bytes(withheldHeadline(blocks.length - kept2));
@@ -1629,7 +1718,38 @@ ${LCM_RECOVERY_POINTER}`;
1629
1718
  kept.push(index);
1630
1719
  body = grown;
1631
1720
  }
1632
- if (kept.length === 0) return clipOversized(blocks, headBytes, footer, separator, requestBlock);
1721
+ if (withheld.length > 0) {
1722
+ const addressLines = blocks.map(({ node }) => `
1723
+ ${NODE_ADDRESS_LABEL}${lcmSummaryAddress(node.nodeId)}`);
1724
+ let room2 = MAX_SUMMARY_BYTES - requestBytes - footerBytes - separator * (blocks.length - 1) - addressLines.reduce((total, line) => total + utf8Bytes(line), 0);
1725
+ const textBytes = blocks.map(({ node }) => utf8Bytes(node.text ?? ""));
1726
+ const budget = new Array(blocks.length).fill(0);
1727
+ let open = blocks.map((_unused, index) => index);
1728
+ while (open.length > 0 && room2 > 0) {
1729
+ const share = Math.floor(room2 / open.length);
1730
+ if (share < 1) break;
1731
+ const fitting = open.filter((index) => textBytes[index] <= share);
1732
+ if (fitting.length === 0) {
1733
+ for (const index of open) budget[index] = share;
1734
+ room2 -= share * open.length;
1735
+ break;
1736
+ }
1737
+ for (const index of fitting) {
1738
+ budget[index] = textBytes[index];
1739
+ room2 -= textBytes[index];
1740
+ }
1741
+ open = open.filter((index) => !fitting.includes(index));
1742
+ }
1743
+ if (budget.every((value, index) => value >= Math.min(textBytes[index], MIN_SHARED_BLOCK_BYTES))) {
1744
+ const shared = blocks.map(({ node }, index) => {
1745
+ const text = clipUtf8(node.text ?? "", budget[index]);
1746
+ return text ? `${text}${addressLines[index]}` : addressLines[index].slice(1);
1747
+ });
1748
+ const parts2 = [...requestBlock ? [requestBlock] : [], ...shared, ...trail ? [trail] : []];
1749
+ return `${parts2.join(BLOCK_SEPARATOR)}${footer}`;
1750
+ }
1751
+ }
1752
+ if (kept.length === 0) return clipOversized(blocks, headBytes, `${trail ? `${BLOCK_SEPARATOR}${trail}` : ""}${footer}`, separator, requestBlock);
1633
1753
  const room = MAX_SUMMARY_BYTES - body - footerBytes - (withheld.length === 0 ? 0 : separator);
1634
1754
  const notice = withheld.length === 0 ? "" : withheldNotice(withheld.map((index) => blocks[index].node.nodeId), Math.max(utf8Bytes(withheldHeadline(withheld.length)), Math.floor(room / 2)));
1635
1755
  const perNode = Math.max(0, Math.floor((room - utf8Bytes(notice)) / kept.length) - 1);
@@ -1639,7 +1759,7 @@ ${LCM_RECOVERY_POINTER}`;
1639
1759
  return addresses && utf8Bytes(addresses) <= perNode ? `${head}
1640
1760
  ${addresses}` : head;
1641
1761
  });
1642
- const parts = [...requestBlock ? [requestBlock] : [], ...rendered, ...notice ? [notice] : []];
1762
+ const parts = [...requestBlock ? [requestBlock] : [], ...rendered, ...notice ? [notice] : [], ...trail ? [trail] : []];
1643
1763
  return `${parts.join(BLOCK_SEPARATOR)}${footer}`;
1644
1764
  };
1645
1765
  var LcmRuntime = class {
@@ -1650,6 +1770,8 @@ var LcmRuntime = class {
1650
1770
  abort = new AbortController();
1651
1771
  writePending = Promise.resolve();
1652
1772
  maintenancePending = Promise.resolve();
1773
+ maintenanceRunning = false;
1774
+ maintenanceQueued = false;
1653
1775
  closed = false;
1654
1776
  dirty = false;
1655
1777
  degradedError;
@@ -1788,7 +1910,7 @@ var LcmRuntime = class {
1788
1910
  const pending = [];
1789
1911
  for (const entry of entries) {
1790
1912
  const payloadJson = canonicalLcmPayload(entry);
1791
- const contentHash = hash(payloadJson);
1913
+ const contentHash = hash(canonicalLcmEntry(entry));
1792
1914
  const known = this.persisted.get(entry);
1793
1915
  if (known?.contentHash === contentHash) active.add(known.key);
1794
1916
  else pending.push({ entry, payloadJson, contentHash });
@@ -1922,7 +2044,21 @@ var LcmRuntime = class {
1922
2044
  }
1923
2045
  scheduleMaintenance() {
1924
2046
  if (this.closed) return;
1925
- const run = this.maintenancePending.then(() => this.runMaintenance());
2047
+ if (this.maintenanceRunning) {
2048
+ this.maintenanceQueued = true;
2049
+ return;
2050
+ }
2051
+ this.maintenanceRunning = true;
2052
+ const run = this.maintenancePending.then(async () => {
2053
+ await this.runMaintenance();
2054
+ while (this.maintenanceQueued && !this.closed) {
2055
+ this.maintenanceQueued = false;
2056
+ await this.runMaintenance();
2057
+ }
2058
+ }).finally(() => {
2059
+ this.maintenanceRunning = false;
2060
+ this.maintenanceQueued = false;
2061
+ });
1926
2062
  this.maintenancePending = run.catch((error) => {
1927
2063
  this.degradedError = error;
1928
2064
  });
@@ -2007,12 +2143,31 @@ var LcmRuntime = class {
2007
2143
  await Promise.all(Array.from({ length: Math.min(this.maintenance.concurrencyLimit, items.length) }, (_unused, slot) => worker(slot)));
2008
2144
  };
2009
2145
  this.reclaimExpiredLeases();
2146
+ const inputBound = this.options.lcmMaxInputChars ?? DEFAULT_LCM_MAX_INPUT_CHARS;
2147
+ const fitsBound = (node) => {
2148
+ if (node.sources.length > (this.options.maxLeafEntries ?? DEFAULT_LEAF_ENTRIES) * fanIn) return false;
2149
+ try {
2150
+ return utf8Bytes(inputFor(node)) <= inputBound;
2151
+ } catch {
2152
+ return false;
2153
+ }
2154
+ };
2010
2155
  for (let pass = 0; pass < passes && !this.closed && Date.now() < runDeadline; pass += 1) {
2011
- const leafCandidate = this.maintenance.createLeaf(this.maintenance.selectLeaf(raw, this.activeSources));
2012
- const leaf = leafCandidate ? this.maintenance.getNode(leafCandidate.nodeId) : void 0;
2156
+ const leaves = [];
2157
+ const seen = /* @__PURE__ */ new Set();
2158
+ while (leaves.length < this.maintenance.concurrencyLimit) {
2159
+ const rows = this.maintenance.selectLeaf(raw, this.activeSources);
2160
+ if (rows.length === 0) break;
2161
+ const created = this.maintenance.createLeaf(rows);
2162
+ const node = created ? this.maintenance.getNode(created.nodeId) : void 0;
2163
+ if (!node || seen.has(node.nodeId)) break;
2164
+ seen.add(node.nodeId);
2165
+ leaves.push(node);
2166
+ }
2013
2167
  const batch = [];
2014
2168
  const queued = /* @__PURE__ */ new Set();
2015
- if (leaf?.state === "pending") {
2169
+ for (const leaf of leaves) {
2170
+ if (leaf.state !== "pending") continue;
2016
2171
  const job = this.maintenance.jobForNode(leaf.nodeId);
2017
2172
  if (job && this.maintenance.isClaimable(job)) {
2018
2173
  batch.push({ job, node: leaf });
@@ -2033,9 +2188,8 @@ var LcmRuntime = class {
2033
2188
  if (job && this.maintenance.isClaimable(job)) await runJob(job, node);
2034
2189
  }
2035
2190
  }
2036
- if (!leaf && pending.length === 0 && children.length < fanIn) {
2037
- const upgrades = this.maintenance.selectUpgrades(sessionId, this.activeSources, 1);
2038
- const upgrade = upgrades[0];
2191
+ if (leaves.length === 0 && pending.length === 0 && children.length < fanIn) {
2192
+ const upgrade = this.maintenance.selectUpgrades(sessionId, this.activeSources, UPGRADE_CANDIDATES).find(fitsBound);
2039
2193
  if (!upgrade) break;
2040
2194
  const job = this.maintenance.reopen(upgrade.nodeId);
2041
2195
  await runJob(job, upgrade);
@@ -2066,71 +2220,105 @@ var LcmRuntime = class {
2066
2220
  payloadJson,
2067
2221
  parentEntryId: entry.parentId ?? null
2068
2222
  });
2069
- persistedEntries.set(`${entry.id}:${stored.payloadHash}`, stored);
2223
+ persistedEntries.set(entry.id, stored);
2070
2224
  }
2071
2225
  });
2072
2226
  this.invalidateFrontier();
2073
2227
  const firstKeptIndex = input.firstKeptEntryId ? input.branchEntries.findIndex((entry) => entry.id === input.firstKeptEntryId) : input.branchEntries.length;
2074
2228
  const sourceEntries = input.branchEntries.slice(0, firstKeptIndex < 0 ? input.branchEntries.length : firstKeptIndex);
2075
2229
  const selectedStored = sourceEntries.map((entry) => {
2076
- const payloadHash = hashLcmPayload(JSON.parse(canonicalLcmPayload(entry)));
2077
- const stored = persistedEntries.get(`${entry.id}:${payloadHash}`);
2230
+ const stored = persistedEntries.get(entry.id);
2078
2231
  if (!stored) throw new Error("compaction source was not persisted");
2079
2232
  return stored;
2080
2233
  });
2081
2234
  const activeSources = new Set(selectedStored.map((entry) => this.sourceKey(entry)));
2082
2235
  const frontier = this.maintenance.getFrontier(input.sessionId, activeSources);
2083
2236
  const coveredSources = new Set(frontier.flatMap((node) => node.sources.map((source) => this.sourceKey(source))));
2084
- const summary = renderAddressedFrontier(frontier, requestLines);
2237
+ const keptEntry = firstKeptIndex >= 0 ? input.branchEntries[firstKeptIndex] : void 0;
2238
+ const keptRole = keptEntry?.type === "message" ? keptEntry.message.role : void 0;
2239
+ const splitTurn = input.isSplitTurn ?? keptRole !== "user";
2240
+ const requestFor = (maxBytes) => splitTurn ? activeRequestBlock(sourceEntries, selectedStored, maxBytes) : "";
2085
2241
  const instructionDetails = instructions.ok && (instructions.requestLines.length > 0 || instructions.policy.preserveCount > 0) ? { instructionPolicy: instructions.policy } : void 0;
2086
- if (summary && selectedStored.every((entry) => coveredSources.has(this.sourceKey(entry)))) {
2087
- return {
2088
- summary,
2089
- firstKeptEntryId: input.firstKeptEntryId,
2090
- tokensBefore: input.tokensBefore,
2091
- source: "ready-frontier",
2092
- ...instructionDetails ? { details: instructionDetails } : {}
2093
- };
2242
+ const uncovered = selectedStored.flatMap((stored, index) => coveredSources.has(this.sourceKey(stored)) ? [] : [index]);
2243
+ if (uncovered.length === 0) {
2244
+ const summary = renderAddressedFrontier(frontier, requestLines, { lead: requestFor(FRONTIER_REQUEST_BYTES) });
2245
+ if (summary) {
2246
+ return {
2247
+ summary,
2248
+ firstKeptEntryId: input.firstKeptEntryId,
2249
+ tokensBefore: input.tokensBefore,
2250
+ source: "ready-frontier",
2251
+ ...instructionDetails ? { details: instructionDetails } : {}
2252
+ };
2253
+ }
2094
2254
  }
2095
2255
  if (selectedStored.length === 0) throw new Error("LCM emergency fallback has no persisted sources");
2096
- const payloads = sourceEntries.map((entry) => canonicalLcmPayload(entry)).join("\n");
2097
- const sources = selectedStored.map((stored) => ({ sessionId: stored.sessionId, entryId: stored.entryId, revision: stored.revision, payloadHash: stored.payloadHash }));
2098
- const fallback = emergencyReduce(payloads, this.options.lcmMaxOutputChars ?? 4096, sources, requestLines);
2099
- const leaf = this.maintenance.createLeaf(selectedStored);
2100
- if (!leaf) throw new Error("LCM emergency fallback node was not created");
2101
- const persisted = this.maintenance.getNode(leaf.nodeId);
2102
- if (persisted?.state === "ready") {
2103
- if (JSON.stringify(persisted.sources) !== JSON.stringify(sources)) throw new Error("LCM emergency fallback provenance mismatch");
2104
- if (!persisted.text) throw new Error("LCM emergency fallback text is missing");
2105
- return {
2106
- summary: persisted.text,
2107
- firstKeptEntryId: input.firstKeptEntryId,
2108
- tokensBefore: input.tokensBefore,
2109
- source: "emergency",
2110
- ...instructionDetails ? { details: instructionDetails } : {}
2111
- };
2256
+ const tailEntries = uncovered.length > 0 ? uncovered.map((index) => sourceEntries[index]) : sourceEntries;
2257
+ const tailStored = uncovered.length > 0 ? uncovered.map((index) => selectedStored[index]) : selectedStored;
2258
+ const outputBound = this.options.lcmMaxOutputChars ?? 4096;
2259
+ const nodeTextFor = (entries, stored) => {
2260
+ const digest = tailDigest(entries, stored, Math.min(TAIL_DIGEST_MAX_BYTES, outputBound));
2261
+ if (digest) return digest;
2262
+ const payloads = entries.map((entry) => canonicalLcmPayload(entry)).join("\n");
2263
+ return emergencyReduce(payloads, outputBound, stored.map((entry) => ({ sessionId: entry.sessionId, entryId: entry.entryId, revision: entry.revision, payloadHash: entry.payloadHash })), requestLines);
2264
+ };
2265
+ const fallback = nodeTextFor(tailEntries, tailStored);
2266
+ const compose = (nodeText) => {
2267
+ const partial = frontier.length > 0 ? renderAddressedFrontier(frontier, requestLines, { lead: requestFor(FRONTIER_REQUEST_BYTES), trail: nodeText }) : "";
2268
+ if (partial) return partial;
2269
+ const pointer = nodeText.includes(LCM_RECOVERY_POINTER) ? "" : LCM_RECOVERY_POINTER;
2270
+ const separator = utf8Bytes(BLOCK_SEPARATOR);
2271
+ const room = Math.max(0, outputBound - (pointer ? utf8Bytes(pointer) + separator : 0));
2272
+ const request = requestLines.length > 0 ? `[Compaction Request]
2273
+ ${requestLines.join("\n")}` : "";
2274
+ const head = [request, requestFor(Math.floor(room * ACTIVE_REQUEST_SHARE))].filter((block) => block.length > 0);
2275
+ const headBytes = head.length > 0 ? utf8Bytes(head.join(BLOCK_SEPARATOR)) + separator : 0;
2276
+ const tailRoom = Math.max(0, room - headBytes);
2277
+ const tail = tailDigest(tailEntries, tailStored, Math.min(TAIL_DIGEST_MAX_BYTES, tailRoom)) || clipUtf8(nodeText, tailRoom);
2278
+ const body = clipUtf8([...head, tail].join(BLOCK_SEPARATOR), room);
2279
+ return pointer ? `${body}${BLOCK_SEPARATOR}${pointer}` : body;
2280
+ };
2281
+ const leafCap = Math.max(1, this.options.maxLeafEntries ?? DEFAULT_LEAF_ENTRIES);
2282
+ const inputCap = this.options.lcmMaxInputChars ?? DEFAULT_LCM_MAX_INPUT_CHARS;
2283
+ const spans = [];
2284
+ let span = [];
2285
+ let spanChars = 0;
2286
+ for (let index = 0; index < tailStored.length; index += 1) {
2287
+ const size = tailStored[index].payloadJson.length;
2288
+ if (span.length > 0 && (span.length >= leafCap || spanChars + size > inputCap)) {
2289
+ spans.push(span);
2290
+ span = [];
2291
+ spanChars = 0;
2292
+ }
2293
+ span.push(index);
2294
+ spanChars += size;
2112
2295
  }
2113
- const job = this.maintenance.jobForNode(leaf.nodeId);
2114
- if (!job) throw new Error("LCM emergency fallback job was not created");
2115
- try {
2116
- const completed = this.maintenance.completeEmergency(this.maintenance.claimEmergency(job.jobId), fallback);
2117
- return {
2118
- summary: completed.text ?? fallback,
2119
- firstKeptEntryId: input.firstKeptEntryId,
2120
- tokensBefore: input.tokensBefore,
2121
- source: "emergency",
2122
- ...instructionDetails ? { details: instructionDetails } : {}
2123
- };
2124
- } catch (error) {
2125
- if (!isLcmRejection(error)) throw error;
2126
- return {
2127
- summary: fallback,
2128
- firstKeptEntryId: input.firstKeptEntryId,
2129
- tokensBefore: input.tokensBefore,
2130
- source: "emergency",
2131
- ...instructionDetails ? { details: instructionDetails } : {}
2132
- };
2296
+ if (span.length > 0) spans.push(span);
2297
+ for (const indices of spans) {
2298
+ const stored = indices.map((index) => tailStored[index]);
2299
+ const entries = indices.map((index) => tailEntries[index]);
2300
+ const leaf = this.maintenance.createLeaf(stored);
2301
+ if (!leaf) throw new Error("LCM emergency fallback node was not created");
2302
+ const persisted = this.maintenance.getNode(leaf.nodeId);
2303
+ if (persisted?.state === "ready") {
2304
+ if (!persisted.text) throw new Error("LCM emergency fallback text is missing");
2305
+ continue;
2306
+ }
2307
+ const job = this.maintenance.jobForNode(leaf.nodeId);
2308
+ if (!job) throw new Error("LCM emergency fallback job was not created");
2309
+ try {
2310
+ this.maintenance.completeEmergency(this.maintenance.claimEmergency(job.jobId), nodeTextFor(entries, stored));
2311
+ } catch (error) {
2312
+ if (!isLcmRejection(error)) throw error;
2313
+ }
2133
2314
  }
2315
+ return {
2316
+ summary: compose(fallback),
2317
+ firstKeptEntryId: input.firstKeptEntryId,
2318
+ tokensBefore: input.tokensBefore,
2319
+ source: "emergency",
2320
+ ...instructionDetails ? { details: instructionDetails } : {}
2321
+ };
2134
2322
  }
2135
2323
  report() {
2136
2324
  const day = new Date(Date.now()).toISOString().slice(0, 10);