@cortexkit/pi-magic-context 0.31.5 → 0.32.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/dist/index.js CHANGED
@@ -15166,7 +15166,7 @@ async function resolvePromptContext(client, sessionId) {
15166
15166
  var PROMPT_CONTEXT_MESSAGE_LIMIT = 50;
15167
15167
 
15168
15168
  // src/index.ts
15169
- import { createRequire as createRequire3 } from "node:module";
15169
+ import { createRequire as createRequire4 } from "node:module";
15170
15170
  import { join as join13, resolve as resolve3 } from "node:path";
15171
15171
 
15172
15172
  // ../plugin/src/config/agent-disable.ts
@@ -148774,6 +148774,12 @@ function parseJsonRecord(value) {
148774
148774
  return null;
148775
148775
  }
148776
148776
  }
148777
+ function isRawCompactionSummaryInfo(info) {
148778
+ if (info === null || typeof info !== "object" || Array.isArray(info))
148779
+ return false;
148780
+ const candidate = info;
148781
+ return candidate.summary === true && candidate.finish === "stop";
148782
+ }
148777
148783
  function parseJsonUnknown(value) {
148778
148784
  try {
148779
148785
  return JSON.parse(value);
@@ -148796,7 +148802,7 @@ function attachRawPartVersion(value, timeUpdated) {
148796
148802
  return value;
148797
148803
  }
148798
148804
  function readRawSessionMessagesFromDb(db, sessionId) {
148799
- const messageRows = db.prepare("SELECT id, data, time_updated FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
148805
+ const messageRows = db.prepare("SELECT id, data, time_created, time_updated FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
148800
148806
  const partRows = db.prepare("SELECT message_id, data, time_updated FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
148801
148807
  const partsByMessageId = new Map;
148802
148808
  for (const part of partRows) {
@@ -148804,10 +148810,7 @@ function readRawSessionMessagesFromDb(db, sessionId) {
148804
148810
  list.push(attachRawPartVersion(parseJsonUnknown(part.data), part.time_updated));
148805
148811
  partsByMessageId.set(part.message_id, list);
148806
148812
  }
148807
- const filtered = messageRows.filter((row) => {
148808
- const info = parseJsonRecord(row.data);
148809
- return !(info?.summary === true && info?.finish === "stop");
148810
- });
148813
+ const filtered = messageRows.filter((row) => !isRawCompactionSummaryInfo(parseJsonRecord(row.data)));
148811
148814
  return filtered.flatMap((row, index) => {
148812
148815
  const info = parseJsonRecord(row.data);
148813
148816
  if (!info)
@@ -148818,6 +148821,7 @@ function readRawSessionMessagesFromDb(db, sessionId) {
148818
148821
  id: row.id,
148819
148822
  role,
148820
148823
  parts: partsByMessageId.get(row.id) ?? [],
148824
+ createdAt: row.time_created ?? null,
148821
148825
  version: row.time_updated ?? null
148822
148826
  };
148823
148827
  });
@@ -148832,7 +148836,7 @@ function readRawSessionTailFromDb(db, sessionId, baseOrdinal, anchorMessageId) {
148832
148836
  const anchorInfo = parseJsonRecord(anchorRow.data ?? "");
148833
148837
  if (anchorInfo?.summary === true && anchorInfo?.finish === "stop")
148834
148838
  return null;
148835
- const messageRows = db.prepare(`SELECT id, data, time_updated FROM message
148839
+ const messageRows = db.prepare(`SELECT id, data, time_created, time_updated FROM message
148836
148840
  WHERE session_id = ?
148837
148841
  AND (time_created > ? OR (time_created = ? AND id >= ?))
148838
148842
  ORDER BY time_created ASC, id ASC`).all(sessionId, anchorRow.time_created, anchorRow.time_created, anchorRow.id).filter(isRawMessageRow);
@@ -148868,6 +148872,7 @@ function readRawSessionTailFromDb(db, sessionId, baseOrdinal, anchorMessageId) {
148868
148872
  id: row.id,
148869
148873
  role: typeof info.role === "string" ? info.role : "unknown",
148870
148874
  parts: partsByMessageId.get(row.id) ?? [],
148875
+ createdAt: row.time_created ?? null,
148871
148876
  version: row.time_updated ?? null
148872
148877
  });
148873
148878
  ord += 1;
@@ -149630,6 +149635,7 @@ var reconciliationScheduledSessions = new Set;
149630
149635
  var sessionLocks = new Map;
149631
149636
  var incrementalTimers = new Map;
149632
149637
  var pendingIncrementalKeys = new Set;
149638
+ var completedIncrementalKeys = new Set;
149633
149639
  function defer(fn) {
149634
149640
  const immediate = globalThis.setImmediate;
149635
149641
  if (typeof immediate === "function") {
@@ -149697,9 +149703,9 @@ function scheduleReconciliation(db, sessionId, readMessages) {
149697
149703
  });
149698
149704
  });
149699
149705
  }
149700
- function scheduleIncrementalIndex(db, sessionId, messageId, readSingleMessage) {
149706
+ function scheduleIncrementalIndex(db, sessionId, messageId, messageSource) {
149701
149707
  const key = `${sessionId}\x00${messageId}`;
149702
- if (incrementalTimers.has(key) || pendingIncrementalKeys.has(key)) {
149708
+ if (completedIncrementalKeys.has(key) || incrementalTimers.has(key) || pendingIncrementalKeys.has(key)) {
149703
149709
  return;
149704
149710
  }
149705
149711
  const timer = setTimeout(() => {
@@ -149707,12 +149713,12 @@ function scheduleIncrementalIndex(db, sessionId, messageId, readSingleMessage) {
149707
149713
  pendingIncrementalKeys.add(key);
149708
149714
  let attemptedOrdinal = null;
149709
149715
  runWithSessionLock(sessionId, () => {
149710
- const message = readSingleMessage(sessionId, messageId);
149711
- if (!message) {
149716
+ const message = typeof messageSource === "function" ? messageSource(sessionId, messageId) : messageSource;
149717
+ if (!message)
149712
149718
  return;
149713
- }
149714
149719
  attemptedOrdinal = message.ordinal;
149715
149720
  indexSingleMessage(db, sessionId, message);
149721
+ completedIncrementalKeys.add(key);
149716
149722
  }).catch((error) => {
149717
149723
  markMessageIndexDirty(db, sessionId, attemptedOrdinal ?? getLastIndexedOrdinal(db, sessionId) + 1);
149718
149724
  reconciledSessions.delete(sessionId);
@@ -149739,6 +149745,10 @@ function clearSessionTracking(sessionId) {
149739
149745
  pendingIncrementalKeys.delete(key);
149740
149746
  }
149741
149747
  }
149748
+ for (const key of completedIncrementalKeys) {
149749
+ if (key.startsWith(prefix))
149750
+ completedIncrementalKeys.delete(key);
149751
+ }
149742
149752
  }
149743
149753
 
149744
149754
  // ../plugin/src/features/magic-context/overflow-detection.ts
@@ -152561,6 +152571,160 @@ function readProjectDocsCanonical(projectDirectory) {
152561
152571
  });
152562
152572
  return { renderedBlock, canonicalHash };
152563
152573
  }
152574
+ // ../plugin/src/features/magic-context/storage-clone.ts
152575
+ function runImmediate(db, body) {
152576
+ db.exec("BEGIN IMMEDIATE");
152577
+ let committed = false;
152578
+ try {
152579
+ const result = body();
152580
+ db.exec("COMMIT");
152581
+ committed = true;
152582
+ return result;
152583
+ } finally {
152584
+ if (!committed) {
152585
+ try {
152586
+ db.exec("ROLLBACK");
152587
+ } catch {}
152588
+ }
152589
+ }
152590
+ }
152591
+ function countRows(db, table, sessionId) {
152592
+ const row = db.prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE session_id = ?`).get(sessionId);
152593
+ return typeof row?.count === "number" ? row.count : 0;
152594
+ }
152595
+ function filterIdBlob(raw, includeMessageId) {
152596
+ if (!raw)
152597
+ return "";
152598
+ try {
152599
+ const value = JSON.parse(raw);
152600
+ if (!Array.isArray(value))
152601
+ return "";
152602
+ const filtered = value.filter((id) => typeof id === "string" && includeMessageId(id));
152603
+ return filtered.length > 0 ? JSON.stringify(filtered) : "";
152604
+ } catch {
152605
+ return "";
152606
+ }
152607
+ }
152608
+ function clampWatermark(value, maxCopiedTag) {
152609
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
152610
+ return 0;
152611
+ return Math.min(Math.floor(value), maxCopiedTag);
152612
+ }
152613
+ function copySessionStateForClone(db, sourceSessionId, destinationSessionId, filter) {
152614
+ return runImmediate(db, () => {
152615
+ if (countRows(db, "compartments", destinationSessionId) > 0 || countRows(db, "tags", destinationSessionId) > 0) {
152616
+ return {
152617
+ kind: "destination-not-empty",
152618
+ compartmentsCopied: 0,
152619
+ tagsCopied: 0,
152620
+ pendingOpsCopied: 0,
152621
+ pendingMarkerMigrated: false
152622
+ };
152623
+ }
152624
+ const sourceCompartments = db.prepare(`SELECT sequence, start_message, end_message, start_message_id, end_message_id,
152625
+ title, content, p1, p2, p3, p4, importance, episode_type, legacy,
152626
+ created_at, harness
152627
+ FROM compartments WHERE session_id = ? ORDER BY sequence ASC`).all(sourceSessionId);
152628
+ const insertCompartment = db.prepare(`INSERT INTO compartments
152629
+ (session_id, sequence, start_message, end_message, start_message_id,
152630
+ end_message_id, title, content, p1, p2, p3, p4, importance,
152631
+ episode_type, legacy, created_at, harness)
152632
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
152633
+ const copiedCompartments = [];
152634
+ for (const row of sourceCompartments) {
152635
+ const startMessage = filter.resolveBoundaryOrdinal(row.start_message_id);
152636
+ const endMessage = filter.resolveBoundaryOrdinal(row.end_message_id);
152637
+ if (startMessage === undefined || endMessage === undefined)
152638
+ continue;
152639
+ insertCompartment.run(destinationSessionId, row.sequence, startMessage, endMessage, row.start_message_id, row.end_message_id, row.title, row.content, row.p1, row.p2, row.p3, row.p4, row.importance, row.episode_type, row.legacy, row.created_at, row.harness);
152640
+ copiedCompartments.push({
152641
+ sequence: row.sequence,
152642
+ startMessage,
152643
+ endMessage,
152644
+ startMessageId: row.start_message_id,
152645
+ endMessageId: row.end_message_id
152646
+ });
152647
+ }
152648
+ const sourceTags = db.prepare(`SELECT message_id, type, status, byte_size, tag_number, harness,
152649
+ entry_fingerprint, token_count, input_token_count,
152650
+ reasoning_token_count, reasoning_byte_size, drop_mode, tool_name,
152651
+ input_byte_size, caveman_depth, tool_owner_message_id
152652
+ FROM tags WHERE session_id = ? ORDER BY tag_number ASC`).all(sourceSessionId);
152653
+ const insertTag = db.prepare(`INSERT INTO tags
152654
+ (session_id, message_id, type, status, byte_size, tag_number, harness,
152655
+ entry_fingerprint, token_count, input_token_count, reasoning_token_count,
152656
+ reasoning_byte_size, drop_mode, tool_name, input_byte_size,
152657
+ caveman_depth, tool_owner_message_id)
152658
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
152659
+ const copiedTagNumbers = [];
152660
+ for (const row of sourceTags) {
152661
+ if (!filter.includeTag({
152662
+ messageId: row.message_id,
152663
+ type: row.type,
152664
+ tagNumber: row.tag_number,
152665
+ toolOwnerMessageId: row.tool_owner_message_id
152666
+ })) {
152667
+ continue;
152668
+ }
152669
+ insertTag.run(destinationSessionId, row.message_id, row.type, row.status, row.byte_size, row.tag_number, row.harness, row.entry_fingerprint, row.token_count, row.input_token_count, row.reasoning_token_count, row.reasoning_byte_size, row.drop_mode, row.tool_name, row.input_byte_size, row.caveman_depth, row.tool_owner_message_id);
152670
+ copiedTagNumbers.push(row.tag_number);
152671
+ }
152672
+ if (copiedTagNumbers.length > 0) {
152673
+ const placeholders2 = copiedTagNumbers.map(() => "?").join(", ");
152674
+ db.prepare(`INSERT INTO source_contents (tag_id, session_id, content, created_at, harness)
152675
+ SELECT tag_id, ?, content, created_at, harness
152676
+ FROM source_contents
152677
+ WHERE session_id = ? AND tag_id IN (${placeholders2})`).run(destinationSessionId, sourceSessionId, ...copiedTagNumbers);
152678
+ db.prepare(`INSERT INTO pending_ops (session_id, tag_id, operation, queued_at, harness)
152679
+ SELECT ?, tag_id, operation, queued_at, harness
152680
+ FROM pending_ops
152681
+ WHERE session_id = ? AND tag_id IN (${placeholders2})`).run(destinationSessionId, sourceSessionId, ...copiedTagNumbers);
152682
+ }
152683
+ const meta = db.prepare(`SELECT cleared_reasoning_through_tag, tool_reclaim_watermark,
152684
+ pi_stable_id_scheme, stripped_placeholder_ids,
152685
+ stale_reduce_stripped_ids, processed_image_stripped_ids,
152686
+ pending_pi_compaction_marker_state, last_todo_state,
152687
+ todo_synthetic_call_id, todo_synthetic_anchor_message_id,
152688
+ todo_synthetic_state_json
152689
+ FROM session_meta WHERE session_id = ?`).get(sourceSessionId);
152690
+ const maxCopiedTag = copiedTagNumbers.reduce((max, value) => Math.max(max, value), 0);
152691
+ const pendingMarker = filter.selectPendingPiMarker(meta?.pending_pi_compaction_marker_state ?? null, copiedCompartments);
152692
+ const todoAnchor = meta?.todo_synthetic_anchor_message_id ?? "";
152693
+ const migrateTodo = todoAnchor.length > 0 && filter.includeMessageId(todoAnchor);
152694
+ db.prepare(`INSERT INTO session_meta
152695
+ (session_id, harness, counter, cleared_reasoning_through_tag,
152696
+ tool_reclaim_watermark, pi_stable_id_scheme, stripped_placeholder_ids,
152697
+ stale_reduce_stripped_ids, processed_image_stripped_ids,
152698
+ pending_pi_compaction_marker_state, last_todo_state,
152699
+ todo_synthetic_call_id, todo_synthetic_anchor_message_id,
152700
+ todo_synthetic_state_json)
152701
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
152702
+ ON CONFLICT(session_id) DO UPDATE SET
152703
+ harness = excluded.harness,
152704
+ counter = excluded.counter,
152705
+ cleared_reasoning_through_tag = excluded.cleared_reasoning_through_tag,
152706
+ tool_reclaim_watermark = excluded.tool_reclaim_watermark,
152707
+ pi_stable_id_scheme = excluded.pi_stable_id_scheme,
152708
+ stripped_placeholder_ids = excluded.stripped_placeholder_ids,
152709
+ stale_reduce_stripped_ids = excluded.stale_reduce_stripped_ids,
152710
+ processed_image_stripped_ids = excluded.processed_image_stripped_ids,
152711
+ pending_pi_compaction_marker_state = excluded.pending_pi_compaction_marker_state,
152712
+ last_todo_state = excluded.last_todo_state,
152713
+ todo_synthetic_call_id = excluded.todo_synthetic_call_id,
152714
+ todo_synthetic_anchor_message_id = excluded.todo_synthetic_anchor_message_id,
152715
+ todo_synthetic_state_json = excluded.todo_synthetic_state_json,
152716
+ cached_m0_bytes = NULL,
152717
+ cached_m1_bytes = NULL`).run(destinationSessionId, getHarness(), maxCopiedTag, clampWatermark(meta?.cleared_reasoning_through_tag ?? null, maxCopiedTag), clampWatermark(meta?.tool_reclaim_watermark ?? null, maxCopiedTag), typeof meta?.pi_stable_id_scheme === "number" && Number.isFinite(meta.pi_stable_id_scheme) ? meta.pi_stable_id_scheme : null, filterIdBlob(meta?.stripped_placeholder_ids ?? null, filter.includeMessageId), filterIdBlob(meta?.stale_reduce_stripped_ids ?? null, filter.includeMessageId), filterIdBlob(meta?.processed_image_stripped_ids ?? null, filter.includeMessageId), pendingMarker, migrateTodo ? meta?.last_todo_state ?? "" : "", migrateTodo ? meta?.todo_synthetic_call_id ?? "" : "", migrateTodo ? todoAnchor : "", migrateTodo ? meta?.todo_synthetic_state_json ?? "" : "");
152718
+ const pendingOpsRow = db.prepare("SELECT COUNT(*) AS count FROM pending_ops WHERE session_id = ?").get(destinationSessionId);
152719
+ return {
152720
+ kind: "migrated",
152721
+ compartmentsCopied: copiedCompartments.length,
152722
+ tagsCopied: copiedTagNumbers.length,
152723
+ pendingOpsCopied: typeof pendingOpsRow?.count === "number" ? pendingOpsRow.count : 0,
152724
+ pendingMarkerMigrated: pendingMarker !== null
152725
+ };
152726
+ });
152727
+ }
152564
152728
  // ../plugin/src/features/magic-context/storage-db.ts
152565
152729
  import { chmodSync, copyFileSync, cpSync, existsSync as existsSync7, mkdirSync as mkdirSync4, mkdtempSync } from "node:fs";
152566
152730
  init_data_path();
@@ -155070,6 +155234,36 @@ function parseStrippedBlob(raw) {
155070
155234
  } catch {}
155071
155235
  return [];
155072
155236
  }
155237
+ function getProcessedImageStrippedIds(db, sessionId) {
155238
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
155239
+ return new Set(parseStrippedBlob(row?.processed_image_stripped_ids));
155240
+ }
155241
+ function addProcessedImageStrippedIds(db, sessionId, ids) {
155242
+ const add = [...ids];
155243
+ if (add.length === 0)
155244
+ return true;
155245
+ ensureSessionMetaRow(db, sessionId);
155246
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
155247
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
155248
+ const rawStored = row ? row.processed_image_stripped_ids ?? null : null;
155249
+ const current = new Set(parseStrippedBlob(rawStored));
155250
+ let changed = false;
155251
+ for (const id of add) {
155252
+ if (!current.has(id)) {
155253
+ current.add(id);
155254
+ changed = true;
155255
+ }
155256
+ }
155257
+ if (!changed)
155258
+ return true;
155259
+ const nextBlob = JSON.stringify([...current]);
155260
+ const result = db.prepare("UPDATE session_meta SET processed_image_stripped_ids = ? WHERE session_id = ? AND processed_image_stripped_ids IS ?").run(nextBlob, sessionId, rawStored);
155261
+ if (result.changes > 0)
155262
+ return true;
155263
+ }
155264
+ sessionLog(sessionId, `processed_image_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
155265
+ return false;
155266
+ }
155073
155267
  function isPendingCompactionMarker(value) {
155074
155268
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
155075
155269
  }
@@ -155792,6 +155986,7 @@ var getTagNumbersByMessageIdStatements = new WeakMap;
155792
155986
  var deleteTagsByMessageIdStatements = new WeakMap;
155793
155987
  var getMaxTagNumberBySessionStatements = new WeakMap;
155794
155988
  var getTagNumberByMessageIdStatements = new WeakMap;
155989
+ var hasPiFallbackMessageTagStatements = new WeakMap;
155795
155990
  function getInsertTagStatement(db) {
155796
155991
  let stmt = insertTagStatements.get(db);
155797
155992
  if (!stmt) {
@@ -155935,6 +156130,18 @@ function getUpdateTagInputTokenCountStatement(db) {
155935
156130
  function updateTagTokenCount(db, sessionId, tagNumber, newTokenCount) {
155936
156131
  getUpdateTagTokenCountStatement(db).run(newTokenCount, sessionId, tagNumber);
155937
156132
  }
156133
+ function getPersistedToolTagAccounting(db, sessionId, tagNumber) {
156134
+ const row = db.prepare(`SELECT byte_size AS byteSize,
156135
+ token_count AS tokenCount,
156136
+ input_byte_size AS inputByteSize,
156137
+ input_token_count AS inputTokenCount
156138
+ FROM tags
156139
+ WHERE session_id = ? AND tag_number = ? AND type = 'tool'`).get(sessionId, tagNumber);
156140
+ if (!row || typeof row.byteSize !== "number" || row.tokenCount !== null && typeof row.tokenCount !== "number" || typeof row.inputByteSize !== "number" || row.inputTokenCount !== null && typeof row.inputTokenCount !== "number") {
156141
+ return null;
156142
+ }
156143
+ return row;
156144
+ }
155938
156145
  function getAllStatusTagTokenTotalsFlat(db, sessionId, floor = 0) {
155939
156146
  const rows = floor > 0 ? db.prepare(`SELECT type, message_id, tool_owner_message_id, token_count, input_token_count, reasoning_token_count
155940
156147
  FROM tags
@@ -156036,6 +156243,19 @@ function updateTagDropMode(db, sessionId, tagNumber, dropMode) {
156036
156243
  function updateCavemanDepth(db, sessionId, tagNumber, depth) {
156037
156244
  db.prepare("UPDATE tags SET caveman_depth = ? WHERE session_id = ? AND tag_number = ?").run(depth, sessionId, tagNumber);
156038
156245
  }
156246
+ function hasPiFallbackMessageTags(db, sessionId) {
156247
+ let statement = hasPiFallbackMessageTagStatements.get(db);
156248
+ if (!statement) {
156249
+ statement = db.prepare(`SELECT 1
156250
+ FROM tags
156251
+ WHERE session_id = ?
156252
+ AND type = 'message'
156253
+ AND message_id LIKE 'pi-msg-%'
156254
+ LIMIT 1`);
156255
+ hasPiFallbackMessageTagStatements.set(db, statement);
156256
+ }
156257
+ return statement.get(sessionId) != null;
156258
+ }
156039
156259
  function findAdoptableFallbackTags(db, sessionId, entryFingerprint) {
156040
156260
  const rows = db.prepare(`SELECT tag_number AS tagNumber, message_id AS messageId
156041
156261
  FROM tags
@@ -156206,27 +156426,24 @@ function adoptPiFallbackToolOwnerTag(db, sessionId, tagNumber, callId, oldOwnerM
156206
156426
  }
156207
156427
  const existing = getPiFallbackToolFoldTagRowByOwner(db, sessionId, callId, newOwnerMessageId);
156208
156428
  if (existing === null) {
156209
- const result2 = db.prepare(`UPDATE tags
156429
+ const result = db.prepare(`UPDATE tags
156210
156430
  SET tool_owner_message_id = ?
156211
156431
  WHERE session_id = ?
156212
156432
  AND tag_number = ?
156213
156433
  AND type = 'tool'
156214
156434
  AND message_id = ?
156215
156435
  AND tool_owner_message_id = ?`).run(newOwnerMessageId, sessionId, tagNumber, callId, oldOwnerMessageId);
156216
- return (result2.changes ?? 0) === 1 ? { action: "rekeyed", tagNumber } : { action: "skipped" };
156436
+ return (result.changes ?? 0) === 1 ? { action: "rekeyed", tagNumber } : { action: "skipped" };
156217
156437
  }
156218
156438
  if (existing.tagNumber === tagNumber) {
156219
156439
  return { action: "skipped" };
156220
156440
  }
156221
- foldDuplicateIntoSurvivor(db, sessionId, survivor, existing);
156222
- const result = db.prepare(`UPDATE tags
156223
- SET tool_owner_message_id = ?
156224
- WHERE session_id = ?
156225
- AND tag_number = ?
156226
- AND type = 'tool'
156227
- AND message_id = ?
156228
- AND tool_owner_message_id = ?`).run(newOwnerMessageId, sessionId, tagNumber, callId, oldOwnerMessageId);
156229
- return (result.changes ?? 0) === 1 ? { action: "folded", tagNumber, deletedTagNumbers: [existing.tagNumber] } : { action: "skipped" };
156441
+ foldDuplicateIntoSurvivor(db, sessionId, existing, survivor);
156442
+ return {
156443
+ action: "folded",
156444
+ tagNumber: existing.tagNumber,
156445
+ deletedTagNumbers: [tagNumber]
156446
+ };
156230
156447
  }
156231
156448
  function adoptPiFallbackMessageTag(db, sessionId, tagNumber, oldFallbackMessageId, newRealMessageId) {
156232
156449
  const survivor = getPiFallbackFoldTagRowByNumber(db, sessionId, tagNumber);
@@ -156235,26 +156452,28 @@ function adoptPiFallbackMessageTag(db, sessionId, tagNumber, oldFallbackMessageI
156235
156452
  }
156236
156453
  const duplicates = getPiFallbackMessageFoldTagRowsByMessageId(db, sessionId, newRealMessageId).filter((row) => row.tagNumber !== tagNumber);
156237
156454
  if (duplicates.length === 0) {
156238
- const result2 = db.prepare(`UPDATE tags
156455
+ const result = db.prepare(`UPDATE tags
156239
156456
  SET message_id = ?
156240
156457
  WHERE session_id = ?
156241
156458
  AND tag_number = ?
156242
156459
  AND type = 'message'
156243
156460
  AND message_id = ?`).run(newRealMessageId, sessionId, tagNumber, oldFallbackMessageId);
156244
- return (result2.changes ?? 0) === 1 ? { action: "rekeyed", tagNumber } : { action: "skipped" };
156461
+ return (result.changes ?? 0) === 1 ? { action: "rekeyed", tagNumber } : { action: "skipped" };
156245
156462
  }
156246
- const deletedTagNumbers = [];
156247
- for (const duplicate of duplicates) {
156248
- foldDuplicateIntoSurvivor(db, sessionId, survivor, duplicate);
156463
+ const realSurvivor = duplicates[0];
156464
+ if (!realSurvivor)
156465
+ return { action: "skipped" };
156466
+ const deletedTagNumbers = [tagNumber];
156467
+ foldDuplicateIntoSurvivor(db, sessionId, realSurvivor, survivor);
156468
+ for (const duplicate of duplicates.slice(1)) {
156469
+ foldDuplicateIntoSurvivor(db, sessionId, realSurvivor, duplicate);
156249
156470
  deletedTagNumbers.push(duplicate.tagNumber);
156250
156471
  }
156251
- const result = db.prepare(`UPDATE tags
156252
- SET message_id = ?
156253
- WHERE session_id = ?
156254
- AND tag_number = ?
156255
- AND type = 'message'
156256
- AND message_id = ?`).run(newRealMessageId, sessionId, tagNumber, oldFallbackMessageId);
156257
- return (result.changes ?? 0) === 1 ? { action: "folded", tagNumber, deletedTagNumbers } : { action: "skipped" };
156472
+ return {
156473
+ action: "folded",
156474
+ tagNumber: realSurvivor.tagNumber,
156475
+ deletedTagNumbers
156476
+ };
156258
156477
  }
156259
156478
  var getOwnerScopedToolTagNumbersStatements = new WeakMap;
156260
156479
  function getMaxTagNumberBySession(db, sessionId) {
@@ -156326,10 +156545,40 @@ function getActiveTagsBySessionStatement(db) {
156326
156545
  }
156327
156546
  return stmt;
156328
156547
  }
156548
+ function getMaxDroppedTagNumberStatement(db) {
156549
+ let stmt = getMaxDroppedTagNumberStatements.get(db);
156550
+ if (!stmt) {
156551
+ stmt = db.prepare("SELECT COALESCE(MAX(tag_number), 0) AS max_tag_number FROM tags WHERE session_id = ? AND status = 'dropped'");
156552
+ getMaxDroppedTagNumberStatements.set(db, stmt);
156553
+ }
156554
+ return stmt;
156555
+ }
156329
156556
  function getActiveTagsBySession(db, sessionId) {
156330
156557
  const rows = getActiveTagsBySessionStatement(db).all(sessionId).filter(isTagRow);
156331
156558
  return rows.map(toTagEntry);
156332
156559
  }
156560
+ function getTagsForPendingOperations(db, sessionId, pendingTagNumbers, protectedTags, recentToolWindow) {
156561
+ const byNumber = new Map;
156562
+ for (const tag of getTagsByNumbers(db, sessionId, pendingTagNumbers)) {
156563
+ byNumber.set(tag.tagNumber, tag);
156564
+ }
156565
+ const addRows = (sql, limit) => {
156566
+ if (limit <= 0)
156567
+ return;
156568
+ const rows = db.prepare(sql).all(sessionId, limit).filter(isTagRow);
156569
+ for (const row of rows) {
156570
+ const tag = toTagEntry(row);
156571
+ byNumber.set(tag.tagNumber, tag);
156572
+ }
156573
+ };
156574
+ addRows(`SELECT ${TAG_SELECT_COLUMNS} FROM tags
156575
+ WHERE session_id = ? AND status = 'active'
156576
+ ORDER BY tag_number DESC, id DESC LIMIT ?`, protectedTags);
156577
+ addRows(`SELECT ${TAG_SELECT_COLUMNS} FROM tags
156578
+ WHERE session_id = ? AND type = 'tool'
156579
+ ORDER BY tag_number DESC, id DESC LIMIT ?`, recentToolWindow);
156580
+ return [...byNumber.values()].sort((left, right) => left.tagNumber - right.tagNumber);
156581
+ }
156333
156582
  function getTagsByNumbers(db, sessionId, tagNumbers) {
156334
156583
  if (tagNumbers.length === 0)
156335
156584
  return [];
@@ -156344,6 +156593,10 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
156344
156593
  const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders3}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
156345
156594
  return rows.map(toTagEntry);
156346
156595
  }
156596
+ function getMaxDroppedTagNumber(db, sessionId) {
156597
+ const row = getMaxDroppedTagNumberStatement(db).get(sessionId);
156598
+ return isMaxTagNumberRow(row) ? row.max_tag_number : 0;
156599
+ }
156347
156600
  var getToolTagNumberByOwnerStatements = new WeakMap;
156348
156601
  var getNullOwnerToolTagStatements = new WeakMap;
156349
156602
  var adoptNullOwnerToolTagStatements = new WeakMap;
@@ -157256,6 +157509,461 @@ function shouldKeepSubagents() {
157256
157509
  // src/index.ts
157257
157510
  init_logger();
157258
157511
 
157512
+ // src/clone-inheritance.ts
157513
+ import { readFile } from "node:fs/promises";
157514
+ init_logger();
157515
+
157516
+ // src/read-session-pi.ts
157517
+ var SYNTH_USER_ID_PREFIX = "synth-user-";
157518
+ function resolvePiStableId(msg, index, entryIds, entryIdByRef) {
157519
+ if (!msg || typeof msg !== "object")
157520
+ return;
157521
+ const byRef = entryIdByRef?.get(msg);
157522
+ if (typeof byRef === "string" && byRef.length > 0)
157523
+ return byRef;
157524
+ const positional = entryIds?.[index];
157525
+ if (typeof positional === "string" && positional.length > 0)
157526
+ return positional;
157527
+ const m = msg;
157528
+ const role = m.role ?? "unknown";
157529
+ return typeof m.timestamp === "number" ? `pi-msg-${index}-${m.timestamp}-${role}` : `pi-msg-${index}-${role}`;
157530
+ }
157531
+ function isMidTurnPi(event, _sessionId, branchEntries) {
157532
+ const messages = event?.messages;
157533
+ if (!Array.isArray(messages))
157534
+ return false;
157535
+ let latestAssistantIndex = -1;
157536
+ let latestAssistant = null;
157537
+ for (let i = messages.length - 1;i >= 0; i--) {
157538
+ const msg = messages[i];
157539
+ if (msg !== null && typeof msg === "object") {
157540
+ const record = msg;
157541
+ if (record.role === "assistant") {
157542
+ latestAssistantIndex = i;
157543
+ latestAssistant = record;
157544
+ break;
157545
+ }
157546
+ }
157547
+ }
157548
+ if (latestAssistant === null)
157549
+ return false;
157550
+ if (hasRealUserAfter(messages, latestAssistantIndex, branchEntries))
157551
+ return false;
157552
+ if (latestAssistant.stopReason === "toolUse")
157553
+ return true;
157554
+ const toolCallIds = getToolCallIds(latestAssistant.content);
157555
+ if (toolCallIds.size === 0)
157556
+ return false;
157557
+ const pairedToolResultIds = new Set;
157558
+ for (const msg of messages.slice(latestAssistantIndex + 1)) {
157559
+ if (msg === null || typeof msg !== "object")
157560
+ continue;
157561
+ const record = msg;
157562
+ if (record.role !== "toolResult")
157563
+ continue;
157564
+ if (typeof record.toolCallId === "string") {
157565
+ pairedToolResultIds.add(record.toolCallId);
157566
+ }
157567
+ }
157568
+ for (const id of toolCallIds) {
157569
+ if (!pairedToolResultIds.has(id))
157570
+ return true;
157571
+ }
157572
+ return false;
157573
+ }
157574
+ function hasRealUserAfter(messages, latestAssistantIndex, branchEntries) {
157575
+ if (!branchEntries)
157576
+ return false;
157577
+ const genuineUserMessages = new Set;
157578
+ for (const entry of branchEntries) {
157579
+ if (entry === null || typeof entry !== "object")
157580
+ continue;
157581
+ const record = entry;
157582
+ if (record.type !== "message")
157583
+ continue;
157584
+ const message = record.message;
157585
+ if (message === null || typeof message !== "object")
157586
+ continue;
157587
+ if (message.role === "user") {
157588
+ genuineUserMessages.add(message);
157589
+ }
157590
+ }
157591
+ for (const msg of messages.slice(latestAssistantIndex + 1)) {
157592
+ if (msg !== null && typeof msg === "object" && genuineUserMessages.has(msg)) {
157593
+ return true;
157594
+ }
157595
+ }
157596
+ return false;
157597
+ }
157598
+ function getToolCallIds(content) {
157599
+ const ids = new Set;
157600
+ if (!Array.isArray(content))
157601
+ return ids;
157602
+ for (const item of content) {
157603
+ if (item === null || typeof item !== "object")
157604
+ continue;
157605
+ const record = item;
157606
+ if (record.type === "toolCall" && typeof record.id === "string") {
157607
+ ids.add(record.id);
157608
+ }
157609
+ }
157610
+ return ids;
157611
+ }
157612
+ function readPiSessionMessages(ctx) {
157613
+ const sm = ctx.sessionManager;
157614
+ if (sm === undefined)
157615
+ return [];
157616
+ const getBranch = sm.getBranch;
157617
+ if (typeof getBranch !== "function")
157618
+ return [];
157619
+ let entries;
157620
+ try {
157621
+ entries = getBranch.call(sm);
157622
+ } catch {
157623
+ return [];
157624
+ }
157625
+ if (!Array.isArray(entries))
157626
+ return [];
157627
+ return convertEntriesToRawMessages(entries);
157628
+ }
157629
+ function findLastModelKeyFromBranch(entries) {
157630
+ if (!Array.isArray(entries))
157631
+ return;
157632
+ for (let i = entries.length - 1;i >= 0; i--) {
157633
+ const e = entries[i];
157634
+ if (!e || typeof e !== "object")
157635
+ continue;
157636
+ const v = e;
157637
+ if (v.type !== "model_change")
157638
+ continue;
157639
+ if (typeof v.provider === "string" && v.provider.length > 0 && typeof v.modelId === "string" && v.modelId.length > 0) {
157640
+ return `${v.provider}/${v.modelId}`;
157641
+ }
157642
+ }
157643
+ return;
157644
+ }
157645
+ function rawEntryVersion(entry) {
157646
+ const record = entry;
157647
+ const updated = record.updatedAt ?? record.updated_at ?? record.timestamp;
157648
+ return typeof updated === "string" || typeof updated === "number" ? updated : entry.id;
157649
+ }
157650
+ function attachPiPartVersion(parts, version) {
157651
+ return parts.map((part) => {
157652
+ if (part === null || typeof part !== "object" || Array.isArray(part))
157653
+ return part;
157654
+ try {
157655
+ Object.defineProperty(part, "__magicContextPartUpdatedAt", {
157656
+ value: version,
157657
+ enumerable: false,
157658
+ configurable: true
157659
+ });
157660
+ } catch {}
157661
+ return part;
157662
+ });
157663
+ }
157664
+ function convertEntriesToRawMessages(entries) {
157665
+ const result = [];
157666
+ let nextOrdinal = 1;
157667
+ let pendingToolParts = [];
157668
+ let pendingFirstRealId = "";
157669
+ let pendingFirstRealVersion = "";
157670
+ for (const entry of entries) {
157671
+ if (!isMessageEntry(entry)) {
157672
+ continue;
157673
+ }
157674
+ const msg = entry.message;
157675
+ const role = msg.role;
157676
+ if (role === "toolResult") {
157677
+ const version = rawEntryVersion(entry);
157678
+ pendingToolParts.push(...attachPiPartVersion(synthesizeToolResultParts(msg), version));
157679
+ if (pendingFirstRealId === "") {
157680
+ pendingFirstRealId = entry.id;
157681
+ pendingFirstRealVersion = version;
157682
+ }
157683
+ continue;
157684
+ }
157685
+ if (role === "user") {
157686
+ const version = rawEntryVersion(entry);
157687
+ const parts = [
157688
+ ...pendingToolParts,
157689
+ ...attachPiPartVersion(synthesizeUserParts(msg), version)
157690
+ ];
157691
+ pendingToolParts = [];
157692
+ pendingFirstRealId = "";
157693
+ pendingFirstRealVersion = "";
157694
+ result.push({
157695
+ ordinal: nextOrdinal++,
157696
+ id: entry.id,
157697
+ role: "user",
157698
+ parts,
157699
+ version
157700
+ });
157701
+ continue;
157702
+ }
157703
+ if (role === "assistant") {
157704
+ if (pendingToolParts.length > 0) {
157705
+ result.push({
157706
+ ordinal: nextOrdinal++,
157707
+ id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
157708
+ role: "user",
157709
+ parts: pendingToolParts,
157710
+ version: pendingFirstRealVersion
157711
+ });
157712
+ pendingToolParts = [];
157713
+ pendingFirstRealId = "";
157714
+ pendingFirstRealVersion = "";
157715
+ }
157716
+ const version = rawEntryVersion(entry);
157717
+ result.push({
157718
+ ordinal: nextOrdinal++,
157719
+ id: entry.id,
157720
+ role: "assistant",
157721
+ parts: attachPiPartVersion(synthesizeAssistantParts(msg), version),
157722
+ version
157723
+ });
157724
+ continue;
157725
+ }
157726
+ result.push({
157727
+ ordinal: nextOrdinal++,
157728
+ id: entry.id,
157729
+ role: typeof role === "string" ? role : "unknown",
157730
+ parts: [],
157731
+ version: rawEntryVersion(entry)
157732
+ });
157733
+ }
157734
+ if (pendingToolParts.length > 0) {
157735
+ result.push({
157736
+ ordinal: nextOrdinal,
157737
+ id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
157738
+ role: "user",
157739
+ parts: pendingToolParts,
157740
+ version: pendingFirstRealVersion
157741
+ });
157742
+ }
157743
+ return result;
157744
+ }
157745
+ function isMessageEntry(value) {
157746
+ if (value === null || typeof value !== "object")
157747
+ return false;
157748
+ const v = value;
157749
+ if (v.type !== "message")
157750
+ return false;
157751
+ if (typeof v.id !== "string")
157752
+ return false;
157753
+ if (v.message === null || typeof v.message !== "object")
157754
+ return false;
157755
+ return true;
157756
+ }
157757
+ function synthesizeUserParts(msg) {
157758
+ const m = msg;
157759
+ if (typeof m.content === "string") {
157760
+ if (m.content.trim().length === 0)
157761
+ return [];
157762
+ return [{ type: "text", text: m.content }];
157763
+ }
157764
+ if (!Array.isArray(m.content))
157765
+ return [];
157766
+ const parts = [];
157767
+ for (const c of m.content) {
157768
+ if (c === null || typeof c !== "object")
157769
+ continue;
157770
+ const cc = c;
157771
+ if (cc.type === "text" && typeof cc.text === "string") {
157772
+ parts.push({ type: "text", text: cc.text });
157773
+ }
157774
+ }
157775
+ return parts;
157776
+ }
157777
+ function synthesizeAssistantParts(msg) {
157778
+ const m = msg;
157779
+ if (!Array.isArray(m.content))
157780
+ return [];
157781
+ const parts = [];
157782
+ for (const c of m.content) {
157783
+ if (c === null || typeof c !== "object")
157784
+ continue;
157785
+ const cc = c;
157786
+ if (cc.type === "text" && typeof cc.text === "string") {
157787
+ parts.push({ type: "text", text: cc.text });
157788
+ } else if (cc.type === "toolCall" && typeof cc.id === "string") {
157789
+ parts.push({
157790
+ type: "tool",
157791
+ tool: typeof cc.name === "string" ? cc.name : "unknown",
157792
+ callID: cc.id,
157793
+ state: {
157794
+ input: cc.arguments ?? {}
157795
+ }
157796
+ });
157797
+ }
157798
+ }
157799
+ return parts;
157800
+ }
157801
+ function synthesizeToolResultParts(msg) {
157802
+ const m = msg;
157803
+ const callID = typeof m.toolCallId === "string" ? m.toolCallId : "";
157804
+ const tool = typeof m.toolName === "string" ? m.toolName : "unknown";
157805
+ if (!callID)
157806
+ return [];
157807
+ let output = "";
157808
+ if (Array.isArray(m.content)) {
157809
+ const fragments = [];
157810
+ for (const c of m.content) {
157811
+ if (c === null || typeof c !== "object")
157812
+ continue;
157813
+ const cc = c;
157814
+ if (cc.type === "text" && typeof cc.text === "string") {
157815
+ fragments.push(cc.text);
157816
+ }
157817
+ }
157818
+ output = fragments.join(`
157819
+ `);
157820
+ }
157821
+ return [
157822
+ {
157823
+ type: "tool",
157824
+ tool,
157825
+ callID,
157826
+ state: {
157827
+ output
157828
+ }
157829
+ }
157830
+ ];
157831
+ }
157832
+
157833
+ // src/clone-inheritance.ts
157834
+ var CONTENT_ID_SUFFIX2 = /:(?:p|file)\d+$/;
157835
+ function entryId(entry) {
157836
+ if (entry === null || typeof entry !== "object")
157837
+ return null;
157838
+ const id = entry.id;
157839
+ return typeof id === "string" && id.length > 0 ? id : null;
157840
+ }
157841
+ function branchPosition(entries, id) {
157842
+ return entries.findIndex((entry) => entryId(entry) === id);
157843
+ }
157844
+ function latestCompactionFirstKept(entries) {
157845
+ for (let index = entries.length - 1;index >= 0; index--) {
157846
+ const entry = entries[index];
157847
+ if (entry === null || typeof entry !== "object")
157848
+ continue;
157849
+ const value = entry;
157850
+ if (value.type === "compaction" && typeof value.firstKeptEntryId === "string" && value.firstKeptEntryId.length > 0) {
157851
+ return value.firstKeptEntryId;
157852
+ }
157853
+ }
157854
+ return null;
157855
+ }
157856
+ function parsePendingMarker(raw) {
157857
+ if (!raw)
157858
+ return null;
157859
+ try {
157860
+ const value = JSON.parse(raw);
157861
+ if (typeof value.firstKeptEntryId === "string" && typeof value.endMessageId === "string" && typeof value.ordinal === "number" && Number.isFinite(value.ordinal) && typeof value.tokensBefore === "number" && Number.isFinite(value.tokensBefore) && typeof value.summary === "string" && typeof value.publishedAt === "number" && Number.isFinite(value.publishedAt)) {
157862
+ return value;
157863
+ }
157864
+ } catch {
157865
+ return null;
157866
+ }
157867
+ return null;
157868
+ }
157869
+ function createCloneFilter(entries) {
157870
+ const rawMessages = convertEntriesToRawMessages([...entries]);
157871
+ const rawOrdinalById = new Map(rawMessages.map((message) => [message.id, message.ordinal]));
157872
+ const validStateIds = new Set(rawOrdinalById.keys());
157873
+ for (const entry of entries) {
157874
+ const id = entryId(entry);
157875
+ if (id)
157876
+ validStateIds.add(id);
157877
+ }
157878
+ const includeMessageId = (id) => validStateIds.has(id);
157879
+ return {
157880
+ resolveBoundaryOrdinal: (id) => rawOrdinalById.get(id),
157881
+ includeMessageId,
157882
+ includeTag: (tag) => {
157883
+ if (tag.type === "tool") {
157884
+ return typeof tag.toolOwnerMessageId === "string" && includeMessageId(tag.toolOwnerMessageId);
157885
+ }
157886
+ return rawOrdinalById.has(tag.messageId.replace(CONTENT_ID_SUFFIX2, ""));
157887
+ },
157888
+ selectPendingPiMarker: (rawState, copiedCompartments) => {
157889
+ const pending = parsePendingMarker(rawState);
157890
+ if (!pending)
157891
+ return null;
157892
+ const pendingFirstKeptPosition = branchPosition(entries, pending.firstKeptEntryId);
157893
+ if (pendingFirstKeptPosition < 0)
157894
+ return null;
157895
+ const mappedOrdinal = rawOrdinalById.get(pending.endMessageId);
157896
+ if (mappedOrdinal === undefined)
157897
+ return null;
157898
+ const matchingCompartment = copiedCompartments.find((compartment) => compartment.endMessageId === pending.endMessageId && compartment.endMessage === mappedOrdinal);
157899
+ if (!matchingCompartment)
157900
+ return null;
157901
+ const currentFirstKept = latestCompactionFirstKept(entries);
157902
+ if (currentFirstKept !== null) {
157903
+ const currentPosition = branchPosition(entries, currentFirstKept);
157904
+ if (currentPosition >= pendingFirstKeptPosition)
157905
+ return null;
157906
+ }
157907
+ return JSON.stringify({ ...pending, ordinal: mappedOrdinal });
157908
+ }
157909
+ };
157910
+ }
157911
+ async function readPiSessionIdFromFile(filePath) {
157912
+ const contents = await readFile(filePath, "utf8");
157913
+ const newline = contents.indexOf(`
157914
+ `);
157915
+ const headerText = newline >= 0 ? contents.slice(0, newline) : contents;
157916
+ const header = JSON.parse(headerText);
157917
+ if (header.type !== "session" || typeof header.id !== "string" || header.id.length === 0) {
157918
+ throw new Error("previous session file has no valid session header");
157919
+ }
157920
+ return header.id;
157921
+ }
157922
+ async function handlePiCloneSessionStart(event, ctx, deps) {
157923
+ if (event.reason !== "fork" || typeof event.previousSessionFile !== "string") {
157924
+ return null;
157925
+ }
157926
+ let stage = "resolve-destination";
157927
+ let sourceSessionId = "unknown";
157928
+ let destinationSessionId = "unknown";
157929
+ const writeLog = deps.writeLog ?? log;
157930
+ try {
157931
+ const manager = ctx.sessionManager;
157932
+ if (typeof manager?.getSessionId !== "function") {
157933
+ throw new Error("Pi session manager does not expose getSessionId");
157934
+ }
157935
+ destinationSessionId = manager.getSessionId() ?? "";
157936
+ if (destinationSessionId.length === 0) {
157937
+ throw new Error("Pi clone session id is empty");
157938
+ }
157939
+ stage = "read-source-header";
157940
+ sourceSessionId = await readPiSessionIdFromFile(event.previousSessionFile);
157941
+ stage = "read-clone-branch";
157942
+ if (typeof manager.getBranch !== "function") {
157943
+ throw new Error("Pi session manager does not expose getBranch");
157944
+ }
157945
+ const branchEntries = manager.getBranch();
157946
+ if (!Array.isArray(branchEntries)) {
157947
+ throw new Error("Pi clone branch is unavailable");
157948
+ }
157949
+ stage = "copy-state";
157950
+ const result = copySessionStateForClone(deps.db, sourceSessionId, destinationSessionId, createCloneFilter(branchEntries));
157951
+ if (result.kind === "destination-not-empty") {
157952
+ writeLog(`[magic-context][pi] clone-inheritance: skipped source=${sourceSessionId} dest=${destinationSessionId} stage=destination-guard reason=destination-not-empty`);
157953
+ return result;
157954
+ }
157955
+ stage = "signal-marker";
157956
+ if (result.pendingMarkerMigrated) {
157957
+ deps.signalPendingMarker(destinationSessionId);
157958
+ }
157959
+ writeLog(`[magic-context][pi] clone-inheritance: migrated compartments=${result.compartmentsCopied} tags=${result.tagsCopied} source=${sourceSessionId} dest=${destinationSessionId} reason=fork`);
157960
+ return result;
157961
+ } catch (error) {
157962
+ writeLog(`[magic-context][pi] clone-inheritance: failed source=${sourceSessionId} dest=${destinationSessionId} stage=${stage} error=${error instanceof Error ? error.message : String(error)}; run /ctx-wrapup to rebuild, or re-clone`);
157963
+ return null;
157964
+ }
157965
+ }
157966
+
157259
157967
  // ../plugin/src/agents/language-directive.ts
157260
157968
  var ENGLISH_LANGUAGE_NAMES = new Intl.DisplayNames(["en"], {
157261
157969
  type: "language",
@@ -173479,8 +174187,11 @@ var MagicContextConfigSchema = exports_external.object({
173479
174187
  provider: "local",
173480
174188
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
173481
174189
  }).describe("Embedding provider configuration"),
173482
- temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
174190
+ temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add compact date ranges to compartment headings. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
173483
174191
  keep_subagents: exports_external.boolean().default(false).describe("Debug: keep the child sessions Magic Context spawns for its own subagents (historian, dreamer, sidekick, memory-migration) instead of deleting them on success. Useful for short-term inspection/data collection — their full transcript (prompt, tool calls, token usage, output) stays in the host session store. Kept sessions accumulate until manually cleared; leave false for normal use. Requires a restart to take effect."),
174192
+ shadow_transform: exports_external.object({
174193
+ enabled: exports_external.boolean().default(false).describe("Dev flag: mirror every transform pass to the Magic Context Rust module over subc and byte-compare outputs. No behavior impact; default off.")
174194
+ }).default({ enabled: false }).describe("Developer-only shadow transform lane. When enabled, the plugin fire-and-forgets a copy of each finalized transform to the Rust module for comparison; the live output is unchanged."),
173484
174195
  todowrite: exports_external.object({
173485
174196
  enabled: exports_external.boolean().default(true).describe("Pi only: register Magic Context's todowrite task-list tool. Disable if you use your own todo extension. OpenCode ships its own built-in todowrite; this setting has no effect there."),
173486
174197
  overlay: exports_external.boolean().default(true).describe("Pi only: show the persistent todo overlay above the editor while tasks are active.")
@@ -175198,7 +175909,7 @@ var GIT_SWEEP_COOLDOWN_MS = 10 * 60 * 1000;
175198
175909
  var GIT_SWEEP_LEASE_TTL_MS = 5 * 60 * 1000;
175199
175910
  var GIT_SWEEP_NON_INDEXABLE_REPROBE_MS = 24 * 60 * 60 * 1000;
175200
175911
  var GIT_SWEEP_LEASE_RENEWAL_MS = 60 * 1000;
175201
- function runImmediate(db, body) {
175912
+ function runImmediate2(db, body) {
175202
175913
  db.exec("BEGIN IMMEDIATE");
175203
175914
  let committed = false;
175204
175915
  try {
@@ -175231,7 +175942,7 @@ function getGitSweepCoordinatorState(db, projectPath) {
175231
175942
  function acquireGitSweepLease(db, projectPath, holderId, options = {}) {
175232
175943
  const cooldownMs = options.cooldownMs ?? GIT_SWEEP_COOLDOWN_MS;
175233
175944
  const leaseTtlMs = options.leaseTtlMs ?? GIT_SWEEP_LEASE_TTL_MS;
175234
- return runImmediate(db, () => {
175945
+ return runImmediate2(db, () => {
175235
175946
  const now = Date.now();
175236
175947
  const row = getGitSweepCoordinatorState(db, projectPath);
175237
175948
  if (row?.leaseHolder && row.leaseExpiresAt !== null && row.leaseExpiresAt > now) {
@@ -175279,7 +175990,7 @@ function acquireGitSweepLease(db, projectPath, holderId, options = {}) {
175279
175990
  });
175280
175991
  }
175281
175992
  function renewGitSweepLease(db, projectPath, holderId, leaseTtlMs = GIT_SWEEP_LEASE_TTL_MS) {
175282
- return runImmediate(db, () => {
175993
+ return runImmediate2(db, () => {
175283
175994
  const now = Date.now();
175284
175995
  const leaseExpiresAt = now + leaseTtlMs;
175285
175996
  const result = db.prepare(`UPDATE git_sweep_coordinator
@@ -175291,7 +176002,7 @@ function renewGitSweepLease(db, projectPath, holderId, leaseTtlMs = GIT_SWEEP_LE
175291
176002
  });
175292
176003
  }
175293
176004
  function markGitSweepSuccessAndRelease(db, projectPath, holderId) {
175294
- return runImmediate(db, () => {
176005
+ return runImmediate2(db, () => {
175295
176006
  const now = Date.now();
175296
176007
  const result = db.prepare(`UPDATE git_sweep_coordinator
175297
176008
  SET lease_holder = NULL,
@@ -175304,7 +176015,7 @@ function markGitSweepSuccessAndRelease(db, projectPath, holderId) {
175304
176015
  });
175305
176016
  }
175306
176017
  function parkGitSweepNonIndexable(db, projectPath, holderId, reprobeMs = GIT_SWEEP_NON_INDEXABLE_REPROBE_MS) {
175307
- return runImmediate(db, () => {
176018
+ return runImmediate2(db, () => {
175308
176019
  const now = Date.now();
175309
176020
  const sweptAt = now + reprobeMs - GIT_SWEEP_COOLDOWN_MS;
175310
176021
  const result = db.prepare(`UPDATE git_sweep_coordinator
@@ -175318,7 +176029,7 @@ function parkGitSweepNonIndexable(db, projectPath, holderId, reprobeMs = GIT_SWE
175318
176029
  });
175319
176030
  }
175320
176031
  function releaseGitSweepLease(db, projectPath, holderId) {
175321
- runImmediate(db, () => {
176032
+ runImmediate2(db, () => {
175322
176033
  db.prepare(`UPDATE git_sweep_coordinator
175323
176034
  SET lease_holder = NULL,
175324
176035
  lease_expires_at = NULL
@@ -177315,7 +178026,7 @@ function peekLeaseHolderAndExpiry(db, expectedHolder, leaseKey = DREAMING_LEASE_
177315
178026
  const expiry = Number(expiryStr);
177316
178027
  return Number.isFinite(expiry) && expiry >= Date.now();
177317
178028
  }
177318
- function runImmediate2(db, body) {
178029
+ function runImmediate3(db, body) {
177319
178030
  db.exec("BEGIN IMMEDIATE");
177320
178031
  let committed = false;
177321
178032
  try {
@@ -177333,7 +178044,7 @@ function runImmediate2(db, body) {
177333
178044
  }
177334
178045
  function acquireLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177335
178046
  const keys = rowKeys(leaseKey);
177336
- return runImmediate2(db, () => {
178047
+ return runImmediate3(db, () => {
177337
178048
  if (isLeaseActive(db, leaseKey)) {
177338
178049
  const existingHolder = getLeaseHolder(db, leaseKey);
177339
178050
  if (existingHolder && existingHolder !== holderId) {
@@ -177349,7 +178060,7 @@ function acquireLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177349
178060
  }
177350
178061
  function renewLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177351
178062
  const keys = rowKeys(leaseKey);
177352
- return runImmediate2(db, () => {
178063
+ return runImmediate3(db, () => {
177353
178064
  if (getLeaseHolder(db, leaseKey) !== holderId || !isLeaseActive(db, leaseKey)) {
177354
178065
  return false;
177355
178066
  }
@@ -177360,7 +178071,7 @@ function renewLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177360
178071
  });
177361
178072
  }
177362
178073
  function runLeaseGuardedWrite(db, holderId, leaseKey, fn) {
177363
- return runImmediate2(db, () => {
178074
+ return runImmediate3(db, () => {
177364
178075
  if (!peekLeaseHolderAndExpiry(db, holderId, leaseKey)) {
177365
178076
  throw new Error("Dream lease lost before guarded write");
177366
178077
  }
@@ -177414,7 +178125,7 @@ function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalMs = LEASE_
177414
178125
  }
177415
178126
  function releaseLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177416
178127
  const keys = rowKeys(leaseKey);
177417
- runImmediate2(db, () => {
178128
+ runImmediate3(db, () => {
177418
178129
  if (getLeaseHolder(db, leaseKey) !== holderId) {
177419
178130
  return;
177420
178131
  }
@@ -183387,6 +184098,9 @@ async function sweepGitCommits(args) {
183387
184098
  }
183388
184099
  }
183389
184100
 
184101
+ // src/embedding-bootstrap.ts
184102
+ import { statSync as statSync7 } from "node:fs";
184103
+
183390
184104
  // ../plugin/src/plugin/embedding-bootstrap-helpers.ts
183391
184105
  import { createHash as createHash11 } from "node:crypto";
183392
184106
  init_logger();
@@ -183934,6 +184648,10 @@ function stripUnsafeProjectConfigFields(projectRaw) {
183934
184648
  delete projectRaw.sqlite;
183935
184649
  warnings.push("Ignoring sqlite.* from project config (security: SQLite cache/mmap PRAGMAs apply to the " + "process-global shared database handle; only user-level config may set them).");
183936
184650
  }
184651
+ if ("shadow_transform" in projectRaw) {
184652
+ delete projectRaw.shadow_transform;
184653
+ warnings.push("Ignoring shadow_transform from project config (security: this developer-only mirror lane is user-level only).");
184654
+ }
183937
184655
  const embedding2 = projectRaw.embedding;
183938
184656
  if (isPlainObject3(embedding2)) {
183939
184657
  const removed = [];
@@ -184607,8 +185325,38 @@ function loadPiConfigDetailed(opts = {}) {
184607
185325
  }
184608
185326
 
184609
185327
  // src/embedding-bootstrap.ts
185328
+ var registrationFingerprintsByDatabase = new WeakMap;
185329
+ function configCandidatePaths(directory, loadedPaths) {
185330
+ const projectBase = cortexKitProjectConfigBasePath(directory);
185331
+ const userBase = cortexKitUserConfigBasePath();
185332
+ return [
185333
+ `${projectBase}.jsonc`,
185334
+ `${projectBase}.json`,
185335
+ `${userBase}.jsonc`,
185336
+ `${userBase}.json`,
185337
+ ...loadedPaths
185338
+ ].filter((path11, index, paths) => paths.indexOf(path11) === index);
185339
+ }
185340
+ function configFingerprint(paths) {
185341
+ return paths.map((path11) => {
185342
+ try {
185343
+ const stat2 = statSync7(path11);
185344
+ return `${path11}:${stat2.size}:${stat2.mtimeMs}`;
185345
+ } catch {
185346
+ return `${path11}:missing`;
185347
+ }
185348
+ }).join("|");
185349
+ }
184610
185350
  async function ensureProjectRegisteredFromPiDirectory(directory, db) {
184611
185351
  const projectIdentity = resolveProjectIdentity(directory);
185352
+ let registrationFingerprints = registrationFingerprintsByDatabase.get(db);
185353
+ if (!registrationFingerprints) {
185354
+ registrationFingerprints = new Map;
185355
+ registrationFingerprintsByDatabase.set(db, registrationFingerprints);
185356
+ }
185357
+ const cached2 = registrationFingerprints.get(projectIdentity);
185358
+ if (cached2 && configFingerprint(cached2.paths) === cached2.fingerprint)
185359
+ return;
184612
185360
  const detailed = loadPiConfigDetailed({ cwd: directory });
184613
185361
  if (isConfigLoadUntrusted(detailed)) {
184614
185362
  handleUntrustedLoad(db, projectIdentity, directory, detailed);
@@ -184619,315 +185367,65 @@ async function ensureProjectRegisteredFromPiDirectory(directory, db) {
184619
185367
  gitCommitEnabled: detailed.config.memory.git_commit_indexing.enabled
184620
185368
  };
184621
185369
  registerProjectEmbedding(db, projectIdentity, detailed.config.embedding, features, directory);
184622
- }
184623
-
184624
- // src/read-session-pi.ts
184625
- var SYNTH_USER_ID_PREFIX = "synth-user-";
184626
- function resolvePiStableId(msg, index, entryIds, entryIdByRef) {
184627
- if (!msg || typeof msg !== "object")
184628
- return;
184629
- const byRef = entryIdByRef?.get(msg);
184630
- if (typeof byRef === "string" && byRef.length > 0)
184631
- return byRef;
184632
- const positional = entryIds?.[index];
184633
- if (typeof positional === "string" && positional.length > 0)
184634
- return positional;
184635
- const m = msg;
184636
- const role = m.role ?? "unknown";
184637
- return typeof m.timestamp === "number" ? `pi-msg-${index}-${m.timestamp}-${role}` : `pi-msg-${index}-${role}`;
184638
- }
184639
- function isMidTurnPi(event, _sessionId) {
184640
- const messages = event?.messages;
184641
- if (!Array.isArray(messages))
184642
- return false;
184643
- let latestAssistantIndex = -1;
184644
- let latestAssistant = null;
184645
- for (let i = messages.length - 1;i >= 0; i--) {
184646
- const msg = messages[i];
184647
- if (msg !== null && typeof msg === "object") {
184648
- const record2 = msg;
184649
- if (record2.role === "assistant") {
184650
- latestAssistantIndex = i;
184651
- latestAssistant = record2;
184652
- break;
184653
- }
184654
- }
184655
- }
184656
- if (latestAssistant === null)
184657
- return false;
184658
- if (hasRealUserAfter(messages, latestAssistantIndex))
184659
- return false;
184660
- if (latestAssistant.stopReason === "toolUse")
184661
- return true;
184662
- const toolCallIds = getToolCallIds(latestAssistant.content);
184663
- if (toolCallIds.size === 0)
184664
- return false;
184665
- const pairedToolResultIds = new Set;
184666
- for (const msg of messages.slice(latestAssistantIndex + 1)) {
184667
- if (msg === null || typeof msg !== "object")
184668
- continue;
184669
- const record2 = msg;
184670
- if (record2.role !== "toolResult")
184671
- continue;
184672
- if (typeof record2.toolCallId === "string") {
184673
- pairedToolResultIds.add(record2.toolCallId);
184674
- }
184675
- }
184676
- for (const id of toolCallIds) {
184677
- if (!pairedToolResultIds.has(id))
184678
- return true;
184679
- }
184680
- return false;
184681
- }
184682
- function hasRealUserAfter(messages, latestAssistantIndex) {
184683
- for (const msg of messages.slice(latestAssistantIndex + 1)) {
184684
- if (msg === null || typeof msg !== "object")
184685
- continue;
184686
- if (msg.role === "user")
184687
- return true;
184688
- }
184689
- return false;
184690
- }
184691
- function getToolCallIds(content) {
184692
- const ids = new Set;
184693
- if (!Array.isArray(content))
184694
- return ids;
184695
- for (const item of content) {
184696
- if (item === null || typeof item !== "object")
184697
- continue;
184698
- const record2 = item;
184699
- if (record2.type === "toolCall" && typeof record2.id === "string") {
184700
- ids.add(record2.id);
184701
- }
184702
- }
184703
- return ids;
184704
- }
184705
- function readPiSessionMessages(ctx) {
184706
- const sm = ctx.sessionManager;
184707
- if (sm === undefined)
184708
- return [];
184709
- const getBranch = sm.getBranch;
184710
- if (typeof getBranch !== "function")
184711
- return [];
184712
- let entries;
184713
- try {
184714
- entries = getBranch.call(sm);
184715
- } catch {
184716
- return [];
184717
- }
184718
- if (!Array.isArray(entries))
184719
- return [];
184720
- return convertEntriesToRawMessages(entries);
184721
- }
184722
- function findLastModelKeyFromBranch(entries) {
184723
- if (!Array.isArray(entries))
184724
- return;
184725
- for (let i = entries.length - 1;i >= 0; i--) {
184726
- const e = entries[i];
184727
- if (!e || typeof e !== "object")
184728
- continue;
184729
- const v = e;
184730
- if (v.type !== "model_change")
184731
- continue;
184732
- if (typeof v.provider === "string" && v.provider.length > 0 && typeof v.modelId === "string" && v.modelId.length > 0) {
184733
- return `${v.provider}/${v.modelId}`;
184734
- }
184735
- }
184736
- return;
184737
- }
184738
- function rawEntryVersion(entry) {
184739
- const record2 = entry;
184740
- const updated = record2.updatedAt ?? record2.updated_at ?? record2.timestamp;
184741
- return typeof updated === "string" || typeof updated === "number" ? updated : entry.id;
184742
- }
184743
- function attachPiPartVersion(parts, version2) {
184744
- return parts.map((part) => {
184745
- if (part === null || typeof part !== "object" || Array.isArray(part))
184746
- return part;
184747
- try {
184748
- Object.defineProperty(part, "__magicContextPartUpdatedAt", {
184749
- value: version2,
184750
- enumerable: false,
184751
- configurable: true
184752
- });
184753
- } catch {}
184754
- return part;
185370
+ const fingerprintPaths = configCandidatePaths(directory, detailed.loadedFromPaths);
185371
+ registrationFingerprints.set(projectIdentity, {
185372
+ paths: fingerprintPaths,
185373
+ fingerprint: configFingerprint(fingerprintPaths)
184755
185374
  });
184756
185375
  }
184757
- function convertEntriesToRawMessages(entries) {
184758
- const result = [];
184759
- let nextOrdinal = 1;
184760
- let pendingToolParts = [];
184761
- let pendingFirstRealId = "";
184762
- let pendingFirstRealVersion = "";
184763
- for (const entry of entries) {
184764
- if (!isMessageEntry(entry)) {
184765
- continue;
184766
- }
184767
- const msg = entry.message;
184768
- const role = msg.role;
184769
- if (role === "toolResult") {
184770
- const version2 = rawEntryVersion(entry);
184771
- pendingToolParts.push(...attachPiPartVersion(synthesizeToolResultParts(msg), version2));
184772
- if (pendingFirstRealId === "") {
184773
- pendingFirstRealId = entry.id;
184774
- pendingFirstRealVersion = version2;
184775
- }
184776
- continue;
184777
- }
184778
- if (role === "user") {
184779
- const version2 = rawEntryVersion(entry);
184780
- const parts = [
184781
- ...pendingToolParts,
184782
- ...attachPiPartVersion(synthesizeUserParts(msg), version2)
184783
- ];
184784
- pendingToolParts = [];
184785
- pendingFirstRealId = "";
184786
- pendingFirstRealVersion = "";
184787
- result.push({
184788
- ordinal: nextOrdinal++,
184789
- id: entry.id,
184790
- role: "user",
184791
- parts,
184792
- version: version2
184793
- });
184794
- continue;
184795
- }
184796
- if (role === "assistant") {
184797
- if (pendingToolParts.length > 0) {
184798
- result.push({
184799
- ordinal: nextOrdinal++,
184800
- id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
184801
- role: "user",
184802
- parts: pendingToolParts,
184803
- version: pendingFirstRealVersion
184804
- });
184805
- pendingToolParts = [];
184806
- pendingFirstRealId = "";
184807
- pendingFirstRealVersion = "";
185376
+
185377
+ // src/dreamer/pi-session-api.ts
185378
+ import { readFileSync as readFileSync9 } from "node:fs";
185379
+ import { createRequire as createRequire3 } from "node:module";
185380
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
185381
+ var PI_CODING_AGENT_MODULE = "@earendil-works/pi-coding-agent";
185382
+ var defaultLoaders = [
185383
+ {
185384
+ name: "Bare import",
185385
+ load: async () => await import(PI_CODING_AGENT_MODULE)
185386
+ },
185387
+ {
185388
+ name: "Resolve from running Pi binary entry",
185389
+ load: async () => {
185390
+ if (!process.argv[1]) {
185391
+ throw new Error("process.argv[1] is undefined");
184808
185392
  }
184809
- const version2 = rawEntryVersion(entry);
184810
- result.push({
184811
- ordinal: nextOrdinal++,
184812
- id: entry.id,
184813
- role: "assistant",
184814
- parts: attachPiPartVersion(synthesizeAssistantParts(msg), version2),
184815
- version: version2
184816
- });
184817
- continue;
185393
+ const require2 = createRequire3(process.argv[1]);
185394
+ const resolved = require2.resolve(PI_CODING_AGENT_MODULE);
185395
+ return await import(pathToFileURL2(resolved).href);
184818
185396
  }
184819
- result.push({
184820
- ordinal: nextOrdinal++,
184821
- id: entry.id,
184822
- role: typeof role === "string" ? role : "unknown",
184823
- parts: [],
184824
- version: rawEntryVersion(entry)
184825
- });
184826
- }
184827
- if (pendingToolParts.length > 0) {
184828
- result.push({
184829
- ordinal: nextOrdinal,
184830
- id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
184831
- role: "user",
184832
- parts: pendingToolParts,
184833
- version: pendingFirstRealVersion
184834
- });
184835
- }
184836
- return result;
184837
- }
184838
- function isMessageEntry(value) {
184839
- if (value === null || typeof value !== "object")
184840
- return false;
184841
- const v = value;
184842
- if (v.type !== "message")
184843
- return false;
184844
- if (typeof v.id !== "string")
184845
- return false;
184846
- if (v.message === null || typeof v.message !== "object")
184847
- return false;
184848
- return true;
184849
- }
184850
- function synthesizeUserParts(msg) {
184851
- const m = msg;
184852
- if (typeof m.content === "string") {
184853
- if (m.content.trim().length === 0)
184854
- return [];
184855
- return [{ type: "text", text: m.content }];
184856
185397
  }
184857
- if (!Array.isArray(m.content))
184858
- return [];
184859
- const parts = [];
184860
- for (const c of m.content) {
184861
- if (c === null || typeof c !== "object")
184862
- continue;
184863
- const cc = c;
184864
- if (cc.type === "text" && typeof cc.text === "string") {
184865
- parts.push({ type: "text", text: cc.text });
184866
- }
184867
- }
184868
- return parts;
184869
- }
184870
- function synthesizeAssistantParts(msg) {
184871
- const m = msg;
184872
- if (!Array.isArray(m.content))
184873
- return [];
184874
- const parts = [];
184875
- for (const c of m.content) {
184876
- if (c === null || typeof c !== "object")
184877
- continue;
184878
- const cc = c;
184879
- if (cc.type === "text" && typeof cc.text === "string") {
184880
- parts.push({ type: "text", text: cc.text });
184881
- } else if (cc.type === "toolCall" && typeof cc.id === "string") {
184882
- parts.push({
184883
- type: "tool",
184884
- tool: typeof cc.name === "string" ? cc.name : "unknown",
184885
- callID: cc.id,
184886
- state: {
184887
- input: cc.arguments ?? {}
184888
- }
184889
- });
184890
- }
184891
- }
184892
- return parts;
184893
- }
184894
- function synthesizeToolResultParts(msg) {
184895
- const m = msg;
184896
- const callID = typeof m.toolCallId === "string" ? m.toolCallId : "";
184897
- const tool = typeof m.toolName === "string" ? m.toolName : "unknown";
184898
- if (!callID)
184899
- return [];
184900
- let output = "";
184901
- if (Array.isArray(m.content)) {
184902
- const fragments = [];
184903
- for (const c of m.content) {
184904
- if (c === null || typeof c !== "object")
184905
- continue;
184906
- const cc = c;
184907
- if (cc.type === "text" && typeof cc.text === "string") {
184908
- fragments.push(cc.text);
185398
+ ];
185399
+ var cachedModulePromise = null;
185400
+ async function resolvePiCodingAgentModule(loaders) {
185401
+ if (cachedModulePromise) {
185402
+ return cachedModulePromise;
185403
+ }
185404
+ const activeLoaders = loaders ?? defaultLoaders;
185405
+ const promise2 = (async () => {
185406
+ const errors3 = [];
185407
+ for (const loader of activeLoaders) {
185408
+ try {
185409
+ return await loader.load();
185410
+ } catch (err) {
185411
+ errors3.push(err);
184909
185412
  }
184910
185413
  }
184911
- output = fragments.join(`
184912
- `);
184913
- }
184914
- return [
184915
- {
184916
- type: "tool",
184917
- tool,
184918
- callID,
184919
- state: {
184920
- output
184921
- }
185414
+ throw new Error(`Failed to resolve ${PI_CODING_AGENT_MODULE} via all strategies:
185415
+ ` + errors3.map((e, i) => ` - ${activeLoaders[i].name}: ${e.message || e}`).join(`
185416
+ `) + `
185417
+ Likely cause: symlinked or nonstandard install layout.`);
185418
+ })();
185419
+ cachedModulePromise = promise2;
185420
+ promise2.catch(() => {
185421
+ if (cachedModulePromise === promise2) {
185422
+ cachedModulePromise = null;
184922
185423
  }
184923
- ];
185424
+ });
185425
+ return promise2;
184924
185426
  }
184925
-
184926
- // src/dreamer/pi-session-api.ts
184927
- import { readFileSync as readFileSync9 } from "node:fs";
184928
- var PI_CODING_AGENT_MODULE = "@earendil-works/pi-coding-agent";
184929
- async function loadDefaultPiSessionApi() {
184930
- const mod = await import(PI_CODING_AGENT_MODULE);
185427
+ async function loadDefaultPiSessionApi(loaders) {
185428
+ const mod = await resolvePiCodingAgentModule(loaders);
184931
185429
  const listSessions = mod.SessionManager?.listAll;
184932
185430
  if (typeof listSessions !== "function") {
184933
185431
  throw new Error("Pi session APIs unavailable: expected SessionManager.listAll on pi-coding-agent");
@@ -185328,6 +185826,8 @@ function makeMessage(role, parts) {
185328
185826
  }
185329
185827
 
185330
185828
  // src/commands/pi-command-utils.ts
185829
+ init_logger();
185830
+ import { Box, Text } from "@earendil-works/pi-tui";
185331
185831
  var CTX_STATUS_CUSTOM_TYPE = "ctx-status";
185332
185832
  function resolveSessionId(ctx) {
185333
185833
  const sm = ctx.sessionManager;
@@ -185341,13 +185841,49 @@ function resolveSessionId(ctx) {
185341
185841
  return;
185342
185842
  }
185343
185843
  }
185844
+ function statusTitleColor(level) {
185845
+ switch (level) {
185846
+ case "success":
185847
+ return "success";
185848
+ case "warning":
185849
+ return "warning";
185850
+ case "error":
185851
+ return "error";
185852
+ default:
185853
+ return "accent";
185854
+ }
185855
+ }
185856
+ var renderCtxStatusEntry = (entry, _options, theme) => {
185857
+ const data = entry?.data;
185858
+ if (!data || typeof data !== "object" || typeof data.title !== "string" || typeof data.text !== "string") {
185859
+ return;
185860
+ }
185861
+ const title = theme.bold(theme.fg(statusTitleColor(data.level), `[${data.title}]`));
185862
+ const body = theme.fg("customMessageText", data.text);
185863
+ const box = new Box(1, 0, (text) => theme.bg("customMessageBg", text));
185864
+ box.addChild(new Text(`${title}
185865
+ ${body}`));
185866
+ return box;
185867
+ };
185868
+ function registerCtxStatusEntryRenderer(pi) {
185869
+ if (typeof pi.registerEntryRenderer !== "function")
185870
+ return false;
185871
+ try {
185872
+ pi.registerEntryRenderer(CTX_STATUS_CUSTOM_TYPE, renderCtxStatusEntry);
185873
+ return true;
185874
+ } catch {
185875
+ return false;
185876
+ }
185877
+ }
185344
185878
  function sendCtxStatusMessage(pi, content, details) {
185345
- pi.sendMessage({
185346
- customType: CTX_STATUS_CUSTOM_TYPE,
185347
- content: content.text,
185348
- display: true,
185349
- details: { ...content, details }
185350
- }, { triggerTurn: false });
185879
+ const data = {
185880
+ ...content,
185881
+ details: details ?? content.details
185882
+ };
185883
+ if (typeof pi.appendEntry === "function") {
185884
+ pi.appendEntry(CTX_STATUS_CUSTOM_TYPE, data);
185885
+ }
185886
+ sessionLog("pi-status", `${content.title}: ${content.text}`);
185351
185887
  }
185352
185888
 
185353
185889
  // src/commands/ctx-dream.ts
@@ -185807,8 +186343,8 @@ function makeToolCompositeKey(ownerMsgId, callId) {
185807
186343
  return `${ownerMsgId}${TOOL_COMPOSITE_KEY_SEP}${callId}`;
185808
186344
  }
185809
186345
  var GET_COUNTER_SQL = `SELECT counter FROM session_meta WHERE session_id = ?`;
185810
- var GET_ASSIGNMENTS_SQL = "SELECT message_id, tag_number, type, tool_owner_message_id FROM tags WHERE session_id = ? ORDER BY tag_number ASC";
185811
- var GET_ASSIGNMENTS_SCOPED_SQL = "SELECT message_id, tag_number, type, tool_owner_message_id FROM tags WHERE session_id = ? AND tag_number >= ? ORDER BY tag_number ASC";
186346
+ var GET_ASSIGNMENTS_SQL = "SELECT message_id, tag_number, type, tool_owner_message_id, byte_size, token_count, input_byte_size, input_token_count FROM tags WHERE session_id = ? ORDER BY tag_number ASC";
186347
+ var GET_ASSIGNMENTS_SCOPED_SQL = "SELECT message_id, tag_number, type, tool_owner_message_id, byte_size, token_count, input_byte_size, input_token_count FROM tags WHERE session_id = ? AND tag_number >= ? ORDER BY tag_number ASC";
185812
186348
  var PROBE_DATA_VERSION_SQL = "PRAGMA main.data_version";
185813
186349
  var probeDataVersionStatements = new WeakMap;
185814
186350
  function getProbeDataVersionStatement(db) {
@@ -185832,6 +186368,14 @@ function isAssignmentRow(row) {
185832
186368
  return false;
185833
186369
  if (candidate.tool_owner_message_id !== null && typeof candidate.tool_owner_message_id !== "string")
185834
186370
  return false;
186371
+ if (typeof candidate.byte_size !== "number")
186372
+ return false;
186373
+ if (candidate.token_count !== null && typeof candidate.token_count !== "number")
186374
+ return false;
186375
+ if (typeof candidate.input_byte_size !== "number")
186376
+ return false;
186377
+ if (candidate.input_token_count !== null && typeof candidate.input_token_count !== "number")
186378
+ return false;
185835
186379
  return true;
185836
186380
  }
185837
186381
  var UPSERT_COUNTER_SQL = `
@@ -185866,6 +186410,7 @@ var MAX_TAG_ALLOC_RETRIES = 5;
185866
186410
  function createTagger() {
185867
186411
  const counters = new Map;
185868
186412
  const assignments = new Map;
186413
+ const toolAccountingBySession = new Map;
185869
186414
  const loadSignatures = new Map;
185870
186415
  function getSessionAssignments(sessionId) {
185871
186416
  let map2 = assignments.get(sessionId);
@@ -185875,6 +186420,14 @@ function createTagger() {
185875
186420
  }
185876
186421
  return map2;
185877
186422
  }
186423
+ function getSessionToolAccounting(sessionId) {
186424
+ let map2 = toolAccountingBySession.get(sessionId);
186425
+ if (!map2) {
186426
+ map2 = new Map;
186427
+ toolAccountingBySession.set(sessionId, map2);
186428
+ }
186429
+ return map2;
186430
+ }
185878
186431
  function isUniqueConstraintError(error51) {
185879
186432
  return error51 instanceof Error && "code" in error51 && error51.code === "SQLITE_CONSTRAINT_UNIQUE";
185880
186433
  }
@@ -185928,6 +186481,14 @@ function createTagger() {
185928
186481
  }
185929
186482
  counters.set(sessionId, next);
185930
186483
  sessionAssignments.set(mapKey, next);
186484
+ if (type === "tool") {
186485
+ getSessionToolAccounting(sessionId).set(next, {
186486
+ byteSize: byteSize2,
186487
+ tokenCount: tokenCounts?.tokenCount ?? null,
186488
+ inputByteSize,
186489
+ inputTokenCount: tokenCounts?.inputTokenCount ?? null
186490
+ });
186491
+ }
185931
186492
  return next;
185932
186493
  }
185933
186494
  throw new Error(`tagger.allocateTag: failed to allocate tag for session=${sessionId} key=${mapKey} after ${MAX_TAG_ALLOC_RETRIES} retries`);
@@ -185988,6 +186549,13 @@ function createTagger() {
185988
186549
  function getToolTag(sessionId, callId, ownerMsgId) {
185989
186550
  return assignments.get(sessionId)?.get(makeToolCompositeKey(ownerMsgId, callId));
185990
186551
  }
186552
+ function getToolTagAccounting(sessionId, callId, ownerMsgId) {
186553
+ const tagNumber = getToolTag(sessionId, callId, ownerMsgId);
186554
+ return tagNumber === undefined ? undefined : toolAccountingBySession.get(sessionId)?.get(tagNumber);
186555
+ }
186556
+ function setToolTagAccounting(sessionId, tagNumber, accounting) {
186557
+ getSessionToolAccounting(sessionId).set(tagNumber, accounting);
186558
+ }
185991
186559
  function bindTag(sessionId, messageId, tagNumber) {
185992
186560
  getSessionAssignments(sessionId).set(messageId, tagNumber);
185993
186561
  }
@@ -186006,6 +186574,7 @@ function createTagger() {
186006
186574
  function resetCounter(sessionId, db) {
186007
186575
  counters.set(sessionId, 0);
186008
186576
  assignments.delete(sessionId);
186577
+ toolAccountingBySession.delete(sessionId);
186009
186578
  loadSignatures.delete(sessionId);
186010
186579
  getResetCounterStatement(db).run(sessionId, getHarness());
186011
186580
  }
@@ -186028,11 +186597,19 @@ function createTagger() {
186028
186597
  const assignmentRows = (floor > 0 ? db.prepare(GET_ASSIGNMENTS_SCOPED_SQL).all(sessionId, floor) : db.prepare(GET_ASSIGNMENTS_SQL).all(sessionId)).filter(isAssignmentRow);
186029
186598
  const sessionAssignments = getSessionAssignments(sessionId);
186030
186599
  sessionAssignments.clear();
186600
+ const sessionToolAccounting = getSessionToolAccounting(sessionId);
186601
+ sessionToolAccounting.clear();
186031
186602
  let maxTagNumber = 0;
186032
186603
  for (const assignment of assignmentRows) {
186033
186604
  if (assignment.type === "tool") {
186034
186605
  if (assignment.tool_owner_message_id !== null) {
186035
186606
  sessionAssignments.set(makeToolCompositeKey(assignment.tool_owner_message_id, assignment.message_id), assignment.tag_number);
186607
+ sessionToolAccounting.set(assignment.tag_number, {
186608
+ byteSize: assignment.byte_size,
186609
+ tokenCount: assignment.token_count,
186610
+ inputByteSize: assignment.input_byte_size,
186611
+ inputTokenCount: assignment.input_token_count
186612
+ });
186036
186613
  }
186037
186614
  } else {
186038
186615
  sessionAssignments.set(assignment.message_id, assignment.tag_number);
@@ -186052,6 +186629,7 @@ function createTagger() {
186052
186629
  function cleanup(sessionId) {
186053
186630
  counters.delete(sessionId);
186054
186631
  assignments.delete(sessionId);
186632
+ toolAccountingBySession.delete(sessionId);
186055
186633
  loadSignatures.delete(sessionId);
186056
186634
  }
186057
186635
  return {
@@ -186059,6 +186637,8 @@ function createTagger() {
186059
186637
  assignToolTag,
186060
186638
  getTag,
186061
186639
  getToolTag,
186640
+ getToolTagAccounting,
186641
+ setToolTagAccounting,
186062
186642
  bindTag,
186063
186643
  unbindTag,
186064
186644
  bindToolTag,
@@ -188034,26 +188614,28 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
188034
188614
  sessionLog(sessionId, `compartment trigger: skipped — historian already in progress (usage=${usage.percentage.toFixed(1)}%)`);
188035
188615
  return { shouldFire: false };
188036
188616
  }
188037
- if (inMemoryTail) {
188617
+ const lazyInMemoryTail = typeof inMemoryTail === "function" ? inMemoryTail : undefined;
188618
+ let resolvedInMemoryTail = typeof inMemoryTail === "function" ? undefined : inMemoryTail;
188619
+ if (resolvedInMemoryTail) {
188038
188620
  try {
188039
188621
  const policyVersion = loadProtectedTailMeta(db, sessionId).protectedTailPolicyVersion;
188040
188622
  if (policyVersion < 3)
188041
- inMemoryTail = undefined;
188623
+ resolvedInMemoryTail = undefined;
188042
188624
  } catch {
188043
- inMemoryTail = undefined;
188625
+ resolvedInMemoryTail = undefined;
188044
188626
  }
188045
188627
  }
188046
- const taggerFloor = taggerFloorOverride !== undefined && taggerFloorOverride > 0 ? taggerFloorOverride : inMemoryTail ? deriveTagLoadFloor(db, sessionId, inMemoryTail.messages.map((m) => m.id)) : 0;
188628
+ const taggerFloor = taggerFloorOverride !== undefined && taggerFloorOverride > 0 ? taggerFloorOverride : resolvedInMemoryTail ? deriveTagLoadFloor(db, sessionId, resolvedInMemoryTail.messages.map((m) => m.id)) : 0;
188047
188629
  const proactiveFloorForGate = getProactiveCompartmentTriggerPercentage(executeThresholdPercentage);
188048
188630
  if (usage.percentage < proactiveFloorForGate) {
188049
188631
  try {
188050
- const boundFloor = inMemoryTail ? taggerFloor : 0;
188632
+ const boundFloor = resolvedInMemoryTail || lazyInMemoryTail ? taggerFloor : 0;
188051
188633
  const { bound: persistedBound, nullCount } = getTriggerTagTokenUpperBound(db, sessionId, boundFloor);
188052
188634
  if (nullCount === 0) {
188053
- const untaggedUpperBound = inMemoryTail ? estimateUntaggedInMemoryTailUpperBound(db, sessionId, inMemoryTail, taggerFloor) : 0;
188635
+ const untaggedUpperBound = resolvedInMemoryTail ? estimateUntaggedInMemoryTailUpperBound(db, sessionId, resolvedInMemoryTail, taggerFloor) : 0;
188054
188636
  const eligibleUpperBound = persistedBound + untaggedUpperBound;
188055
188637
  if (eligibleUpperBound < triggerBudget) {
188056
- const memorySuffix = inMemoryTail ? ` (persisted=${persistedBound}, untagged-memory≤${untaggedUpperBound})` : "";
188638
+ const memorySuffix = resolvedInMemoryTail ? ` (persisted=${persistedBound}, untagged-memory≤${untaggedUpperBound})` : "";
188057
188639
  sessionLog(sessionId, `compartment trigger: cheap-skip at ${usage.percentage.toFixed(1)}% (below proactive floor ${proactiveFloorForGate}%) — live-tail upper bound ${eligibleUpperBound}${memorySuffix} < triggerBudget ${triggerBudget}; no size trigger possible, skipped full raw read`);
188058
188640
  return { shouldFire: false };
188059
188641
  }
@@ -188062,7 +188644,16 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
188062
188644
  sessionLog(sessionId, `compartment trigger: cheap-gate skipped (falling through to full read): ${error51 instanceof Error ? error51.message : String(error51)}`);
188063
188645
  }
188064
188646
  }
188065
- const tailInfo = getUnsummarizedTailInfo(db, sessionId, triggerBudget, usage, executeThresholdPercentage, contextLimit, inMemoryTail, taggerFloor);
188647
+ if (lazyInMemoryTail) {
188648
+ try {
188649
+ const policyVersion = loadProtectedTailMeta(db, sessionId).protectedTailPolicyVersion;
188650
+ if (policyVersion >= 3)
188651
+ resolvedInMemoryTail = lazyInMemoryTail();
188652
+ } catch {
188653
+ resolvedInMemoryTail = undefined;
188654
+ }
188655
+ }
188656
+ const tailInfo = getUnsummarizedTailInfo(db, sessionId, triggerBudget, usage, executeThresholdPercentage, contextLimit, resolvedInMemoryTail, taggerFloor);
188066
188657
  if (!tailInfo.hasNewRawHistory) {
188067
188658
  try {
188068
188659
  const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
@@ -188089,11 +188680,11 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
188089
188680
  }
188090
188681
  const scale = usage.percentage >= BLOCK_UNTIL_DONE_PERCENTAGE ? 0.25 : 0.5;
188091
188682
  const scaledBoundary = withRawSessionMessageCache(() => {
188092
- if (inMemoryTail) {
188683
+ if (resolvedInMemoryTail) {
188093
188684
  primeInMemoryTailRawMessageCache({
188094
188685
  sessionId,
188095
- messages: inMemoryTail.messages,
188096
- absoluteMessageCount: inMemoryTail.absoluteMessageCount
188686
+ messages: resolvedInMemoryTail.messages,
188687
+ absoluteMessageCount: resolvedInMemoryTail.absoluteMessageCount
188097
188688
  });
188098
188689
  }
188099
188690
  return resolveOpenCodeProtectedTailBoundary({
@@ -188380,12 +188971,26 @@ function computeBudgetPressure(compartments, historyBudget) {
188380
188971
 
188381
188972
  // ../plugin/src/hooks/magic-context/decay-render.ts
188382
188973
  var DEFAULT_HISTORY_BUDGET_TOKENS = 60000;
188383
- function escapeXmlAttr2(s) {
188384
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
188385
- }
188386
188974
  function escapeXmlContent2(s) {
188387
188975
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
188388
188976
  }
188977
+ function formatDateRange(startDate, endDate) {
188978
+ if (!startDate || !endDate)
188979
+ return "";
188980
+ if (startDate === endDate)
188981
+ return startDate;
188982
+ if (startDate.slice(0, 7) === endDate.slice(0, 7))
188983
+ return `${startDate}→${endDate.slice(8)}`;
188984
+ return `${startDate}→${endDate}`;
188985
+ }
188986
+ function compartmentHeading(c) {
188987
+ const dateRange = formatDateRange(c.startDate, c.endDate);
188988
+ const dateSegment = dateRange ? ` · ${dateRange}` : "";
188989
+ return `## ${c.startMessage}-${c.endMessage}${dateSegment} · ${c.title}`;
188990
+ }
188991
+ function guardCompartmentBody(body) {
188992
+ return body.replace(/^## /gm, " ## ");
188993
+ }
188389
188994
  function isTieredRow(c) {
188390
188995
  return typeof c.p1 === "string" && c.p1.length > 0;
188391
188996
  }
@@ -188415,25 +189020,22 @@ function renderCompartmentAtTier(c, tier2) {
188415
189020
  return renderOneCompartment(c, tier2);
188416
189021
  }
188417
189022
  function renderOneCompartment(c, tier2) {
188418
- const baseAttrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr2(c.title)}"`;
188419
189023
  if (tier2 >= 5)
188420
189024
  return "";
189025
+ const heading = compartmentHeading(c);
188421
189026
  if (c.legacy === 1 || !isTieredRow(c)) {
188422
189027
  const flat = (c.content ?? "").trim();
188423
189028
  if (tier2 >= 4 || flat.length === 0)
188424
- return `<compartment ${baseAttrs} />`;
188425
- return [
188426
- `<compartment ${baseAttrs}>`,
188427
- escapeXmlContent2(legacyBodyForTier(flat, tier2)),
188428
- "</compartment>"
188429
- ].join(`
188430
- `);
189029
+ return heading;
189030
+ const body2 = guardCompartmentBody(escapeXmlContent2(legacyBodyForTier(flat, tier2)));
189031
+ return `${heading}
189032
+ ${body2}`;
188431
189033
  }
188432
189034
  const body = tierBody(c, tier2);
188433
189035
  if (body.length === 0)
188434
- return `<compartment ${baseAttrs} />`;
188435
- return [`<compartment ${baseAttrs}>`, escapeXmlContent2(body), "</compartment>"].join(`
188436
- `);
189036
+ return heading;
189037
+ return `${heading}
189038
+ ${guardCompartmentBody(escapeXmlContent2(body))}`;
188437
189039
  }
188438
189040
  function computeTiers(compartments, historyBudgetTokens) {
188439
189041
  const v2Compartments = compartments.map((c, originalIndex) => ({ c, originalIndex })).filter(({ c }) => c.legacy !== 1);
@@ -188556,33 +189158,9 @@ function getVisibleMemoryIds(db, sessionId) {
188556
189158
  }
188557
189159
  }
188558
189160
  function renderMemoryBlock(memories) {
188559
- const byCategory = new Map;
188560
- for (const m of memories) {
188561
- const existing = byCategory.get(m.category);
188562
- if (existing) {
188563
- existing.push(m);
188564
- } else {
188565
- byCategory.set(m.category, [m]);
188566
- }
188567
- }
188568
- const sections = [];
188569
- for (const category of CATEGORY_PRIORITY) {
188570
- const categoryMemories = byCategory.get(category);
188571
- if (!categoryMemories || categoryMemories.length === 0) {
188572
- continue;
188573
- }
188574
- sections.push(`<${category}>`, ...categoryMemories.map((m) => `- ${escapeXmlContent(m.content)}`), `</${category}>`);
188575
- }
188576
- if (sections.length === 0) {
188577
- return null;
188578
- }
188579
- return `<project-memory>
188580
- ${sections.join(`
188581
- `)}
188582
- </project-memory>`;
189161
+ return renderMemoryBlockV2(memories) || null;
188583
189162
  }
188584
189163
  var DEFAULT_MEMORY_BUDGET_TOKENS = 8000;
188585
- var MEMORY_BLOCK_WRAPPER_TOKENS = 6;
188586
189164
  var DEFAULT_USER_PROFILE_BUDGET_TOKENS = 4000;
188587
189165
  function memoryCanonicalIdentity(memory, workspace) {
188588
189166
  return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
@@ -188600,11 +189178,18 @@ function memorySelectionOrder(left, right) {
188600
189178
  return left.id - right.id;
188601
189179
  }
188602
189180
  function memoryRenderOrder(left, right) {
188603
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
188604
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
188605
- const categoryDiff = aPriority - bPriority;
188606
- if (categoryDiff !== 0)
188607
- return categoryDiff;
189181
+ const leftPriority = V2_MEMORY_CATEGORIES.indexOf(left.category);
189182
+ const rightPriority = V2_MEMORY_CATEGORIES.indexOf(right.category);
189183
+ if (leftPriority >= 0 || rightPriority >= 0) {
189184
+ if (leftPriority < 0)
189185
+ return 1;
189186
+ if (rightPriority < 0)
189187
+ return -1;
189188
+ if (leftPriority !== rightPriority)
189189
+ return leftPriority - rightPriority;
189190
+ } else if (left.category !== right.category) {
189191
+ return left.category < right.category ? -1 : 1;
189192
+ }
188608
189193
  return left.id - right.id;
188609
189194
  }
188610
189195
  var maxCompartmentSeqStatements = new WeakMap;
@@ -188615,13 +189200,13 @@ var newCompartmentStatements = new WeakMap;
188615
189200
  function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
188616
189201
  const selectionOrder = [...memories].sort(memorySelectionOrder);
188617
189202
  const selected = [];
188618
- let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
189203
+ const accounting = createMemoryBlockAccounting(renderOptions);
188619
189204
  for (const memory of selectionOrder) {
188620
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
188621
- if (usedTokens + memoryTokens > budgetTokens)
189205
+ const cost = accounting.candidateCost(memory);
189206
+ if (accounting.usedTokens + cost > budgetTokens)
188622
189207
  continue;
189208
+ accounting.admit(memory, cost);
188623
189209
  selected.push(memory);
188624
- usedTokens += memoryTokens;
188625
189210
  }
188626
189211
  if (selected.length < memories.length) {
188627
189212
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
@@ -188635,23 +189220,22 @@ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, work
188635
189220
  }
188636
189221
  const selected = [];
188637
189222
  const selectedIds = new Set;
188638
- let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
188639
- const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
189223
+ const accounting = createMemoryBlockAccounting(renderOptions);
188640
189224
  const trySelect = (memory) => {
188641
189225
  if (selectedIds.has(memory.id))
188642
189226
  return false;
188643
- const tokens = tokenCost(memory);
188644
- if (usedTokens + tokens > budgetTokens)
189227
+ const cost = accounting.candidateCost(memory);
189228
+ if (accounting.usedTokens + cost > budgetTokens)
188645
189229
  return false;
188646
189230
  selected.push(memory);
188647
189231
  selectedIds.add(memory.id);
188648
- usedTokens += tokens;
189232
+ accounting.admit(memory, cost);
188649
189233
  return true;
188650
189234
  };
188651
189235
  for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
188652
189236
  trySelect(memory);
188653
189237
  }
188654
- const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
189238
+ const remainingAfterPermanent = Math.max(0, budgetTokens - accounting.usedTokens);
188655
189239
  const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
188656
189240
  const byIdentity = new Map;
188657
189241
  for (const memory of memories) {
@@ -188670,15 +189254,15 @@ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, work
188670
189254
  for (const memory of candidates) {
188671
189255
  if (selectedIds.has(memory.id))
188672
189256
  continue;
188673
- const tokens = tokenCost(memory);
188674
- if (memberTokens + tokens > floorTokens)
189257
+ const cost = accounting.candidateCost(memory);
189258
+ if (memberTokens + cost > floorTokens)
188675
189259
  continue;
188676
- if (usedTokens + tokens > budgetTokens)
189260
+ if (accounting.usedTokens + cost > budgetTokens)
188677
189261
  continue;
188678
189262
  selected.push(memory);
188679
189263
  selectedIds.add(memory.id);
188680
- usedTokens += tokens;
188681
- memberTokens += tokens;
189264
+ accounting.admit(memory, cost);
189265
+ memberTokens += cost;
188682
189266
  }
188683
189267
  }
188684
189268
  const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
@@ -188702,17 +189286,55 @@ function trimUserMemoriesToBudget(memories, budgetTokens) {
188702
189286
  }
188703
189287
  return selected;
188704
189288
  }
189289
+ function createMemoryBlockAccounting(renderOptions) {
189290
+ const seenCategories = new Set;
189291
+ const categoryCost = new Map;
189292
+ return {
189293
+ usedTokens: estimateTokens(`<project-memory>
189294
+ </project-memory>`),
189295
+ candidateCost(memory) {
189296
+ const line = renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id));
189297
+ let cost = estimateTokens(`${line}
189298
+ `);
189299
+ if (!seenCategories.has(memory.category)) {
189300
+ let tags = categoryCost.get(memory.category);
189301
+ if (tags === undefined) {
189302
+ tags = estimateTokens(`<${escapeXmlAttr(memory.category)}>
189303
+ </${escapeXmlAttr(memory.category)}>
189304
+ `);
189305
+ categoryCost.set(memory.category, tags);
189306
+ }
189307
+ cost += tags;
189308
+ }
189309
+ return cost;
189310
+ },
189311
+ admit(memory, cost) {
189312
+ this.usedTokens += cost;
189313
+ seenCategories.add(memory.category);
189314
+ }
189315
+ };
189316
+ }
188705
189317
  function renderMemoryLineV2(memory, sourceName) {
188706
- const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
188707
- return ` <memory id="${memory.id}" category="${escapeXmlAttr(memory.category)}"${sourceAttr} importance="${memory.importance ?? 50}">${escapeXmlContent(memory.content)}</memory>`;
189318
+ const source = sourceName ? ` [${escapeXmlContent(sourceName)}]` : "";
189319
+ return `#${memory.id}${source}: ${escapeXmlContent(memory.content)}`;
188708
189320
  }
188709
189321
  function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
188710
189322
  if (memories.length === 0)
188711
189323
  return "";
189324
+ const ordered = [...memories].sort(memoryRenderOrder);
188712
189325
  const lines = [`<${wrapper}>`];
188713
- for (const memory of memories) {
189326
+ let openCategory;
189327
+ for (const memory of ordered) {
189328
+ if (memory.category !== openCategory) {
189329
+ if (openCategory !== undefined)
189330
+ lines.push(`</${escapeXmlAttr(openCategory)}>`);
189331
+ openCategory = memory.category;
189332
+ lines.push(`<${escapeXmlAttr(openCategory)}>`);
189333
+ }
188714
189334
  lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
188715
189335
  }
189336
+ if (openCategory !== undefined)
189337
+ lines.push(`</${escapeXmlAttr(openCategory)}>`);
188716
189338
  lines.push(`</${wrapper}>`);
188717
189339
  return lines.join(`
188718
189340
  `);
@@ -189006,6 +189628,7 @@ function readUint32BE(b, offset) {
189006
189628
  function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
189007
189629
  const skipPrefixInjection = options.skipPrefixInjection === true;
189008
189630
  const targets = new Map;
189631
+ const timing = options.onTiming ? { identity: 0, prefix: 0, targets: 0, tokenCounting: 0 } : undefined;
189009
189632
  const toolAggregates = new Map;
189010
189633
  const openToolAggregateKeysByCallId = new Map;
189011
189634
  let activeToolResultRun;
@@ -189015,6 +189638,7 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
189015
189638
  continue;
189016
189639
  activeToolResultRun = undefined;
189017
189640
  const messageId = message.info.id;
189641
+ const reuseIdentity = messageId !== undefined && options.reuseMessageIds?.has(messageId) === true;
189018
189642
  let textOrdinal = 0;
189019
189643
  const parts = message.parts;
189020
189644
  for (let partIndex = 0;partIndex < parts.length; partIndex += 1) {
@@ -189040,7 +189664,10 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
189040
189664
  db,
189041
189665
  targets,
189042
189666
  skipPrefixInjection,
189043
- entryFingerprint: options.entryFingerprintByMessageId?.get(messageId) ?? null
189667
+ entryFingerprint: options.entryFingerprintByMessageId?.get(messageId) ?? null,
189668
+ reuseIdentity,
189669
+ timing,
189670
+ textTokenCache: options.textTokenCache
189044
189671
  });
189045
189672
  textOrdinal += 1;
189046
189673
  continue;
@@ -189050,12 +189677,8 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
189050
189677
  activeToolResultRun = undefined;
189051
189678
  continue;
189052
189679
  }
189680
+ const identityStart = timing ? performance.now() : 0;
189053
189681
  const callId = part.id;
189054
- const text = part.getText() ?? "";
189055
- const toolByteSize = getToolPartByteSize(part, text);
189056
- const toolTokenCount = getToolPartTokenCount(part, text);
189057
- const meta3 = part.getToolMetadata();
189058
- const inputTokenCount = meta3.inputTokenCount;
189059
189682
  if (typeof callId !== "string" || callId.length === 0) {
189060
189683
  activeToolResultRun = undefined;
189061
189684
  tagToolPart({
@@ -189068,7 +189691,9 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
189068
189691
  tagger,
189069
189692
  db,
189070
189693
  targets,
189071
- skipPrefixInjection
189694
+ skipPrefixInjection,
189695
+ reuseIdentity,
189696
+ timing
189072
189697
  });
189073
189698
  continue;
189074
189699
  }
@@ -189084,79 +189709,234 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
189084
189709
  const aggregateKey = existingKey ?? makeToolCompositeKey(messageId, callId);
189085
189710
  const existing = toolAggregates.get(aggregateKey);
189086
189711
  if (existing) {
189087
- existing.occurrences.push({
189088
- message,
189712
+ existing.occurrences.push({ message, part, kind: part.kind });
189713
+ const canReuseIdentity = reuseIdentity && existing.identityReusable;
189714
+ let text2 = "";
189715
+ if (canReuseIdentity) {
189716
+ if (part.kind === "tool_result") {
189717
+ text2 = part.getText() ?? "";
189718
+ applyGrownToolResultAccounting({
189719
+ db,
189720
+ sessionId,
189721
+ tagger,
189722
+ aggregate: existing,
189723
+ byteSize: getToolPartByteSize(part, text2),
189724
+ part,
189725
+ text: text2,
189726
+ timing,
189727
+ tokenCache: options.toolTokenCache,
189728
+ tokenCacheKey: aggregateKey
189729
+ });
189730
+ }
189731
+ if (timing)
189732
+ timing.identity += performance.now() - identityStart;
189733
+ } else {
189734
+ const accounting = readAggregateToolAccounting(part, timing, options.toolTokenCache, aggregateKey);
189735
+ text2 = accounting.text;
189736
+ if (part.kind === "tool_result") {
189737
+ applyGrownToolResultAccounting({
189738
+ db,
189739
+ sessionId,
189740
+ tagger,
189741
+ aggregate: existing,
189742
+ byteSize: accounting.byteSize,
189743
+ part,
189744
+ text: text2,
189745
+ timing,
189746
+ knownTokenCount: accounting.tokenCount
189747
+ });
189748
+ }
189749
+ if (existing.toolName === null && accounting.toolName) {
189750
+ existing.toolName = accounting.toolName;
189751
+ }
189752
+ if (existing.inputByteSize === 0 && part.kind === "tool_use" && accounting.inputByteSize > 0) {
189753
+ existing.inputByteSize = accounting.inputByteSize;
189754
+ updateTagInputByteSize(db, sessionId, existing.tagId, accounting.inputByteSize);
189755
+ }
189756
+ if (existing.inputTokenCount === null && part.kind === "tool_use" && accounting.inputTokenCount > 0) {
189757
+ existing.inputTokenCount = accounting.inputTokenCount;
189758
+ updateTagInputTokenCount(db, sessionId, existing.tagId, accounting.inputTokenCount);
189759
+ }
189760
+ syncToolAggregateAccounting(tagger, sessionId, existing);
189761
+ if (timing)
189762
+ timing.identity += performance.now() - identityStart;
189763
+ }
189764
+ existing.identityReusable &&= reuseIdentity;
189765
+ applyToolPrefixAndTarget({
189766
+ skipPrefixInjection,
189089
189767
  part,
189090
- kind: part.kind
189768
+ text: text2,
189769
+ tagId: existing.tagId,
189770
+ aggregate: existing,
189771
+ targets,
189772
+ timing
189091
189773
  });
189092
- if (part.kind === "tool_result" && toolByteSize > existing.maxByteSize) {
189093
- existing.maxByteSize = toolByteSize;
189094
- existing.maxTokenCount = toolTokenCount;
189095
- updateTagByteSize(db, sessionId, existing.tagId, toolByteSize);
189096
- updateTagTokenCount(db, sessionId, existing.tagId, toolTokenCount);
189097
- }
189098
- if (existing.toolName === null && meta3.toolName) {
189099
- existing.toolName = meta3.toolName;
189100
- }
189101
- if (existing.inputByteSize === 0 && part.kind === "tool_use" && meta3.inputByteSize > 0) {
189102
- existing.inputByteSize = meta3.inputByteSize;
189103
- updateTagInputByteSize(db, sessionId, existing.tagId, meta3.inputByteSize);
189104
- }
189105
- if (!existing.inputTokenStored && part.kind === "tool_use" && inputTokenCount > 0) {
189106
- existing.inputTokenStored = true;
189107
- updateTagInputTokenCount(db, sessionId, existing.tagId, inputTokenCount);
189108
- }
189109
- if (!skipPrefixInjection && part.kind === "tool_result") {
189110
- part.setText(prependTag(existing.tagId, text));
189111
- }
189112
- targets.set(existing.tagId, buildAggregateTarget(existing.tagId, existing.occurrences));
189113
189774
  if (part.kind === "tool_result") {
189114
189775
  markToolAggregateResolved(callId, aggregateKey, openToolAggregateKeysByCallId);
189115
189776
  activeToolResultRun = { callId, aggregateKey };
189116
189777
  }
189778
+ continue;
189779
+ }
189780
+ const reusableTagId = reuseIdentity ? tagger.getToolTag(sessionId, callId, messageId) : undefined;
189781
+ const reusableAccounting = reuseIdentity ? tagger.getToolTagAccounting(sessionId, callId, messageId) : undefined;
189782
+ let aggregate;
189783
+ let text = "";
189784
+ if (reusableTagId !== undefined && reusableAccounting !== undefined) {
189785
+ aggregate = {
189786
+ callId,
189787
+ tagId: reusableTagId,
189788
+ identityReusable: true,
189789
+ occurrences: [{ message, part, kind: part.kind }],
189790
+ maxByteSize: reusableAccounting.byteSize,
189791
+ maxTokenCount: reusableAccounting.tokenCount ?? 0,
189792
+ toolName: null,
189793
+ inputByteSize: reusableAccounting.inputByteSize,
189794
+ inputTokenCount: reusableAccounting.inputTokenCount
189795
+ };
189796
+ if (part.kind === "tool_result") {
189797
+ text = part.getText() ?? "";
189798
+ applyGrownToolResultAccounting({
189799
+ db,
189800
+ sessionId,
189801
+ tagger,
189802
+ aggregate,
189803
+ byteSize: getToolPartByteSize(part, text),
189804
+ part,
189805
+ text,
189806
+ timing,
189807
+ tokenCache: options.toolTokenCache,
189808
+ tokenCacheKey: aggregateKey
189809
+ });
189810
+ }
189811
+ if (timing)
189812
+ timing.identity += performance.now() - identityStart;
189117
189813
  } else {
189118
- const outputByteSize = part.kind === "tool_result" ? toolByteSize : 0;
189119
- const outputTokenCount = part.kind === "tool_result" ? toolTokenCount : 0;
189120
- const firstInputTokenCount = part.kind === "tool_use" ? inputTokenCount : 0;
189121
- const tagId = tagger.assignToolTag(sessionId, callId, messageId, outputByteSize, db, 0, meta3.toolName ?? null, meta3.inputByteSize, () => ({
189814
+ const accounting = readAggregateToolAccounting(part, timing, options.toolTokenCache, aggregateKey);
189815
+ text = accounting.text;
189816
+ const outputByteSize = part.kind === "tool_result" ? accounting.byteSize : 0;
189817
+ const outputTokenCount = part.kind === "tool_result" ? accounting.tokenCount : 0;
189818
+ const firstInputTokenCount = part.kind === "tool_use" ? accounting.inputTokenCount : 0;
189819
+ const tagId = tagger.assignToolTag(sessionId, callId, messageId, outputByteSize, db, 0, accounting.toolName, accounting.inputByteSize, () => ({
189122
189820
  tokenCount: outputTokenCount,
189123
189821
  inputTokenCount: firstInputTokenCount,
189124
189822
  reasoningTokenCount: null
189125
189823
  }));
189126
- const aggregate = {
189824
+ const persistedAccounting = tagger.getToolTagAccounting(sessionId, callId, messageId);
189825
+ aggregate = {
189127
189826
  callId,
189128
189827
  tagId,
189129
- occurrences: [
189130
- {
189131
- message,
189132
- part,
189133
- kind: part.kind
189134
- }
189135
- ],
189136
- maxByteSize: outputByteSize,
189137
- maxTokenCount: outputTokenCount,
189138
- toolName: meta3.toolName ?? null,
189139
- inputByteSize: part.kind === "tool_use" ? meta3.inputByteSize : 0,
189140
- inputTokenStored: part.kind === "tool_use" && firstInputTokenCount > 0
189828
+ identityReusable: false,
189829
+ occurrences: [{ message, part, kind: part.kind }],
189830
+ maxByteSize: persistedAccounting?.byteSize ?? outputByteSize,
189831
+ maxTokenCount: persistedAccounting?.tokenCount ?? outputTokenCount,
189832
+ toolName: accounting.toolName,
189833
+ inputByteSize: persistedAccounting?.inputByteSize ?? (part.kind === "tool_use" ? accounting.inputByteSize : 0),
189834
+ inputTokenCount: persistedAccounting?.inputTokenCount ?? (part.kind === "tool_use" ? firstInputTokenCount : null)
189141
189835
  };
189142
- toolAggregates.set(aggregateKey, aggregate);
189143
- if (part.kind === "tool_use") {
189144
- openToolAggregateKeysByCallId.set(callId, [...pendingKeys, aggregateKey]);
189145
- }
189146
- if (!skipPrefixInjection && part.kind === "tool_result") {
189147
- part.setText(prependTag(tagId, text));
189148
- }
189149
- targets.set(tagId, buildAggregateTarget(tagId, aggregate.occurrences));
189150
189836
  if (part.kind === "tool_result") {
189151
- markToolAggregateResolved(callId, aggregateKey, openToolAggregateKeysByCallId);
189152
- activeToolResultRun = { callId, aggregateKey };
189837
+ applyGrownToolResultAccounting({
189838
+ db,
189839
+ sessionId,
189840
+ tagger,
189841
+ aggregate,
189842
+ byteSize: accounting.byteSize,
189843
+ part,
189844
+ text,
189845
+ timing,
189846
+ knownTokenCount: accounting.tokenCount
189847
+ });
189153
189848
  }
189849
+ syncToolAggregateAccounting(tagger, sessionId, aggregate);
189850
+ if (timing)
189851
+ timing.identity += performance.now() - identityStart;
189852
+ }
189853
+ toolAggregates.set(aggregateKey, aggregate);
189854
+ if (part.kind === "tool_use") {
189855
+ openToolAggregateKeysByCallId.set(callId, [...pendingKeys, aggregateKey]);
189856
+ }
189857
+ applyToolPrefixAndTarget({
189858
+ skipPrefixInjection,
189859
+ part,
189860
+ text,
189861
+ tagId: aggregate.tagId,
189862
+ aggregate,
189863
+ targets,
189864
+ timing
189865
+ });
189866
+ if (part.kind === "tool_result") {
189867
+ markToolAggregateResolved(callId, aggregateKey, openToolAggregateKeysByCallId);
189868
+ activeToolResultRun = { callId, aggregateKey };
189154
189869
  }
189155
189870
  }
189156
189871
  }
189157
189872
  }
189873
+ if (timing && options.onTiming) {
189874
+ options.onTiming("identity", timing.identity);
189875
+ options.onTiming("prefix", timing.prefix);
189876
+ options.onTiming("targets", timing.targets);
189877
+ options.onTiming("tokenCounting", timing.tokenCounting);
189878
+ }
189158
189879
  return { targets };
189159
189880
  }
189881
+ function syncToolAggregateAccounting(tagger, sessionId, aggregate) {
189882
+ tagger.setToolTagAccounting(sessionId, aggregate.tagId, {
189883
+ byteSize: aggregate.maxByteSize,
189884
+ tokenCount: aggregate.maxTokenCount,
189885
+ inputByteSize: aggregate.inputByteSize,
189886
+ inputTokenCount: aggregate.inputTokenCount
189887
+ });
189888
+ }
189889
+ function applyGrownToolResultAccounting(args) {
189890
+ if (args.byteSize <= args.aggregate.maxByteSize)
189891
+ return;
189892
+ let tokenCount = args.knownTokenCount;
189893
+ if (tokenCount !== undefined && args.tokenCacheKey) {
189894
+ args.tokenCache?.set(args.tokenCacheKey, { text: args.text, tokenCount });
189895
+ }
189896
+ if (tokenCount === undefined) {
189897
+ const tokenStart = args.timing ? performance.now() : 0;
189898
+ tokenCount = getCachedToolPartTokenCount(args.part, args.text, args.tokenCache, args.tokenCacheKey);
189899
+ if (args.timing)
189900
+ args.timing.tokenCounting += performance.now() - tokenStart;
189901
+ }
189902
+ args.aggregate.maxByteSize = args.byteSize;
189903
+ args.aggregate.maxTokenCount = tokenCount;
189904
+ updateTagByteSize(args.db, args.sessionId, args.aggregate.tagId, args.byteSize);
189905
+ updateTagTokenCount(args.db, args.sessionId, args.aggregate.tagId, tokenCount);
189906
+ syncToolAggregateAccounting(args.tagger, args.sessionId, args.aggregate);
189907
+ }
189908
+ function readAggregateToolAccounting(part, timing, tokenCache, tokenCacheKey) {
189909
+ const text = part.getText() ?? "";
189910
+ const byteSize2 = getToolPartByteSize(part, text);
189911
+ const metadata = part.getToolMetadata();
189912
+ let tokenCount = 0;
189913
+ if (part.kind === "tool_result") {
189914
+ const tokenStart = timing ? performance.now() : 0;
189915
+ tokenCount = getCachedToolPartTokenCount(part, text, tokenCache, tokenCacheKey);
189916
+ if (timing)
189917
+ timing.tokenCounting += performance.now() - tokenStart;
189918
+ }
189919
+ return {
189920
+ text,
189921
+ byteSize: byteSize2,
189922
+ tokenCount,
189923
+ toolName: metadata.toolName ?? null,
189924
+ inputByteSize: metadata.inputByteSize,
189925
+ inputTokenCount: metadata.inputTokenCount
189926
+ };
189927
+ }
189928
+ function applyToolPrefixAndTarget(args) {
189929
+ if (!args.skipPrefixInjection && args.part.kind === "tool_result") {
189930
+ const prefixStart = args.timing ? performance.now() : 0;
189931
+ args.part.setText(prependTag(args.tagId, args.text));
189932
+ if (args.timing)
189933
+ args.timing.prefix += performance.now() - prefixStart;
189934
+ }
189935
+ const targetStart = args.timing ? performance.now() : 0;
189936
+ args.targets.set(args.tagId, buildAggregateTarget(args.tagId, args.aggregate.occurrences));
189937
+ if (args.timing)
189938
+ args.timing.targets += performance.now() - targetStart;
189939
+ }
189160
189940
  function findLastUnresolvedToolAggregateKey(pendingKeys, toolAggregates) {
189161
189941
  for (let i = pendingKeys.length - 1;i >= 0; i -= 1) {
189162
189942
  const key = pendingKeys[i];
@@ -189207,6 +189987,15 @@ function getToolPartTokenCount(part, text) {
189207
189987
  }
189208
189988
  return 0;
189209
189989
  }
189990
+ function getCachedToolPartTokenCount(part, text, cache, cacheKey2) {
189991
+ const cached2 = cacheKey2 ? cache?.get(cacheKey2) : undefined;
189992
+ if (cached2?.text === text)
189993
+ return cached2.tokenCount;
189994
+ const tokenCount = getToolPartTokenCount(part, text);
189995
+ if (cacheKey2)
189996
+ cache?.set(cacheKey2, { text, tokenCount });
189997
+ return tokenCount;
189998
+ }
189210
189999
  function getNonTextToolResultByteSize(part) {
189211
190000
  const raw = part.rawByteSize?.();
189212
190001
  if (typeof raw === "number" && raw > 0)
@@ -189227,39 +190016,97 @@ function isRecord2(value) {
189227
190016
  return typeof value === "object" && value !== null;
189228
190017
  }
189229
190018
  function tagTextPart(args) {
190019
+ const identityStart = args.timing ? performance.now() : 0;
189230
190020
  const text = args.part.getText() ?? "";
189231
190021
  const contentId = `${args.messageId}:p${args.textOrdinal}`;
189232
- const tagId = args.tagger.assignTag(args.sessionId, contentId, "message", byteSize(text), args.db, 0, null, 0, args.entryFingerprint, () => ({
189233
- tokenCount: estimateTagTextTokens(stripTagPrefix(text)),
189234
- inputTokenCount: null,
189235
- reasoningTokenCount: null
189236
- }));
190022
+ const reusableTagId = args.reuseIdentity ? args.tagger.getTag(args.sessionId, contentId, "message") : undefined;
190023
+ if (reusableTagId !== undefined) {
190024
+ if (args.timing)
190025
+ args.timing.identity += performance.now() - identityStart;
190026
+ applyTextPrefixAndTarget(args, reusableTagId, text);
190027
+ return;
190028
+ }
190029
+ const tagId = args.tagger.assignTag(args.sessionId, contentId, "message", byteSize(text), args.db, 0, null, 0, args.entryFingerprint, () => {
190030
+ const tokenStart = args.timing ? performance.now() : 0;
190031
+ const cached2 = args.textTokenCache?.get(contentId);
190032
+ const tokenCount = cached2?.text === text ? cached2.tokenCount : estimateTagTextTokens(stripTagPrefix(text));
190033
+ if (cached2?.text !== text) {
190034
+ args.textTokenCache?.set(contentId, { text, tokenCount });
190035
+ }
190036
+ const counts = {
190037
+ tokenCount,
190038
+ inputTokenCount: null,
190039
+ reasoningTokenCount: null
190040
+ };
190041
+ if (args.timing)
190042
+ args.timing.tokenCounting += performance.now() - tokenStart;
190043
+ return counts;
190044
+ });
189237
190045
  const sourceContent = stripTagPrefix(text);
189238
190046
  if (sourceContent.trim().length > 0) {
189239
190047
  saveSourceContent(args.db, args.sessionId, tagId, sourceContent);
189240
190048
  }
190049
+ if (args.timing)
190050
+ args.timing.identity += performance.now() - identityStart;
190051
+ applyTextPrefixAndTarget(args, tagId, text);
190052
+ }
190053
+ function applyTextPrefixAndTarget(args, tagId, text) {
189241
190054
  if (!args.skipPrefixInjection) {
190055
+ const prefixStart = args.timing ? performance.now() : 0;
189242
190056
  args.part.setText(prependTag(tagId, text));
190057
+ if (args.timing)
190058
+ args.timing.prefix += performance.now() - prefixStart;
189243
190059
  }
190060
+ const targetStart = args.timing ? performance.now() : 0;
189244
190061
  args.targets.set(tagId, buildTextTarget(args.part, args.message));
190062
+ if (args.timing)
190063
+ args.timing.targets += performance.now() - targetStart;
189245
190064
  }
189246
190065
  function tagToolPart(args) {
190066
+ const identityStart = args.timing ? performance.now() : 0;
189247
190067
  const stableId = args.part.id;
189248
190068
  const contentId = stableId ?? `${args.messageId}:t${args.partIndex}`;
190069
+ const reusableTagId = args.reuseIdentity ? args.tagger.getToolTag(args.sessionId, contentId, contentId) : undefined;
190070
+ if (reusableTagId !== undefined) {
190071
+ const text2 = args.part.kind === "tool_result" ? args.part.getText() ?? "" : "";
190072
+ if (args.timing)
190073
+ args.timing.identity += performance.now() - identityStart;
190074
+ applySingleToolPrefixAndTarget(args, reusableTagId, text2);
190075
+ return;
190076
+ }
189249
190077
  const text = args.part.getText() ?? "";
189250
190078
  const toolByteSize = getToolPartByteSize(args.part, text);
189251
- const toolTokenCount = getToolPartTokenCount(args.part, text);
189252
190079
  const meta3 = args.part.getToolMetadata();
189253
- const tagId = args.tagger.assignToolTag(args.sessionId, contentId, contentId, toolByteSize, args.db, 0, meta3.toolName ?? null, meta3.inputByteSize, () => ({
189254
- tokenCount: toolTokenCount,
189255
- inputTokenCount: meta3.inputTokenCount,
189256
- reasoningTokenCount: null
189257
- }));
190080
+ const tokenStart = args.timing ? performance.now() : 0;
190081
+ const toolTokenCount = getToolPartTokenCount(args.part, text);
190082
+ if (args.timing)
190083
+ args.timing.tokenCounting += performance.now() - tokenStart;
190084
+ const tagId = args.tagger.assignToolTag(args.sessionId, contentId, contentId, toolByteSize, args.db, 0, meta3.toolName ?? null, meta3.inputByteSize, () => {
190085
+ const tokenStart2 = args.timing ? performance.now() : 0;
190086
+ const counts = {
190087
+ tokenCount: toolTokenCount,
190088
+ inputTokenCount: meta3.inputTokenCount,
190089
+ reasoningTokenCount: null
190090
+ };
190091
+ if (args.timing)
190092
+ args.timing.tokenCounting += performance.now() - tokenStart2;
190093
+ return counts;
190094
+ });
190095
+ if (args.timing)
190096
+ args.timing.identity += performance.now() - identityStart;
190097
+ applySingleToolPrefixAndTarget(args, tagId, text);
190098
+ }
190099
+ function applySingleToolPrefixAndTarget(args, tagId, text) {
189258
190100
  if (!args.skipPrefixInjection && args.part.kind === "tool_result") {
189259
- const tagged = prependTag(tagId, text);
189260
- args.part.setText(tagged);
190101
+ const prefixStart = args.timing ? performance.now() : 0;
190102
+ args.part.setText(prependTag(tagId, text));
190103
+ if (args.timing)
190104
+ args.timing.prefix += performance.now() - prefixStart;
189261
190105
  }
190106
+ const targetStart = args.timing ? performance.now() : 0;
189262
190107
  args.targets.set(tagId, buildToolTarget(args.part, args.message, tagId));
190108
+ if (args.timing)
190109
+ args.timing.targets += performance.now() - targetStart;
189263
190110
  }
189264
190111
  function setToolContentOrText(part, content) {
189265
190112
  try {
@@ -190539,6 +191386,7 @@ async function runAutoSearchHintForPi(args) {
190539
191386
  sessionLog(sessionId, "Pi auto-search: strict entry-id resolution failed; replayed persisted decisions only");
190540
191387
  return messages;
190541
191388
  }
191389
+ await args.ensureProjectRegistered?.();
190542
191390
  const writeNoHintAndReconcile = (reason) => {
190543
191391
  const outcome2 = appendAutoSearchHintDecision(db, sessionId, {
190544
191392
  messageId: userMsgId,
@@ -190686,8 +191534,17 @@ function getEntryId(entry) {
190686
191534
  const id = entry.id;
190687
191535
  return typeof id === "string" ? id : null;
190688
191536
  }
190689
- function findEntryIndex(branchEntries, entryId) {
190690
- return branchEntries.findIndex((entry) => getEntryId(entry) === entryId);
191537
+ function findEntryIndex(branchEntries, entryId2) {
191538
+ return branchEntries.findIndex((entry) => getEntryId(entry) === entryId2);
191539
+ }
191540
+
191541
+ // src/context-perf-hooks.ts
191542
+ var timingObserver;
191543
+ function hasPiTransformTimingObserver() {
191544
+ return timingObserver !== undefined;
191545
+ }
191546
+ function recordPiTransformTiming(sample) {
191547
+ timingObserver?.(sample);
190691
191548
  }
190692
191549
 
190693
191550
  // src/ctx-reduce-nudge-pi.ts
@@ -191386,7 +192243,7 @@ init_logger();
191386
192243
  function resolveStableId(msg, index, entryIds) {
191387
192244
  return resolvePiStableId(msg, index, entryIds) ?? "";
191388
192245
  }
191389
- function trimPiMessagesToBoundary(piMessages, entryIds, cutoffMessageId) {
192246
+ function trimPiMessagesToBoundary(piMessages, entryIds, cutoffMessageId, trimMutableEntryIds = false) {
191390
192247
  if (cutoffMessageId.length === 0)
191391
192248
  return 0;
191392
192249
  const effectiveCutoffId = cutoffMessageId.startsWith(SYNTH_USER_ID_PREFIX) ? cutoffMessageId.slice(SYNTH_USER_ID_PREFIX.length) : cutoffMessageId;
@@ -191453,6 +192310,10 @@ function trimPiMessagesToBoundary(piMessages, entryIds, cutoffMessageId) {
191453
192310
  const kept = piMessages.filter((_, index) => !remove.has(index));
191454
192311
  const removed = piMessages.length - kept.length;
191455
192312
  piMessages.splice(0, piMessages.length, ...kept);
192313
+ if (trimMutableEntryIds && Array.isArray(entryIds)) {
192314
+ const keptIds = entryIds.filter((_, index) => !remove.has(index));
192315
+ entryIds.splice(0, entryIds.length, ...keptIds);
192316
+ }
191456
192317
  return removed;
191457
192318
  }
191458
192319
  function toolPairKey(callId, assistantIndex) {
@@ -191563,6 +192424,23 @@ var EMPTY_PI_HARD_SIGNALS = {
191563
192424
  cacheExpired: false,
191564
192425
  lastResponseTime: 0
191565
192426
  };
192427
+ var injectionTokenCountsBySession = new Map;
192428
+ function cachedInjectionTokenCounts(sessionId, m0, m1) {
192429
+ const cached2 = injectionTokenCountsBySession.get(sessionId);
192430
+ if (cached2?.m0 === m0 && cached2.m1 === m1)
192431
+ return cached2;
192432
+ const counts = {
192433
+ m0,
192434
+ m0Tokens: estimateTokens(m0),
192435
+ m1,
192436
+ m1Tokens: m1 === PI_M1_PLACEHOLDER ? 0 : estimateTokens(m1)
192437
+ };
192438
+ injectionTokenCountsBySession.set(sessionId, counts);
192439
+ return counts;
192440
+ }
192441
+ function clearPiInjectionTokenCountCache(sessionId) {
192442
+ injectionTokenCountsBySession.delete(sessionId);
192443
+ }
191566
192444
  function decodeCachedM0(value) {
191567
192445
  if (!value)
191568
192446
  return null;
@@ -191581,6 +192459,20 @@ function getCachedBoundary(db, sessionId) {
191581
192459
  const row = db.prepare("SELECT cached_m0_last_baseline_end_message_id AS boundary FROM session_meta WHERE session_id = ?").get(sessionId);
191582
192460
  return typeof row?.boundary === "string" && row.boundary.length > 0 ? row.boundary : null;
191583
192461
  }
192462
+ function trimPiMessagesToCachedBoundary(db, sessionId, piMessages, entryIds) {
192463
+ const row = db.prepare(`SELECT cached_m0_bytes AS m0, cached_m1_bytes AS m1,
192464
+ cached_m0_last_baseline_end_message_id AS boundary
192465
+ FROM session_meta WHERE session_id = ?`).get(sessionId);
192466
+ if (!row?.m0 || !row.m1 || typeof row.boundary !== "string")
192467
+ return 0;
192468
+ const boundary = row.boundary;
192469
+ if (boundary.length === 0)
192470
+ return 0;
192471
+ const boundaryIsLive = getCompartments(db, sessionId).some((compartment) => compartment.endMessageId === boundary);
192472
+ if (!boundaryIsLive)
192473
+ return 0;
192474
+ return trimPiMessagesToBoundary(piMessages, entryIds, boundary, true);
192475
+ }
191584
192476
  function setCachedBoundary(db, sessionId, boundary) {
191585
192477
  db.prepare("UPDATE session_meta SET cached_m0_last_baseline_end_message_id = ? WHERE session_id = ?").run(boundary, sessionId);
191586
192478
  }
@@ -191650,7 +192542,7 @@ function readCurrentMarkersFromCompartments(db, state, compartments, projectDocs
191650
192542
  function mustMaterializePi(state, db, currentCompartmentsOverride) {
191651
192543
  const meta3 = getOrCreateSessionMeta(db, state.sessionId);
191652
192544
  const currentCompartments = currentCompartmentsOverride ?? getCompartments(db, state.sessionId);
191653
- const current = readCurrentMarkersFromCompartments(db, state, currentCompartments);
192545
+ const current = readCurrentMarkersFromCompartments(db, state, currentCompartments, meta3.cachedM0ProjectDocsHash ?? undefined);
191654
192546
  if (!meta3.cachedM0Bytes)
191655
192547
  return { value: true, reason: "first_render" };
191656
192548
  if (!meta3.cachedM1Bytes)
@@ -192284,8 +193176,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
192284
193176
  const M1_ABSOLUTE_CAP_RATIO = 0.2;
192285
193177
  const m1AbsoluteBudget = (state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS) * M1_ABSOLUTE_CAP_RATIO;
192286
193178
  const m1HasContent = m1 !== PI_M1_PLACEHOLDER;
192287
- const m1Tokens = m1HasContent ? estimateTokens(m1) : 0;
192288
- const m0Tokens = estimateTokens(m0);
193179
+ const { m0Tokens, m1Tokens } = cachedInjectionTokenCounts(state.sessionId, m0, m1);
192289
193180
  const m1OverAbsoluteCap = m1HasContent && m1Tokens > m1AbsoluteBudget;
192290
193181
  if (!materialized && !contentionExhausted && m1Recomputed && recomputeM1ThisPass && (memoryUpdateCount > 40 || m1OverAbsoluteCap || m1HasContent && m0Tokens >= M0_DRIFT_RATIO_FLOOR_TOKENS && m1Tokens > m0Tokens * M1_DRIFT_RATIO)) {
192291
193182
  decision = { value: true, reason: "drift" };
@@ -196711,7 +197602,6 @@ function stripPiDroppedPlaceholderMessages(args) {
196711
197602
  const { db, sessionId, messages, isCacheBusting, stableIdByRef } = args;
196712
197603
  const persistedIds = getStrippedPlaceholderIds(db, sessionId);
196713
197604
  const idsToStrip = new Set(persistedIds);
196714
- let discovered = 0;
196715
197605
  const idOf = (msg, index) => {
196716
197606
  const m = msg && typeof msg === "object" ? msg : undefined;
196717
197607
  const carried = m ? stableIdByRef?.get(m) : undefined;
@@ -196730,15 +197620,36 @@ function stripPiDroppedPlaceholderMessages(args) {
196730
197620
  if (!id)
196731
197621
  continue;
196732
197622
  presentIds?.add(id);
196733
- if (!messageIsPlaceholderOnly(messages[i]))
196734
- continue;
196735
- if (!idsToStrip.has(id)) {
196736
- idsToStrip.add(id);
197623
+ if (messageIsPlaceholderOnly(messages[i]) && !persistedIds.has(id)) {
196737
197624
  discoveredIds.push(id);
196738
- discovered++;
196739
197625
  }
196740
197626
  }
196741
197627
  }
197628
+ const removedIds = [];
197629
+ if (presentIds) {
197630
+ for (const id of persistedIds) {
197631
+ if (!presentIds.has(id))
197632
+ removedIds.push(id);
197633
+ }
197634
+ }
197635
+ let discovered = 0;
197636
+ let pruned = 0;
197637
+ if (discoveredIds.length > 0 || removedIds.length > 0) {
197638
+ const persisted = (args.applyDelta ?? applyStrippedPlaceholderDelta)(db, sessionId, {
197639
+ add: discoveredIds,
197640
+ remove: removedIds
197641
+ });
197642
+ if (persisted) {
197643
+ for (const id of discoveredIds)
197644
+ idsToStrip.add(id);
197645
+ for (const id of removedIds)
197646
+ idsToStrip.delete(id);
197647
+ discovered = discoveredIds.length;
197648
+ pruned = removedIds.length;
197649
+ } else {
197650
+ sessionLog(sessionId, "placeholder strip: persistence failed; leaving newly discovered messages intact");
197651
+ }
197652
+ }
196742
197653
  let removed = 0;
196743
197654
  for (let i = messages.length - 1;i >= 0; i--) {
196744
197655
  const id = idOf(messages[i], i);
@@ -196747,29 +197658,76 @@ function stripPiDroppedPlaceholderMessages(args) {
196747
197658
  messages.splice(i, 1);
196748
197659
  removed++;
196749
197660
  }
196750
- let pruned = 0;
196751
- if (presentIds) {
196752
- const removedIds = [];
196753
- for (const id of persistedIds) {
196754
- if (!presentIds.has(id))
196755
- removedIds.push(id);
196756
- }
196757
- pruned = removedIds.length;
196758
- if (discoveredIds.length > 0 || removedIds.length > 0) {
196759
- applyStrippedPlaceholderDelta(db, sessionId, {
196760
- add: discoveredIds,
196761
- remove: removedIds
196762
- });
196763
- }
196764
- } else if (discoveredIds.length > 0) {
196765
- applyStrippedPlaceholderDelta(db, sessionId, { add: discoveredIds });
196766
- }
196767
197661
  if (removed > 0 || discovered > 0 || pruned > 0) {
196768
197662
  sessionLog(sessionId, `placeholder strip: removed=${removed} discovered=${discovered} pruned=${pruned}`);
196769
197663
  }
196770
197664
  return { removed, discovered };
196771
197665
  }
196772
197666
 
197667
+ // src/strip-processed-images-pi.ts
197668
+ init_logger();
197669
+ function isLargeImagePart(value) {
197670
+ if (!value || typeof value !== "object")
197671
+ return false;
197672
+ const part = value;
197673
+ return part.type === "image" && typeof part.mimeType === "string" && part.mimeType.startsWith("image/") && typeof part.data === "string" && part.data.length > 200;
197674
+ }
197675
+ function stripPiProcessedImages(args) {
197676
+ const frozenIds = getProcessedImageStrippedIds(args.db, args.sessionId);
197677
+ const newlyStrippedIds = [];
197678
+ let hasAssistantResponse = false;
197679
+ if (args.detect && args.watermark > 0) {
197680
+ for (let i = args.messages.length - 1;i >= 0; i--) {
197681
+ const raw = args.messages[i];
197682
+ if (!raw || typeof raw !== "object")
197683
+ continue;
197684
+ const message = raw;
197685
+ if (message.role === "assistant") {
197686
+ hasAssistantResponse = true;
197687
+ continue;
197688
+ }
197689
+ if (message.role !== "user" && message.role !== "toolResult" || !hasAssistantResponse || !Array.isArray(message.content) || !message.content.some(isLargeImagePart)) {
197690
+ continue;
197691
+ }
197692
+ const id = args.stableId(raw, i);
197693
+ if (!id || frozenIds.has(id))
197694
+ continue;
197695
+ const maxTag = args.messageIdToMaxTag.get(id) ?? 0;
197696
+ if (maxTag <= args.watermark)
197697
+ newlyStrippedIds.push(id);
197698
+ }
197699
+ }
197700
+ if (newlyStrippedIds.length > 0) {
197701
+ const persisted = (args.addIds ?? addProcessedImageStrippedIds)(args.db, args.sessionId, newlyStrippedIds);
197702
+ if (persisted) {
197703
+ for (const id of newlyStrippedIds)
197704
+ frozenIds.add(id);
197705
+ } else {
197706
+ newlyStrippedIds.length = 0;
197707
+ sessionLog(args.sessionId, "processed-image strip: persistence failed; leaving newly detected images intact");
197708
+ }
197709
+ }
197710
+ let stripped = 0;
197711
+ for (let i = 0;i < args.messages.length; i++) {
197712
+ const raw = args.messages[i];
197713
+ if (!raw || typeof raw !== "object")
197714
+ continue;
197715
+ const id = args.stableId(raw, i);
197716
+ if (!id || !frozenIds.has(id))
197717
+ continue;
197718
+ const message = raw;
197719
+ if (!Array.isArray(message.content))
197720
+ continue;
197721
+ for (let partIndex = 0;partIndex < message.content.length; partIndex++) {
197722
+ if (!isLargeImagePart(message.content[partIndex]))
197723
+ continue;
197724
+ message.content[partIndex] = { type: "text", text: "" };
197725
+ stripped++;
197726
+ }
197727
+ }
197728
+ return { stripped, newlyStrippedIds };
197729
+ }
197730
+
196773
197731
  // src/system-prompt.ts
196774
197732
  import { createHash as createHash12 } from "node:crypto";
196775
197733
 
@@ -196790,6 +197748,7 @@ Context is managed for you entirely automatically — there's nothing to prune a
196790
197748
  var CTX_NOTE_GUIDANCE = `Use \`ctx_note\` ONLY for genuinely future concerns — something to revisit much later, not work coming up in the next few turns (that's already in your active context) and not active multi-step work (use todos for that). Magic Context preserves your full context across both compaction and restarts, so an upcoming restart or "let's come back to this later" is never a reason to take a note — nothing is lost either way. Notes you do take survive compression and resurface at natural work boundaries (after commits, historian runs, todo completion).`;
196791
197749
  var TOOL_HISTORY_GUIDANCE = `Compressed history intentionally omits tool calls and their outputs — summaries like "I edited file X" are historian records, not patterns to replicate. In the live conversation, older tool calls and their results are cleaned up to save context — you may see your own past messages referencing actions without the corresponding tool call or result visible. This is normal context management. ALWAYS use real tool calls; never simulate, fabricate, or inline tool outputs in your text. If there is no tool result message, the action did not happen. NEVER simulate, hallucinate or claim tool calls, command output, search results, file edits, or diffs in plain text as if they actually occurred.`;
196792
197750
  var MEMORY_GUIDANCE = `Use \`ctx_memory\` for durable project knowledge: write what future sessions must know, update/archive/merge the memories you see in \`<project-memory>\` when they drift. Memories persist across sessions and every new session starts with them.
197751
+ Memories are grouped by category as \`#id: fact\` lines; pass the numeric id to \`ctx_memory\` actions.
196793
197752
  **Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
196794
197753
  - Found a project's source code path after searching → \`ctx_memory(action="write", category="CONFIG_VALUES", content="OpenCode source is at ~/Work/OSS/opencode")\`
196795
197754
  - Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="PROJECT_RULES", content="Always use scripts/release.sh for releases")\`
@@ -196803,7 +197762,7 @@ Use \`ctx_reduce\` to mark spent tagged content as discardable and reclaim space
196803
197762
  Do not announce or narrate \`ctx_reduce\` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
196804
197763
  ${CTX_NOTE_GUIDANCE}
196805
197764
  ${memoryGuidanceBlock(memoryEnabled)}Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
196806
- Use \`ctx_expand\` to recover the raw conversation behind a \`<compartment>\` summary in \`<session-history>\` — pass its \`start\`/\`end\` attributes when the summary is not enough (exact wording, values, error text).
197765
+ Use \`ctx_expand\` to recover the raw conversation behind a summary under a \`## start-end · date · title\` heading inside \`<session-history>\` — pass the heading's start/end range when the summary is not enough (exact wording, values, error text).
196807
197766
  **Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
196808
197767
  - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
196809
197768
  - Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
@@ -196818,7 +197777,7 @@ NEVER drop assistant text messages unless they are exceptionally large. Your con
196818
197777
  Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
196819
197778
  var BASE_INTRO_NO_REDUCE = (memoryEnabled) => `${CTX_NOTE_GUIDANCE}
196820
197779
  ${memoryGuidanceBlock(memoryEnabled)}Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
196821
- Use \`ctx_expand\` to recover the raw conversation behind a \`<compartment>\` summary in \`<session-history>\` — pass its \`start\`/\`end\` attributes when the summary is not enough (exact wording, values, error text).
197780
+ Use \`ctx_expand\` to recover the raw conversation behind a summary under a \`## start-end · date · title\` heading inside \`<session-history>\` — pass the heading's start/end range when the summary is not enough (exact wording, values, error text).
196822
197781
  **Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
196823
197782
  - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
196824
197783
  - Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
@@ -197018,74 +197977,222 @@ function withTimeout(p, ms) {
197018
197977
  }
197019
197978
 
197020
197979
  // src/tokenize-pi-messages.ts
197021
- function tokenizePiMessages(messages) {
197980
+ function tokenizePiMessages(messages, options) {
197022
197981
  let conversation = 0;
197023
197982
  let toolCall = 0;
197983
+ let cacheValidationMs = 0;
197984
+ let bpeMs = 0;
197985
+ const liveIds = options ? new Set : undefined;
197024
197986
  for (const raw of messages) {
197025
197987
  if (!raw || typeof raw !== "object")
197026
197988
  continue;
197027
- const msg = raw;
197028
- const content = msg.content;
197029
- if (msg.role === "user" || msg.role === "assistant") {
197030
- if (typeof content === "string") {
197031
- conversation += estimateTokens(content);
197989
+ const cacheValidationStart = options?.onTiming ? performance.now() : 0;
197990
+ const resolvedStableId = options?.stableId(raw);
197991
+ const stableId = resolvedStableId !== undefined && isTokenCacheSafeMessage(raw) ? resolvedStableId : undefined;
197992
+ const fingerprint = stableId === undefined ? null : buildTokenCacheFingerprint(raw);
197993
+ if (stableId !== undefined && fingerprint !== null) {
197994
+ liveIds?.add(stableId);
197995
+ const cached2 = options?.cache.get(stableId);
197996
+ if (cached2 && tokenCacheFingerprintsEqual(cached2.fingerprint, fingerprint)) {
197997
+ conversation += cached2.counts.conversation;
197998
+ toolCall += cached2.counts.toolCall;
197999
+ cacheValidationMs += performance.now() - cacheValidationStart;
197032
198000
  continue;
197033
198001
  }
197034
- if (!Array.isArray(content))
198002
+ }
198003
+ cacheValidationMs += performance.now() - cacheValidationStart;
198004
+ const bpeStart = options?.onTiming ? performance.now() : 0;
198005
+ const beforeConversation = conversation;
198006
+ const beforeToolCall = toolCall;
198007
+ try {
198008
+ const msg = raw;
198009
+ const content = msg.content;
198010
+ if (msg.role === "user" || msg.role === "assistant") {
198011
+ if (typeof content === "string") {
198012
+ conversation += estimateTokens(content);
198013
+ continue;
198014
+ }
198015
+ if (!Array.isArray(content))
198016
+ continue;
198017
+ for (const part of content) {
198018
+ if (!part || typeof part !== "object")
198019
+ continue;
198020
+ const p = part;
198021
+ switch (p.type) {
198022
+ case "text":
198023
+ if (typeof p.text === "string")
198024
+ conversation += estimateTokens(p.text);
198025
+ if (typeof p.textSignature === "string")
198026
+ conversation += estimateTokens(p.textSignature);
198027
+ break;
198028
+ case "thinking":
198029
+ if (typeof p.thinking === "string")
198030
+ conversation += estimateTokens(p.thinking);
198031
+ if (typeof p.thinkingSignature === "string")
198032
+ conversation += estimateTokens(p.thinkingSignature);
198033
+ break;
198034
+ case "image":
198035
+ conversation += 1200;
198036
+ break;
198037
+ case "toolCall":
198038
+ if (typeof p.name === "string")
198039
+ toolCall += estimateTokens(p.name);
198040
+ if (p.arguments !== undefined) {
198041
+ const s = typeof p.arguments === "string" ? p.arguments : safeJsonStringify2(p.arguments);
198042
+ if (s)
198043
+ toolCall += estimateTokens(s);
198044
+ }
198045
+ break;
198046
+ }
198047
+ }
197035
198048
  continue;
197036
- for (const part of content) {
197037
- if (!part || typeof part !== "object")
198049
+ }
198050
+ if (msg.role === "toolResult") {
198051
+ if (typeof content === "string") {
198052
+ toolCall += estimateTokens(content);
197038
198053
  continue;
197039
- const p = part;
197040
- switch (p.type) {
197041
- case "text":
197042
- if (typeof p.text === "string")
197043
- conversation += estimateTokens(p.text);
197044
- if (typeof p.textSignature === "string")
197045
- conversation += estimateTokens(p.textSignature);
197046
- break;
197047
- case "thinking":
197048
- if (typeof p.thinking === "string")
197049
- conversation += estimateTokens(p.thinking);
197050
- if (typeof p.thinkingSignature === "string")
197051
- conversation += estimateTokens(p.thinkingSignature);
197052
- break;
197053
- case "image":
197054
- conversation += 1200;
197055
- break;
197056
- case "toolCall":
197057
- if (typeof p.name === "string")
197058
- toolCall += estimateTokens(p.name);
197059
- if (p.arguments !== undefined) {
197060
- const s = typeof p.arguments === "string" ? p.arguments : safeJsonStringify2(p.arguments);
197061
- if (s)
197062
- toolCall += estimateTokens(s);
197063
- }
197064
- break;
197065
198054
  }
198055
+ if (!Array.isArray(content))
198056
+ continue;
198057
+ for (const part of content) {
198058
+ if (!part || typeof part !== "object")
198059
+ continue;
198060
+ const p = part;
198061
+ if (p.type === "text" && typeof p.text === "string") {
198062
+ toolCall += estimateTokens(p.text);
198063
+ } else if (p.type === "image") {
198064
+ toolCall += 1200;
198065
+ }
198066
+ }
198067
+ }
198068
+ } finally {
198069
+ bpeMs += performance.now() - bpeStart;
198070
+ if (stableId !== undefined && fingerprint !== null) {
198071
+ options?.cache.set(stableId, {
198072
+ fingerprint,
198073
+ counts: {
198074
+ conversation: conversation - beforeConversation,
198075
+ toolCall: toolCall - beforeToolCall
198076
+ }
198077
+ });
198078
+ }
198079
+ }
198080
+ }
198081
+ if (options && liveIds) {
198082
+ const cachePruneStart = options.onTiming ? performance.now() : 0;
198083
+ for (const id of options.cache.keys()) {
198084
+ if (!liveIds.has(id))
198085
+ options.cache.delete(id);
198086
+ }
198087
+ options.onTiming?.("cachePrune", performance.now() - cachePruneStart);
198088
+ }
198089
+ options?.onTiming?.("cacheValidation", cacheValidationMs);
198090
+ options?.onTiming?.("bpe", bpeMs);
198091
+ return { conversation, toolCall };
198092
+ }
198093
+ function buildTokenCacheFingerprint(value) {
198094
+ const message = value;
198095
+ const role = typeof message.role === "string" ? message.role : null;
198096
+ const fingerprint = [role];
198097
+ if (role !== "user" && role !== "assistant" && role !== "toolResult") {
198098
+ return fingerprint;
198099
+ }
198100
+ if (typeof message.content === "string") {
198101
+ fingerprint.push("string", message.content);
198102
+ return fingerprint;
198103
+ }
198104
+ if (!Array.isArray(message.content)) {
198105
+ fingerprint.push("non-array");
198106
+ return fingerprint;
198107
+ }
198108
+ fingerprint.push("parts");
198109
+ for (const rawPart of message.content) {
198110
+ if (!rawPart || typeof rawPart !== "object")
198111
+ continue;
198112
+ const part = rawPart;
198113
+ if (role === "toolResult") {
198114
+ if (part.type === "text" && typeof part.text === "string") {
198115
+ fingerprint.push("text", part.text);
198116
+ } else if (part.type === "image") {
198117
+ fingerprint.push("image");
197066
198118
  }
197067
198119
  continue;
197068
198120
  }
197069
- if (msg.role === "toolResult") {
197070
- if (typeof content === "string") {
197071
- toolCall += estimateTokens(content);
197072
- continue;
198121
+ switch (part.type) {
198122
+ case "text":
198123
+ fingerprint.push("text", typeof part.text === "string" ? part.text : null, typeof part.textSignature === "string" ? part.textSignature : null);
198124
+ break;
198125
+ case "thinking":
198126
+ fingerprint.push("thinking", typeof part.thinking === "string" ? part.thinking : null, typeof part.thinkingSignature === "string" ? part.thinkingSignature : null);
198127
+ break;
198128
+ case "image":
198129
+ fingerprint.push("image");
198130
+ break;
198131
+ case "toolCall": {
198132
+ const argumentsJson = part.arguments === undefined ? null : typeof part.arguments === "string" ? part.arguments : safeJsonStringify2(part.arguments);
198133
+ fingerprint.push("toolCall", typeof part.name === "string" ? part.name : null, argumentsJson);
198134
+ break;
197073
198135
  }
197074
- if (!Array.isArray(content))
198136
+ }
198137
+ }
198138
+ return fingerprint;
198139
+ }
198140
+ function tokenCacheFingerprintsEqual(left, right) {
198141
+ if (left.length !== right.length)
198142
+ return false;
198143
+ for (let index = 0;index < left.length; index += 1) {
198144
+ if (left[index] !== right[index])
198145
+ return false;
198146
+ }
198147
+ return true;
198148
+ }
198149
+ function isTokenCacheSafeMessage(value) {
198150
+ const prototype = Object.getPrototypeOf(value);
198151
+ if (prototype !== Object.prototype && prototype !== null)
198152
+ return false;
198153
+ if ("toJSON" in value)
198154
+ return false;
198155
+ if (!isJsonVisibleDataProperty(value, "role") || !isJsonVisibleDataProperty(value, "content")) {
198156
+ return false;
198157
+ }
198158
+ return isPlainJsonData(value.content, new Set);
198159
+ }
198160
+ function isJsonVisibleDataProperty(value, key) {
198161
+ if (!(key in value))
198162
+ return true;
198163
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
198164
+ return descriptor !== undefined && descriptor.enumerable === true && "value" in descriptor;
198165
+ }
198166
+ function isPlainJsonData(value, seen) {
198167
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "undefined") {
198168
+ return true;
198169
+ }
198170
+ if (typeof value !== "object" || seen.has(value))
198171
+ return false;
198172
+ const prototype = Object.getPrototypeOf(value);
198173
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
198174
+ return false;
198175
+ }
198176
+ if ("toJSON" in value)
198177
+ return false;
198178
+ seen.add(value);
198179
+ try {
198180
+ for (const key of Reflect.ownKeys(value)) {
198181
+ if (Array.isArray(value) && key === "length")
197075
198182
  continue;
197076
- for (const part of content) {
197077
- if (!part || typeof part !== "object")
197078
- continue;
197079
- const p = part;
197080
- if (p.type === "text" && typeof p.text === "string") {
197081
- toolCall += estimateTokens(p.text);
197082
- } else if (p.type === "image") {
197083
- toolCall += 1200;
197084
- }
198183
+ if (typeof key !== "string")
198184
+ return false;
198185
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
198186
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
198187
+ return false;
197085
198188
  }
198189
+ if (!isPlainJsonData(descriptor.value, seen))
198190
+ return false;
197086
198191
  }
198192
+ return true;
198193
+ } finally {
198194
+ seen.delete(value);
197087
198195
  }
197088
- return { conversation, toolCall };
197089
198196
  }
197090
198197
  function safeJsonStringify2(value) {
197091
198198
  try {
@@ -197589,6 +198696,9 @@ function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUs
197589
198696
  } : { percentage: trailingPercentage, inputTokens: trailingInputTokens };
197590
198697
  }
197591
198698
  var injectM0M1PiForRun = injectM0M1Pi;
198699
+ var persistReasoningWatermarkForRun = updateSessionMeta;
198700
+ var persistStableIdSchemeForRun = updateSessionMeta;
198701
+ var afterFallbackAdoptionForTests;
197592
198702
  var DEFAULT_CLEAR_REASONING_AGE = 50;
197593
198703
  var PI_STABLE_ID_SCHEME = 1;
197594
198704
  var lastEmergencyNotificationAtMs = new Map;
@@ -197606,9 +198716,26 @@ var activeContextHandlerSessions = new Set;
197606
198716
  var lastHeuristicsTurnIdBySession = new Map;
197607
198717
  var firstContextPassSeenBySession = new Set;
197608
198718
  var liveModelBySession = new Map;
198719
+ var taggedStableMessageIdsBySession = new Map;
198720
+ var taggersBySession = new Map;
198721
+ function recordSuccessfulTaggedMessageIds(sessionId, entryIds) {
198722
+ const liveRealIds = new Set;
198723
+ for (const entryId2 of entryIds) {
198724
+ if (entryId2 && !entryId2.startsWith("pi-msg-"))
198725
+ liveRealIds.add(entryId2);
198726
+ }
198727
+ taggedStableMessageIdsBySession.set(sessionId, liveRealIds);
198728
+ }
198729
+ var piMessageTokenCacheBySession = new Map;
198730
+ var piTagTextTokenCacheBySession = new Map;
198731
+ var piTagToolTokenCacheBySession = new Map;
198732
+ var piBranchProjectionBySession = new Map;
198733
+ var piBranchLookupByProjection = new WeakMap;
197609
198734
  function logTransformTiming(sessionId, stage, start, extra) {
197610
- const elapsed = (performance.now() - start).toFixed(1);
198735
+ const elapsedMs = performance.now() - start;
198736
+ const elapsed = elapsedMs.toFixed(1);
197611
198737
  const suffix = extra ? ` ${extra}` : "";
198738
+ recordPiTransformTiming({ sessionId, stage, elapsedMs, extra });
197612
198739
  sessionLog(sessionId, `transform stage: stage=${stage} elapsed=${elapsed}ms${suffix}`);
197613
198740
  }
197614
198741
  function resolvePiContextModelKey(ctx) {
@@ -197625,6 +198752,35 @@ function resolvePiContextModelKey(ctx) {
197625
198752
  function readPiSessionMessageById(ctx, messageId) {
197626
198753
  return readPiSessionMessages(ctx).find((message) => message.id === messageId) ?? null;
197627
198754
  }
198755
+ function convertLocatedPiUserEntry(branchEntries, messageId) {
198756
+ let rawOrdinal = 0;
198757
+ let pendingToolStart = -1;
198758
+ for (let index = 0;index < branchEntries.length; index += 1) {
198759
+ const entry = branchEntries[index];
198760
+ if (!entry || typeof entry !== "object")
198761
+ continue;
198762
+ const record2 = entry;
198763
+ if (record2.type !== "message" || !record2.message || typeof record2.message !== "object") {
198764
+ continue;
198765
+ }
198766
+ const role = record2.message.role;
198767
+ if (role === "toolResult") {
198768
+ if (pendingToolStart < 0)
198769
+ pendingToolStart = index;
198770
+ continue;
198771
+ }
198772
+ if (role === "assistant" && pendingToolStart >= 0)
198773
+ rawOrdinal += 1;
198774
+ rawOrdinal += 1;
198775
+ if (record2.id === messageId && role === "user") {
198776
+ const start = pendingToolStart >= 0 ? pendingToolStart : index;
198777
+ const converted = convertEntriesToRawMessages(branchEntries.slice(start, index + 1)).find((message) => message.id === messageId);
198778
+ return converted ? { ...converted, ordinal: rawOrdinal } : null;
198779
+ }
198780
+ pendingToolStart = -1;
198781
+ }
198782
+ return null;
198783
+ }
197628
198784
  function signalPiHistoryRefresh(sessionId) {
197629
198785
  historyRefreshSessions.add(sessionId);
197630
198786
  }
@@ -197776,29 +198932,7 @@ function collectMessageEntryIdsByRef(ctx, messages, sessionId, preloadedBranchEn
197776
198932
  return null;
197777
198933
  }
197778
198934
  }
197779
- const entryIdByMsgRef = new Map;
197780
- const entryIdsByFingerprint = new Map;
197781
- for (const entry of entries) {
197782
- if (!entry || typeof entry !== "object")
197783
- continue;
197784
- const e = entry;
197785
- if (e.type !== "message")
197786
- continue;
197787
- if (typeof e.id !== "string")
197788
- continue;
197789
- if (!e.message || typeof e.message !== "object")
197790
- continue;
197791
- const message = e.message;
197792
- entryIdByMsgRef.set(message, e.id);
197793
- const fingerprint = piMessageEntryFingerprint(e.message);
197794
- if (fingerprint) {
197795
- const bucket = entryIdsByFingerprint.get(fingerprint);
197796
- if (bucket)
197797
- bucket.push(e.id);
197798
- else
197799
- entryIdsByFingerprint.set(fingerprint, [e.id]);
197800
- }
197801
- }
198935
+ const { entryIdByMessageRef, entryIdsByFingerprint } = getPiBranchEntryLookup(entries);
197802
198936
  const result = new Array(messages.length);
197803
198937
  let resolved = 0;
197804
198938
  let fingerprintResolved = 0;
@@ -197809,7 +198943,7 @@ function collectMessageEntryIdsByRef(ctx, messages, sessionId, preloadedBranchEn
197809
198943
  result[i] = undefined;
197810
198944
  continue;
197811
198945
  }
197812
- const id = entryIdByMsgRef.get(msg);
198946
+ const id = entryIdByMessageRef.get(msg);
197813
198947
  if (typeof id === "string") {
197814
198948
  result[i] = id;
197815
198949
  resolved += 1;
@@ -197828,38 +198962,183 @@ function collectMessageEntryIdsByRef(ctx, messages, sessionId, preloadedBranchEn
197828
198962
  }
197829
198963
  }
197830
198964
  if (resolved < messages.length) {
197831
- log(`[magic-context][pi]${sessionId ? `[${sessionId}]` : ""} ` + `collectMessageEntryIdsByRef: resolved=${resolved}/${messages.length} ` + `(fingerprint=${fingerprintResolved}, branchEntries=${entries.length}, messageEntries=${entryIdByMsgRef.size}) — ` + `unmapped slots fall through to synthesized ids; boundary lookup still works ` + `for any compartment whose start/end message is among the resolved set`);
198965
+ log(`[magic-context][pi]${sessionId ? `[${sessionId}]` : ""} ` + `collectMessageEntryIdsByRef: resolved=${resolved}/${messages.length} ` + `(fingerprint=${fingerprintResolved}, branchEntries=${entries.length}, messageEntries=${entryIdByMessageRef.size}) — ` + `unmapped slots fall through to synthesized ids; boundary lookup still works ` + `for any compartment whose start/end message is among the resolved set`);
197832
198966
  }
197833
198967
  return result;
197834
198968
  }
197835
- function buildEntryIdByRefMap(branchEntries) {
197836
- const map2 = new Map;
197837
- if (!branchEntries)
197838
- return map2;
197839
- for (const entry of branchEntries) {
197840
- if (!entry || typeof entry !== "object")
197841
- continue;
197842
- const e = entry;
197843
- if (e.type !== "message")
197844
- continue;
197845
- if (typeof e.id !== "string")
197846
- continue;
197847
- if (!e.message || typeof e.message !== "object")
198969
+ function addPiBranchEntryToLookup(lookup, entry) {
198970
+ if (!entry || typeof entry !== "object")
198971
+ return;
198972
+ const row = entry;
198973
+ if (row.type !== "message" || typeof row.id !== "string" || !row.message || typeof row.message !== "object") {
198974
+ return;
198975
+ }
198976
+ lookup.entryIdByMessageRef.set(row.message, row.id);
198977
+ const fingerprint = piMessageEntryFingerprint(row.message);
198978
+ if (!fingerprint)
198979
+ return;
198980
+ const bucket = lookup.entryIdsByFingerprint.get(fingerprint);
198981
+ if (bucket)
198982
+ bucket.push(row.id);
198983
+ else
198984
+ lookup.entryIdsByFingerprint.set(fingerprint, [row.id]);
198985
+ }
198986
+ function isPiContextEmitEligible(entry) {
198987
+ if (!entry || typeof entry !== "object")
198988
+ return false;
198989
+ const row = entry;
198990
+ if (typeof row.id !== "string")
198991
+ return false;
198992
+ return row.type === "message" || row.type === "custom_message" || row.type === "branch_summary" && typeof row.summary === "string" && row.summary.length > 0;
198993
+ }
198994
+ function buildPiAlignedEntryIds(entries) {
198995
+ let compactionIndex = -1;
198996
+ let firstKeptEntryId;
198997
+ for (let index = entries.length - 1;index >= 0; index -= 1) {
198998
+ const row = entries[index];
198999
+ if (row?.type !== "compaction")
197848
199000
  continue;
197849
- map2.set(e.message, e.id);
199001
+ compactionIndex = index;
199002
+ firstKeptEntryId = typeof row.firstKeptEntryId === "string" ? row.firstKeptEntryId : undefined;
199003
+ break;
197850
199004
  }
197851
- return map2;
199005
+ if (compactionIndex < 0) {
199006
+ return entries.filter(isPiContextEmitEligible).map((entry) => entry.id);
199007
+ }
199008
+ const ids = [undefined];
199009
+ if (firstKeptEntryId !== undefined) {
199010
+ let foundFirstKept = false;
199011
+ for (let index = 0;index < compactionIndex; index += 1) {
199012
+ const entry = entries[index];
199013
+ if (entry?.id === firstKeptEntryId) {
199014
+ foundFirstKept = true;
199015
+ }
199016
+ if (foundFirstKept && isPiContextEmitEligible(entry))
199017
+ ids.push(entry.id);
199018
+ }
199019
+ }
199020
+ for (let index = compactionIndex + 1;index < entries.length; index += 1) {
199021
+ const entry = entries[index];
199022
+ if (isPiContextEmitEligible(entry))
199023
+ ids.push(entry.id);
199024
+ }
199025
+ return ids;
199026
+ }
199027
+ function getPiBranchEntryLookup(entries) {
199028
+ const cached2 = piBranchLookupByProjection.get(entries);
199029
+ if (cached2)
199030
+ return cached2;
199031
+ const lookup = {
199032
+ entryIdByMessageRef: new Map,
199033
+ entryIdsByFingerprint: new Map,
199034
+ alignedEntryIds: buildPiAlignedEntryIds(entries)
199035
+ };
199036
+ for (const entry of entries)
199037
+ addPiBranchEntryToLookup(lookup, entry);
199038
+ piBranchLookupByProjection.set(entries, lookup);
199039
+ return lookup;
199040
+ }
199041
+ function buildEntryIdByRefMap(branchEntries) {
199042
+ return branchEntries ? getPiBranchEntryLookup(branchEntries).entryIdByMessageRef : new Map;
197852
199043
  }
197853
199044
  function readPiBranchEntriesForContext(ctx, sessionId) {
197854
199045
  const sm = ctx.sessionManager;
197855
- if (typeof sm?.getBranch !== "function")
197856
- return null;
197857
- try {
199046
+ const installProjection = (leafId, entries) => {
199047
+ const indexById = new Map;
199048
+ const lookup = getPiBranchEntryLookup(entries);
199049
+ for (let index = 0;index < entries.length; index += 1) {
199050
+ const entry = entries[index];
199051
+ if (entry && typeof entry === "object") {
199052
+ const id = entry.id;
199053
+ if (typeof id === "string")
199054
+ indexById.set(id, index);
199055
+ }
199056
+ }
199057
+ const projection = { leafId, entries, indexById, lookup };
199058
+ piBranchProjectionBySession.set(sessionId, projection);
199059
+ piBranchLookupByProjection.set(entries, lookup);
199060
+ return entries;
199061
+ };
199062
+ const fallbackToBranch = () => {
199063
+ if (typeof sm?.getBranch !== "function")
199064
+ return null;
197858
199065
  const entries = sm.getBranch.call(sm);
197859
- return Array.isArray(entries) ? entries : null;
199066
+ if (!Array.isArray(entries))
199067
+ return null;
199068
+ const leafId = typeof entries.at(-1)?.id === "string" ? entries.at(-1).id ?? "" : "";
199069
+ return leafId ? installProjection(leafId, entries) : entries;
199070
+ };
199071
+ try {
199072
+ if (typeof sm?.getLeafId !== "function" || typeof sm.getEntry !== "function") {
199073
+ return fallbackToBranch();
199074
+ }
199075
+ const leafId = sm.getLeafId.call(sm);
199076
+ if (leafId === null)
199077
+ return [];
199078
+ if (typeof leafId !== "string" || leafId.length === 0) {
199079
+ return fallbackToBranch();
199080
+ }
199081
+ const cached2 = piBranchProjectionBySession.get(sessionId);
199082
+ if (cached2?.leafId === leafId)
199083
+ return cached2.entries;
199084
+ const suffix = [];
199085
+ const seen = new Set;
199086
+ let cursor = leafId;
199087
+ let cachedAncestorIndex;
199088
+ while (cursor !== null) {
199089
+ const priorIndex = cached2?.indexById.get(cursor);
199090
+ if (priorIndex !== undefined) {
199091
+ cachedAncestorIndex = priorIndex;
199092
+ break;
199093
+ }
199094
+ if (seen.has(cursor))
199095
+ return fallbackToBranch();
199096
+ seen.add(cursor);
199097
+ const entry = sm.getEntry.call(sm, cursor);
199098
+ if (!entry || typeof entry !== "object")
199099
+ return fallbackToBranch();
199100
+ const row = entry;
199101
+ if (row.id !== cursor || row.parentId !== null && typeof row.parentId !== "string") {
199102
+ return fallbackToBranch();
199103
+ }
199104
+ suffix.push(entry);
199105
+ cursor = row.parentId;
199106
+ }
199107
+ suffix.reverse();
199108
+ if (cached2 && cachedAncestorIndex === cached2.entries.length - 1) {
199109
+ const entries2 = [...cached2.entries, ...suffix];
199110
+ if (suffix.some((entry) => entry?.type === "compaction")) {
199111
+ return installProjection(leafId, entries2);
199112
+ }
199113
+ for (let index = 0;index < suffix.length; index += 1) {
199114
+ const entry = suffix[index];
199115
+ const id = entry.id;
199116
+ cached2.indexById.set(id, cached2.entries.length + index);
199117
+ addPiBranchEntryToLookup(cached2.lookup, entry);
199118
+ if (isPiContextEmitEligible(entry)) {
199119
+ cached2.lookup.alignedEntryIds.push(entry.id);
199120
+ }
199121
+ }
199122
+ const projection = {
199123
+ leafId,
199124
+ entries: entries2,
199125
+ indexById: cached2.indexById,
199126
+ lookup: cached2.lookup
199127
+ };
199128
+ piBranchProjectionBySession.set(sessionId, projection);
199129
+ piBranchLookupByProjection.set(entries2, cached2.lookup);
199130
+ return entries2;
199131
+ }
199132
+ const entries = cached2 && cachedAncestorIndex !== undefined ? [...cached2.entries.slice(0, cachedAncestorIndex + 1), ...suffix] : suffix;
199133
+ return installProjection(leafId, entries);
197860
199134
  } catch (error51) {
197861
- sessionLog(sessionId, `Pi branch pre-read failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
197862
- return null;
199135
+ sessionLog(sessionId, `Pi branch projection failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
199136
+ try {
199137
+ return fallbackToBranch();
199138
+ } catch (fallbackError) {
199139
+ sessionLog(sessionId, `Pi branch pre-read failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
199140
+ return null;
199141
+ }
197863
199142
  }
197864
199143
  }
197865
199144
  function piMessageEntryFingerprint(message) {
@@ -197893,13 +199172,15 @@ function firstPiTextContent(content) {
197893
199172
  }
197894
199173
  return null;
197895
199174
  }
197896
- function buildEntryFingerprintMap(messages, resolveStableId2) {
199175
+ function buildEntryFingerprintMap(messages, resolveStableId2, reusableMessageIds, includeReusable = true) {
197897
199176
  const map2 = new Map;
197898
199177
  for (let i = 0;i < messages.length; i++) {
197899
199178
  const msg = messages[i];
197900
199179
  const id = resolveStableId2(msg, i);
197901
199180
  if (!id)
197902
199181
  continue;
199182
+ if (!includeReusable && reusableMessageIds?.has(id))
199183
+ continue;
197903
199184
  const fp = piMessageEntryFingerprint(msg);
197904
199185
  if (fp)
197905
199186
  map2.set(id, fp);
@@ -197983,8 +199264,10 @@ function hasAdoptablePiFallbackMessageTags(db, sessionId, fingerprintById) {
197983
199264
  return false;
197984
199265
  }
197985
199266
  function adoptPiFallbackTags(db, sessionId, tagger, fingerprintById, options = {}) {
197986
- const shouldRunMessageMigration = options.stampStableIdScheme !== undefined || hasAdoptablePiFallbackMessageTags(db, sessionId, fingerprintById);
197987
- const shouldRunToolOwnerMigration = Boolean(options.messages && options.resolveStableId && hasPiFallbackToolOwnerTags(db, sessionId));
199267
+ const hasFallbackMessageTags = options.hasFallbackMessageTags === true || hasPiFallbackMessageTags(db, sessionId);
199268
+ const hasFallbackToolOwnerTags = options.hasFallbackToolOwnerTags === true || hasPiFallbackToolOwnerTags(db, sessionId);
199269
+ const shouldRunMessageMigration = hasFallbackMessageTags && hasAdoptablePiFallbackMessageTags(db, sessionId, fingerprintById);
199270
+ const shouldRunToolOwnerMigration = Boolean(options.messages && options.resolveStableId && hasFallbackToolOwnerTags);
197988
199271
  if (!shouldRunMessageMigration && !shouldRunToolOwnerMigration)
197989
199272
  return;
197990
199273
  runImmediateTransaction(db, () => {
@@ -198037,14 +199320,13 @@ function adoptPiFallbackTags(db, sessionId, tagger, fingerprintById, options = {
198037
199320
  tagger.unbindToolTag(sessionId, realOwnerId, row.callId);
198038
199321
  }
198039
199322
  tagger.bindToolTag(sessionId, row.callId, realOwnerId, adoption.tagNumber);
199323
+ const accounting = getPersistedToolTagAccounting(db, sessionId, adoption.tagNumber);
199324
+ if (accounting) {
199325
+ tagger.setToolTagAccounting(sessionId, adoption.tagNumber, accounting);
199326
+ }
198040
199327
  }
198041
199328
  }
198042
199329
  }
198043
- if (options.stampStableIdScheme !== undefined) {
198044
- updateSessionMeta(db, sessionId, {
198045
- piStableIdScheme: options.stampStableIdScheme
198046
- });
198047
- }
198048
199330
  });
198049
199331
  }
198050
199332
  function registerPiContextHandler(pi, baseOptions) {
@@ -198077,12 +199359,14 @@ function registerPiContextHandler(pi, baseOptions) {
198077
199359
  }
198078
199360
  sessionIdForError = sessionId;
198079
199361
  const projectDirectory = ctx.cwd;
199362
+ const fullWireMessageCount = event.messages.length;
198080
199363
  const options = baseOptions.resolveForProject?.(projectDirectory) ?? baseOptions;
198081
199364
  const schedulerConfig = options.scheduler ?? DEFAULT_SCHEDULER_CONFIG;
198082
199365
  const scheduler = schedulerFor(options);
198083
199366
  const projectIdentity = resolveProjectIdentity(projectDirectory);
198084
199367
  updateSessionProjectTracking(sessionId, projectIdentity, options.db);
198085
199368
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
199369
+ const tEntryBranch = performance.now();
198086
199370
  const branchEntries = readPiBranchEntriesForContext(ctx, sessionId);
198087
199371
  schedulePiTransformDecisionResolve({
198088
199372
  db: options.db,
@@ -198097,15 +199381,39 @@ function registerPiContextHandler(pi, baseOptions) {
198097
199381
  const unregisterRaw = setRawMessageProvider(sessionId, rawMessageProvider);
198098
199382
  rawMessageProviderUnregistersBySession.set(sessionId, unregisterRaw);
198099
199383
  scheduleReconciliation(options.db, sessionId, readRawSessionMessages);
198100
- const strictEntryIds = branchEntries === null ? null : collectMessageEntryIdsByRef(ctx, event.messages, sessionId, branchEntries);
199384
+ const branchLookup = branchEntries === null ? null : getPiBranchEntryLookup(branchEntries);
199385
+ const alignedEntryIds = branchLookup?.alignedEntryIds ?? null;
199386
+ const resolvedEntryIds = alignedEntryIds?.length === event.messages.length ? alignedEntryIds : branchEntries === null ? null : collectMessageEntryIdsByRef(ctx, event.messages, sessionId, branchEntries);
199387
+ const strictEntryIds = resolvedEntryIds ? [...resolvedEntryIds] : null;
199388
+ if (strictEntryIds && options.injection) {
199389
+ const removed = trimPiMessagesToCachedBoundary(options.db, sessionId, event.messages, strictEntryIds);
199390
+ if (removed > 0) {
199391
+ logTransformTiming(sessionId, "cachedBoundaryEarlyTrim", tEntryBranch, `removed=${removed}`);
199392
+ }
199393
+ }
198101
199394
  const entryIdByRef = buildEntryIdByRefMap(branchEntries);
199395
+ const previouslyTaggedIds = taggedStableMessageIdsBySession.get(sessionId);
199396
+ const reusableMessageIds = new Set;
199397
+ if (strictEntryIds && previouslyTaggedIds) {
199398
+ for (const entryId2 of strictEntryIds) {
199399
+ if (entryId2 && previouslyTaggedIds.has(entryId2)) {
199400
+ reusableMessageIds.add(entryId2);
199401
+ }
199402
+ }
199403
+ }
199404
+ logTransformTiming(sessionId, "entryParseAndBranchResolution", tEntryBranch, `branchEntries=${branchEntries?.length ?? 0}`);
198102
199405
  const tLastUser = performance.now();
198103
199406
  const latestUser = findLatestUserMessageIdPi(event.messages, buildPiMessageIdByIndex(event.messages, strictEntryIds));
198104
199407
  logTransformTiming(sessionId, "findLastUserMessageId", tLastUser);
199408
+ const tMessageIndexScheduling = performance.now();
198105
199409
  if (latestUser) {
198106
- scheduleIncrementalIndex(options.db, sessionId, latestUser.messageId, (_sessionId, messageId) => readPiSessionMessageById(ctx, messageId));
199410
+ const located = branchEntries ? convertLocatedPiUserEntry(branchEntries, latestUser.messageId) : null;
199411
+ scheduleIncrementalIndex(options.db, sessionId, latestUser.messageId, located ?? ((_sessionId, messageId) => readPiSessionMessageById(ctx, messageId)));
198107
199412
  }
198108
- tagger.initFromDb(sessionId, options.db);
199413
+ logTransformTiming(sessionId, "messageIndexScheduling", tMessageIndexScheduling);
199414
+ const taggerFloor = strictEntryIds ? deriveTagLoadFloor(options.db, sessionId, strictEntryIds) : 0;
199415
+ tagger.initFromDb(sessionId, options.db, taggerFloor);
199416
+ taggersBySession.set(sessionId, tagger);
198109
199417
  const isFirstContextPassForSession = !firstContextPassSeenBySession.has(sessionId);
198110
199418
  firstContextPassSeenBySession.add(sessionId);
198111
199419
  const piUsage = ctx.getContextUsage?.();
@@ -198209,7 +199517,7 @@ function registerPiContextHandler(pi, baseOptions) {
198209
199517
  schedulerDecision = "defer";
198210
199518
  }
198211
199519
  logTransformTiming(sessionId, "schedulerAndUsage", tScheduler);
198212
- const piMessageCount = Array.isArray(event.messages) ? event.messages.length : 0;
199520
+ const piMessageCount = fullWireMessageCount;
198213
199521
  const looksLikeImportedSession = schedulerDecision === "defer" && usagePercentage === 0 && sessionMeta.lastResponseTime === 0 && piMessageCount >= 50;
198214
199522
  if (looksLikeImportedSession) {
198215
199523
  schedulerDecision = "execute";
@@ -198227,8 +199535,9 @@ function registerPiContextHandler(pi, baseOptions) {
198227
199535
  signalPiPendingMaterialization(sessionId);
198228
199536
  sessionLog(sessionId, `stable-id scheme cutover: stored=${storedStableIdScheme} < current=${PI_STABLE_ID_SCHEME} — forcing execute+materialize this pass`);
198229
199537
  }
199538
+ const tBoundaryChecks = performance.now();
198230
199539
  const schedulerDecisionEarly = schedulerDecision;
198231
- const midTurn = isMidTurnPi(event, sessionId);
199540
+ const midTurn = isMidTurnPi(event, sessionId, branchEntries);
198232
199541
  const bypassReason = detectMidTurnBypassReason({
198233
199542
  contextUsage: { percentage: usagePercentage },
198234
199543
  sessionMeta,
@@ -198276,6 +199585,7 @@ function registerPiContextHandler(pi, baseOptions) {
198276
199585
  }
198277
199586
  }
198278
199587
  const isCacheBusting = historyRefreshSessions.has(sessionId);
199588
+ logTransformTiming(sessionId, "boundaryTriggerChecks", tBoundaryChecks);
198279
199589
  sessionLog(sessionId, `transform: usage=${usagePercentage.toFixed(1)}% (${usageInputTokens} tokens, limit=${usageContextLimit ?? "?"}) decision=${schedulerDecision}${forceMaterialization ? " force=true" : ""}${isEmergency ? " EMERGENCY=true" : ""}${isCacheBusting ? " busting=true" : ""}`);
198280
199590
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tEmergencyRecovery);
198281
199591
  const entryIds = strictEntryIds ?? undefined;
@@ -198284,12 +199594,15 @@ function registerPiContextHandler(pi, baseOptions) {
198284
199594
  contextLimit: usageContextLimit,
198285
199595
  sessionId
198286
199596
  }) / 100)) : undefined;
199597
+ logTransformTiming(sessionId, "prePipelineTotal", transformStartTime);
199598
+ const tRunPipeline = performance.now();
198287
199599
  const result = await runPipeline({
198288
199600
  db: options.db,
198289
199601
  tagger,
198290
199602
  sessionId,
198291
199603
  projectIdentity,
198292
199604
  projectDirectory,
199605
+ sessionMeta,
198293
199606
  messages: event.messages,
198294
199607
  smartDrops: options.smartDrops === true,
198295
199608
  protectedTags: options.protectedTags ?? 20,
@@ -198310,6 +199623,7 @@ function registerPiContextHandler(pi, baseOptions) {
198310
199623
  } : undefined,
198311
199624
  entryIds,
198312
199625
  entryIdByRef,
199626
+ reusableMessageIds,
198313
199627
  stableIdSchemeCutover,
198314
199628
  schedulerDecision,
198315
199629
  forceMaterialization: forceMaterialization || isEmergency,
@@ -198327,8 +199641,14 @@ function registerPiContextHandler(pi, baseOptions) {
198327
199641
  readBranchEntries: resolvePiReadBranchEntries(ctx),
198328
199642
  isSubagent: sessionMeta.isSubagent
198329
199643
  });
198330
- const piDecisionSnapshotNewestAssistant = branchEntries === null ? undefined : findNewestPiAssistantEntryId(branchEntries);
198331
- if (result.bustedThisPass && piDecisionSnapshotNewestAssistant !== undefined) {
199644
+ logTransformTiming(sessionId, "runPipeline", tRunPipeline);
199645
+ const postPipelineStart = performance.now();
199646
+ const tTransformDecision = performance.now();
199647
+ if (strictEntryIds) {
199648
+ recordSuccessfulTaggedMessageIds(sessionId, strictEntryIds);
199649
+ }
199650
+ const piDecisionSnapshotNewestAssistant = result.bustedThisPass ? findNewestPiAssistantEntryId(branchEntries) : undefined;
199651
+ if (piDecisionSnapshotNewestAssistant !== undefined) {
198332
199652
  recordPendingPiTransformDecision(sessionId, {
198333
199653
  tsMs: Date.now(),
198334
199654
  decision: schedulerDecision,
@@ -198341,6 +199661,8 @@ function registerPiContextHandler(pi, baseOptions) {
198341
199661
  bustedThisPass: true
198342
199662
  }, piDecisionSnapshotNewestAssistant);
198343
199663
  }
199664
+ logTransformTiming(sessionId, "transformDecisionAndReuseState", tTransformDecision);
199665
+ const tHistorianScheduling = performance.now();
198344
199666
  if (options.historian) {
198345
199667
  maybeFireHistorian({
198346
199668
  pi,
@@ -198350,11 +199672,14 @@ function registerPiContextHandler(pi, baseOptions) {
198350
199672
  historian: options.historian,
198351
199673
  isFirstContextPassForSession,
198352
199674
  activeTags: result.activeTags,
198353
- rawMessageProvider
199675
+ rawMessageProvider,
199676
+ taggerFloor
198354
199677
  });
198355
199678
  }
199679
+ logTransformTiming(sessionId, "historianScheduling", tHistorianScheduling);
198356
199680
  const tPostTransform = performance.now();
198357
199681
  let outputMessages = result.messages;
199682
+ const tNoteNudges = performance.now();
198358
199683
  try {
198359
199684
  outputMessages = applyNoteNudges({
198360
199685
  sessionId,
@@ -198369,15 +199694,17 @@ function registerPiContextHandler(pi, baseOptions) {
198369
199694
  } catch (err) {
198370
199695
  sessionLog(sessionId, `note nudges failed: ${err instanceof Error ? err.message : String(err)}`);
198371
199696
  }
199697
+ logTransformTiming(sessionId, "noteNudges", tNoteNudges);
199698
+ const tAutoSearch = performance.now();
198372
199699
  if (options.autoSearch?.enabled) {
198373
199700
  try {
198374
- await ensureProjectRegisteredFromPiDirectory(projectDirectory, options.db);
198375
199701
  outputMessages = await runAutoSearchHintForPi({
198376
199702
  sessionId,
198377
199703
  db: options.db,
198378
199704
  messages: outputMessages,
198379
199705
  entryIds: strictEntryIds,
198380
199706
  entryIdByRef: result.postCommitEntryIdByRef,
199707
+ ensureProjectRegistered: () => ensureProjectRegisteredFromPiDirectory(projectDirectory, options.db),
198381
199708
  options: {
198382
199709
  enabled: true,
198383
199710
  scoreThreshold: options.autoSearch.scoreThreshold,
@@ -198390,6 +199717,8 @@ function registerPiContextHandler(pi, baseOptions) {
198390
199717
  sessionLog(sessionId, `auto-search failed: ${err instanceof Error ? err.message : String(err)}`);
198391
199718
  }
198392
199719
  }
199720
+ logTransformTiming(sessionId, "autoSearch", tAutoSearch);
199721
+ const tTodoCapture = performance.now();
198393
199722
  try {
198394
199723
  const sessionMetaForTodo = getOrCreateSessionMeta(options.db, sessionId);
198395
199724
  if (!sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
@@ -198406,6 +199735,8 @@ function registerPiContextHandler(pi, baseOptions) {
198406
199735
  } catch (err) {
198407
199736
  sessionLog(sessionId, `synthetic todowrite injection failed: ${err instanceof Error ? err.message : String(err)}`);
198408
199737
  }
199738
+ logTransformTiming(sessionId, "todoCapture", tTodoCapture);
199739
+ const tChannelAccounting = performance.now();
198409
199740
  try {
198410
199741
  const sessionMetaForCh1 = getOrCreateSessionMeta(options.db, sessionId);
198411
199742
  if (!sessionMetaForCh1.isSubagent) {
@@ -198465,16 +199796,39 @@ function registerPiContextHandler(pi, baseOptions) {
198465
199796
  } catch (err) {
198466
199797
  sessionLog(sessionId, `channel1 baseline / channel2 trigger failed: ${err instanceof Error ? err.message : String(err)}`);
198467
199798
  }
199799
+ logTransformTiming(sessionId, "channelNudgeAccounting", tChannelAccounting);
199800
+ const tWorkMetrics = performance.now();
198468
199801
  try {
198469
199802
  const metrics = computePiWorkMetrics(outputMessages);
198470
199803
  setSessionWorkMetrics(options.db, sessionId, metrics.newWorkTokens, metrics.totalInputTokens);
198471
199804
  } catch (err) {
198472
199805
  sessionLog(sessionId, `work-metrics update failed: ${err instanceof Error ? err.message : String(err)}`);
198473
199806
  }
199807
+ logTransformTiming(sessionId, "workMetrics", tWorkMetrics);
199808
+ const tStableIdSchemePersist = performance.now();
199809
+ if (stableIdSchemeCutover) {
199810
+ persistStableIdSchemeForRun(options.db, sessionId, {
199811
+ piStableIdScheme: PI_STABLE_ID_SCHEME
199812
+ });
199813
+ invalidateTrueRawTokenCache({
199814
+ sessionId,
199815
+ reason: "pi.stable-id-scheme.changed"
199816
+ });
199817
+ sessionLog(sessionId, `stable-id scheme cutover complete — stamped scheme=${PI_STABLE_ID_SCHEME}`);
199818
+ }
199819
+ logTransformTiming(sessionId, "stableIdSchemePersist", tStableIdSchemePersist);
198474
199820
  logTransformTiming(sessionId, "postTransformPhase", tPostTransform);
198475
- sessionLog(sessionId, `transform completed in ${(performance.now() - transformStartTime).toFixed(1)}ms (${outputMessages.length} messages, ${result.targetCount} targets, watermark: ${result.reasoningWatermark})`);
198476
199821
  clearLastTransformErrorIfSet(options.db, sessionId);
198477
199822
  options.maybeAutoEmbedSession?.(sessionId, projectDirectory, projectIdentity);
199823
+ logTransformTiming(sessionId, "postPipelineTotal", postPipelineStart);
199824
+ const transformElapsedMs = performance.now() - transformStartTime;
199825
+ recordPiTransformTiming({
199826
+ sessionId,
199827
+ stage: "total",
199828
+ elapsedMs: transformElapsedMs,
199829
+ extra: `messages=${outputMessages.length} targets=${result.targetCount}`
199830
+ });
199831
+ sessionLog(sessionId, `transform completed in ${transformElapsedMs.toFixed(1)}ms (${outputMessages.length} messages, ${result.targetCount} targets, watermark: ${result.reasoningWatermark})`);
198478
199832
  return { messages: outputMessages };
198479
199833
  } catch (err) {
198480
199834
  const message = err instanceof Error ? err.message : String(err);
@@ -198775,7 +200129,7 @@ function maybeFireHistorian(args) {
198775
200129
  }
198776
200130
  return snapshot;
198777
200131
  };
198778
- const boundarySnapshot = resolveRunnablePiBoundarySnapshot();
200132
+ let boundarySnapshot;
198779
200133
  let triggered = false;
198780
200134
  try {
198781
200135
  if (isFirstContextPassForSession) {
@@ -198785,7 +200139,10 @@ function maybeFireHistorian(args) {
198785
200139
  sessionLog(sessionId, "historian: cleared stale compartmentInProgress flag on first context pass after restart");
198786
200140
  }
198787
200141
  const failureState = getHistorianFailureState(db, sessionId);
198788
- const shouldRecoverOnFirstPass = failureState.failureCount > 0 && hasEligiblePiCompartmentHistory(db, sessionId, boundarySnapshot);
200142
+ if (failureState.failureCount > 0) {
200143
+ boundarySnapshot = resolveRunnablePiBoundarySnapshot();
200144
+ }
200145
+ const shouldRecoverOnFirstPass = failureState.failureCount > 0 && boundarySnapshot !== undefined && hasEligiblePiCompartmentHistory(db, sessionId, boundarySnapshot);
198789
200146
  if (shouldRecoverOnFirstPass) {
198790
200147
  triggered = true;
198791
200148
  sessionLog(sessionId, `historian recovery triggered on session load after ${failureState.failureCount} failure(s)`);
@@ -198808,12 +200165,18 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
198808
200165
  return;
198809
200166
  }
198810
200167
  }
198811
- const trigger = checkCompartmentTrigger(db, sessionId, sessionMeta, usage, 0, triggerInputs.executeThresholdPercentage, triggerInputs.triggerBudget, triggerInputs.clearReasoningAge, triggerInputs.commitClusterTrigger, args.activeTags, boundaryContextLimit);
200168
+ const trigger = checkCompartmentTrigger(db, sessionId, sessionMeta, usage, 0, triggerInputs.executeThresholdPercentage, triggerInputs.triggerBudget, triggerInputs.clearReasoningAge, triggerInputs.commitClusterTrigger, args.activeTags, boundaryContextLimit, () => {
200169
+ const messages = provider2.readMessages();
200170
+ return { messages, absoluteMessageCount: messages.length };
200171
+ }, args.taggerFloor);
198812
200172
  if (!trigger.shouldFire) {
198813
200173
  sessionLog(sessionId, `historian trigger eval: shouldFire=false (no trigger condition met)`);
198814
200174
  try {
198815
200175
  const overflowState = getOverflowState(db, sessionId);
198816
- if (overflowState.needsEmergencyRecovery && usage.percentage < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId) && !hasRunnableCompartmentWindow(boundarySnapshot)) {
200176
+ if (overflowState.needsEmergencyRecovery && usage.percentage < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId)) {
200177
+ boundarySnapshot ??= resolveRunnablePiBoundarySnapshot();
200178
+ }
200179
+ if (overflowState.needsEmergencyRecovery && usage.percentage < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId) && boundarySnapshot !== undefined && !hasRunnableCompartmentWindow(boundarySnapshot)) {
198817
200180
  clearEmergencyRecovery(db, sessionId);
198818
200181
  sessionLog(sessionId, `historian: disarming stale emergency recovery — real pressure ${usage.percentage.toFixed(1)}% with no runnable compartment window (would otherwise bump to 95% every pass)`);
198819
200182
  }
@@ -198833,7 +200196,7 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
198833
200196
  provider: provider2,
198834
200197
  unregister,
198835
200198
  boundarySnapshot: selectPiHistorianRunBoundarySnapshot({
198836
- resolvedBoundarySnapshot: boundarySnapshot,
200199
+ resolvedBoundarySnapshot: boundarySnapshot ?? trigger.boundarySnapshot ?? resolveRunnablePiBoundarySnapshot(),
198837
200200
  triggerBoundarySnapshot: trigger.boundarySnapshot
198838
200201
  }),
198839
200202
  refreshBoundarySnapshot: resolveRunnablePiBoundarySnapshot,
@@ -198856,6 +200219,44 @@ function pendingPiMarkerCoveredByRenderedBoundary(pending, injection) {
198856
200219
  return true;
198857
200220
  return boundary.ordinal !== null && pending.ordinal <= boundary.ordinal;
198858
200221
  }
200222
+ function captureReasoningMutationRollback(messages) {
200223
+ const snapshots = [];
200224
+ for (const raw of messages) {
200225
+ if (!raw || typeof raw !== "object")
200226
+ continue;
200227
+ const message = raw;
200228
+ if (message.role !== "assistant" || !Array.isArray(message.content))
200229
+ continue;
200230
+ for (const rawPart of message.content) {
200231
+ if (!rawPart || typeof rawPart !== "object")
200232
+ continue;
200233
+ const part = rawPart;
200234
+ if (part.type === "thinking") {
200235
+ snapshots.push({
200236
+ part,
200237
+ field: "thinking",
200238
+ value: part.thinking,
200239
+ hadSignature: Object.hasOwn(part, "thinkingSignature"),
200240
+ signature: part.thinkingSignature
200241
+ });
200242
+ } else if (part.type === "text") {
200243
+ snapshots.push({ part, field: "text", value: part.text });
200244
+ }
200245
+ }
200246
+ }
200247
+ return () => {
200248
+ for (const snapshot of snapshots) {
200249
+ snapshot.part[snapshot.field] = snapshot.value;
200250
+ if (snapshot.field === "thinking") {
200251
+ if (snapshot.hadSignature) {
200252
+ snapshot.part.thinkingSignature = snapshot.signature;
200253
+ } else {
200254
+ delete snapshot.part.thinkingSignature;
200255
+ }
200256
+ }
200257
+ }
200258
+ };
200259
+ }
198859
200260
  async function runPipeline(args) {
198860
200261
  let executedWorkThisPass = false;
198861
200262
  let historyWasConsumedThisPass = false;
@@ -198884,7 +200285,9 @@ async function runPipeline(args) {
198884
200285
  }
198885
200286
  logTransformTiming(args.sessionId, "injectTemporalMarkers", tTemporal);
198886
200287
  }
200288
+ const tTranscriptBuild = performance.now();
198887
200289
  const transcript = createPiTranscript(args.messages, args.sessionId, args.entryIds);
200290
+ logTransformTiming(args.sessionId, "transcriptBuild", tTranscriptBuild);
198888
200291
  const workingMessages = transcript.getWorkingMessages();
198889
200292
  const stableIdResolver = (msg, index) => resolvePiStableId(msg, index, args.entryIds, args.entryIdByRef ?? undefined);
198890
200293
  const currentTurnId = (() => {
@@ -198892,12 +200295,11 @@ async function runPipeline(args) {
198892
200295
  return findLatestUserMessageIdPi(args.messages, ids)?.messageId ?? null;
198893
200296
  })();
198894
200297
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
198895
- const sessionMetaForAvailability = getOrCreateSessionMeta(args.db, args.sessionId);
198896
- const ctxReduceCallable = !sessionMetaForAvailability.isSubagent;
200298
+ const ctxReduceCallable = !args.sessionMeta.isSubagent;
198897
200299
  const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
198898
200300
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
198899
200301
  const piHardSignals = args.injection ? (() => {
198900
- const hardMeta = getOrCreateSessionMeta(args.db, args.sessionId);
200302
+ const hardMeta = args.sessionMeta;
198901
200303
  let piTtlMs = 5 * 60 * 1000;
198902
200304
  try {
198903
200305
  piTtlMs = parseCacheTtl(hardMeta.cacheTtl);
@@ -198922,28 +200324,44 @@ async function runPipeline(args) {
198922
200324
  const bypassHistorianGate = args.forceMaterialization === true || m0HardFoldThisPass;
198923
200325
  const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
198924
200326
  const shouldRunHeuristics = args.heuristics !== undefined && (!historianRunning || bypassHistorianGate) && (args.forceMaterialization === true || hasPendingMaterializeSignal || deferredMaterializeEligible || m0HardFoldThisPass || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
198925
- const entryFingerprintByMessageId = buildEntryFingerprintMap(args.messages, stableIdResolver);
200327
+ const tFallbackIdentity = performance.now();
200328
+ const hasFallbackMessageTags = hasPiFallbackMessageTags(args.db, args.sessionId);
200329
+ const entryFingerprintByMessageId = buildEntryFingerprintMap(args.messages, stableIdResolver, args.reusableMessageIds, hasFallbackMessageTags);
198926
200330
  adoptPiFallbackTags(args.db, args.sessionId, args.tagger, entryFingerprintByMessageId, {
198927
200331
  messages: args.messages,
198928
200332
  resolveStableId: stableIdResolver,
198929
- stampStableIdScheme: args.stableIdSchemeCutover ? PI_STABLE_ID_SCHEME : undefined
200333
+ hasFallbackMessageTags
198930
200334
  });
198931
- if (args.stableIdSchemeCutover === true) {
198932
- invalidateTrueRawTokenCache({
198933
- sessionId: args.sessionId,
198934
- reason: "pi.stable-id-scheme.changed"
198935
- });
198936
- sessionLog(args.sessionId, `stable-id scheme cutover complete — stamped scheme=${PI_STABLE_ID_SCHEME}`);
198937
- }
200335
+ logTransformTiming(args.sessionId, "fallbackIdentityAndAdoption", tFallbackIdentity);
200336
+ afterFallbackAdoptionForTests?.(args.stableIdSchemeCutover === true);
198938
200337
  const tTag = performance.now();
200338
+ let tagTextTokenCache = piTagTextTokenCacheBySession.get(args.sessionId);
200339
+ if (!tagTextTokenCache) {
200340
+ tagTextTokenCache = new Map;
200341
+ piTagTextTokenCacheBySession.set(args.sessionId, tagTextTokenCache);
200342
+ }
200343
+ let tagToolTokenCache = piTagToolTokenCacheBySession.get(args.sessionId);
200344
+ if (!tagToolTokenCache) {
200345
+ tagToolTokenCache = new Map;
200346
+ piTagToolTokenCacheBySession.set(args.sessionId, tagToolTokenCache);
200347
+ }
198939
200348
  const { targets } = tagTranscript(args.sessionId, transcript, args.tagger, args.db, {
198940
200349
  skipPrefixInjection: !ctxReduceCallable,
198941
- entryFingerprintByMessageId
200350
+ entryFingerprintByMessageId,
200351
+ reuseMessageIds: args.reusableMessageIds,
200352
+ textTokenCache: tagTextTokenCache,
200353
+ toolTokenCache: tagToolTokenCache,
200354
+ onTiming: hasPiTransformTimingObserver() ? (phase, elapsedMs) => {
200355
+ recordPiTransformTiming({
200356
+ sessionId: args.sessionId,
200357
+ stage: `tag:${phase}`,
200358
+ elapsedMs
200359
+ });
200360
+ } : undefined
198942
200361
  });
198943
200362
  logTransformTiming(args.sessionId, "tagMessages", tTag);
198944
200363
  try {
198945
- const sessionMeta = getOrCreateSessionMeta(args.db, args.sessionId);
198946
- if (!sessionMeta.isSubagent) {
200364
+ if (!args.sessionMeta.isSubagent) {
198947
200365
  const hasRecentCommit = detectRecentCommit(args.messages);
198948
200366
  const hadPriorCommitState = commitSeenLastPass.has(args.sessionId);
198949
200367
  const sawCommitLastPass = commitSeenLastPass.get(args.sessionId) ?? false;
@@ -198958,6 +200376,7 @@ async function runPipeline(args) {
198958
200376
  const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
198959
200377
  const deferredHistoryRefreshWasPending = deferredHistoryWasPendingAtPassStart;
198960
200378
  const pendingOps = getPendingOps(args.db, args.sessionId);
200379
+ const pendingOperationTags = getTagsForPendingOperations(args.db, args.sessionId, pendingOps.map((operation) => operation.tagId), args.protectedTags, RECENT_TOOL_SKELETON_WINDOW);
198961
200380
  const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal || m0HardFoldThisPass;
198962
200381
  const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
198963
200382
  const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
@@ -198967,7 +200386,7 @@ async function runPipeline(args) {
198967
200386
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
198968
200387
  try {
198969
200388
  const tApplyPending = performance.now();
198970
- pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags, undefined, pendingOps);
200389
+ pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags, pendingOperationTags, pendingOps);
198971
200390
  if (pendingOpsDidMutate) {
198972
200391
  droppedCount += pendingOps.length;
198973
200392
  }
@@ -199023,6 +200442,7 @@ async function runPipeline(args) {
199023
200442
  }
199024
200443
  }
199025
200444
  if (args.heuristics?.caveman?.enabled && !args.isSubagent) {
200445
+ const tCavemanReplay = performance.now();
199026
200446
  try {
199027
200447
  const tags = getTagsByNumbers(args.db, args.sessionId, targetTagNumbers);
199028
200448
  const replayed = replayCavemanCompression(args.sessionId, args.db, targets, tags);
@@ -199032,6 +200452,7 @@ async function runPipeline(args) {
199032
200452
  } catch (err) {
199033
200453
  sessionLog(args.sessionId, `caveman replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
199034
200454
  }
200455
+ logTransformTiming(args.sessionId, "cavemanReplay", tCavemanReplay);
199035
200456
  }
199036
200457
  let heuristicsExecuted = false;
199037
200458
  let heuristicsResult = null;
@@ -199082,10 +200503,10 @@ async function runPipeline(args) {
199082
200503
  }
199083
200504
  }
199084
200505
  if (args.reasoningClearing && shouldRunHeuristics) {
200506
+ const rollbackReasoning = captureReasoningMutationRollback(workingMessages);
199085
200507
  try {
199086
200508
  const tClearReasoning = performance.now();
199087
- const meta3 = getOrCreateSessionMeta(args.db, args.sessionId);
199088
- const prevWatermark = meta3.clearedReasoningThroughTag ?? 0;
200509
+ const prevWatermark = args.sessionMeta.clearedReasoningThroughTag ?? 0;
199089
200510
  const clearOutcome = clearOldReasoningPi({
199090
200511
  messages: workingMessages,
199091
200512
  messageIdToMaxTag,
@@ -199100,9 +200521,10 @@ async function runPipeline(args) {
199100
200521
  });
199101
200522
  const combinedWatermark = Math.max(clearOutcome.newWatermark, stripOutcome.newWatermark);
199102
200523
  if (combinedWatermark > prevWatermark) {
199103
- updateSessionMeta(args.db, args.sessionId, {
200524
+ persistReasoningWatermarkForRun(args.db, args.sessionId, {
199104
200525
  clearedReasoningThroughTag: combinedWatermark
199105
200526
  });
200527
+ args.sessionMeta.clearedReasoningThroughTag = combinedWatermark;
199106
200528
  sessionLog(args.sessionId, `reasoning cleanup: cleared=${clearOutcome.cleared} inlineStripped=${stripOutcome.stripped} watermark=${prevWatermark}→${combinedWatermark}`);
199107
200529
  }
199108
200530
  logTransformTiming(args.sessionId, "clearOldReasoning", tClearReasoning);
@@ -199115,7 +200537,8 @@ async function runPipeline(args) {
199115
200537
  executedWorkThisPass = true;
199116
200538
  }
199117
200539
  } catch (err) {
199118
- sessionLog(args.sessionId, `reasoning clearing failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
200540
+ rollbackReasoning();
200541
+ sessionLog(args.sessionId, `reasoning clearing failed; restored original reasoning: ${err instanceof Error ? err.message : String(err)}`);
199119
200542
  }
199120
200543
  }
199121
200544
  const toolReclaimExecutePass = args.schedulerDecision === "execute";
@@ -199124,7 +200547,7 @@ async function runPipeline(args) {
199124
200547
  let autoReclaimTargetCount = 0;
199125
200548
  let autoReclaimDidMutate = false;
199126
200549
  if (toolReclaimExecutePass && alreadyMutatingThisPass && !emergencyDropEligible) {
199127
- const reclaimMeta = getOrCreateSessionMeta(args.db, args.sessionId);
200550
+ const reclaimMeta = args.sessionMeta;
199128
200551
  const syntheticPendingOps = buildSyntheticToolReclaimOps({
199129
200552
  db: args.db,
199130
200553
  sessionId: args.sessionId,
@@ -199170,13 +200593,38 @@ async function runPipeline(args) {
199170
200593
  }
199171
200594
  }
199172
200595
  }
200596
+ if (args.canUseEmptySentinels) {
200597
+ const tProcessedImages = performance.now();
200598
+ try {
200599
+ const imageResult = stripPiProcessedImages({
200600
+ db: args.db,
200601
+ sessionId: args.sessionId,
200602
+ messages: workingMessages,
200603
+ detect: args.isCacheBusting || shouldApplyPendingOps || shouldRunHeuristics,
200604
+ watermark: getMaxDroppedTagNumber(args.db, args.sessionId),
200605
+ messageIdToMaxTag,
200606
+ stableId: stableIdResolver
200607
+ });
200608
+ if (imageResult.newlyStrippedIds.length > 0) {
200609
+ heuristicOrReasoningDidMutate = true;
200610
+ executedWorkThisPass = true;
200611
+ droppedCount += imageResult.stripped;
200612
+ }
200613
+ } catch (err) {
200614
+ sessionLog(args.sessionId, `processed-image strip failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
200615
+ }
200616
+ logTransformTiming(args.sessionId, "stripProcessedImages", tProcessedImages);
200617
+ }
200618
+ const tTranscriptCommit = performance.now();
199173
200619
  transcript.commit();
200620
+ logTransformTiming(args.sessionId, "transcriptCommit", tTranscriptCommit);
199174
200621
  if (toolReclaimExecutePass) {
199175
200622
  advanceToolReclaimWatermarkToCurrentMax(args.db, args.sessionId);
199176
200623
  }
199177
200624
  if (autoReclaimTargetCount > 0) {
199178
200625
  sessionLog(args.sessionId, `tool reclaim auto-drop: targets=${autoReclaimTargetCount} mutated=${autoReclaimDidMutate}`);
199179
200626
  }
200627
+ const tPostCommitStableIdMaps = performance.now();
199180
200628
  const postCommitStableIdByRef = new Map;
199181
200629
  const postCommitEntryIdByRef = new Map;
199182
200630
  for (let i = 0;i < args.messages.length; i++) {
@@ -199191,6 +200639,7 @@ async function runPipeline(args) {
199191
200639
  postCommitEntryIdByRef.set(m, realId);
199192
200640
  }
199193
200641
  }
200642
+ logTransformTiming(args.sessionId, "postCommitStableIdMaps", tPostCommitStableIdMaps);
199194
200643
  let injectionResult = null;
199195
200644
  if (args.injection) {
199196
200645
  try {
@@ -199218,6 +200667,7 @@ async function runPipeline(args) {
199218
200667
  sessionLog(args.sessionId, `compartment injection failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
199219
200668
  }
199220
200669
  }
200670
+ const tDroppedPlaceholders = performance.now();
199221
200671
  stripPiDroppedPlaceholderMessages({
199222
200672
  db: args.db,
199223
200673
  sessionId: args.sessionId,
@@ -199226,6 +200676,7 @@ async function runPipeline(args) {
199226
200676
  stableIdByRef: postCommitStableIdByRef,
199227
200677
  forceDiscovery: args.stableIdSchemeCutover === true
199228
200678
  });
200679
+ logTransformTiming(args.sessionId, "stripDroppedPlaceholders", tDroppedPlaceholders);
199229
200680
  const deferredHistoryDrainEligible = historyWasConsumedThisPass && materializationSatisfiedThisPass && (deferredHistoryWasPendingAtPassStart || hasPendingMaterializeSignal) && !suppressDeferredHistoryDrain && !casLost;
199230
200681
  let preserveDeferredMaterializationForMarkerDrain = false;
199231
200682
  if (deferredHistoryDrainEligible) {
@@ -199282,11 +200733,28 @@ async function runPipeline(args) {
199282
200733
  logTransformTiming(args.sessionId, "batchFinalize:heuristics", performance.now());
199283
200734
  const outputMessages = transcript.getOutputMessages();
199284
200735
  try {
199285
- const counts = tokenizePiMessages(outputMessages);
200736
+ const tTokenAccounting = performance.now();
200737
+ let tokenCache = piMessageTokenCacheBySession.get(args.sessionId);
200738
+ if (!tokenCache) {
200739
+ tokenCache = new Map;
200740
+ piMessageTokenCacheBySession.set(args.sessionId, tokenCache);
200741
+ }
200742
+ const counts = tokenizePiMessages(outputMessages, {
200743
+ cache: tokenCache,
200744
+ stableId: (message) => postCommitEntryIdByRef.get(message),
200745
+ onTiming: hasPiTransformTimingObserver() ? (phase, elapsedMs) => {
200746
+ recordPiTransformTiming({
200747
+ sessionId: args.sessionId,
200748
+ stage: `token:${phase}`,
200749
+ elapsedMs
200750
+ });
200751
+ } : undefined
200752
+ });
199286
200753
  updateSessionMeta(args.db, args.sessionId, {
199287
200754
  conversationTokens: counts.conversation,
199288
200755
  toolCallTokens: counts.toolCall
199289
200756
  });
200757
+ logTransformTiming(args.sessionId, "tokenAccounting", tTokenAccounting);
199290
200758
  } catch (err) {
199291
200759
  sessionLog(args.sessionId, `token accounting failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
199292
200760
  }
@@ -199308,15 +200776,18 @@ async function runPipeline(args) {
199308
200776
  emergency,
199309
200777
  bustedThisPass,
199310
200778
  targetCount: targets.size,
199311
- reasoningWatermark: getOrCreateSessionMeta(args.db, args.sessionId).clearedReasoningThroughTag ?? 0,
200779
+ reasoningWatermark: args.sessionMeta.clearedReasoningThroughTag ?? 0,
199312
200780
  activeTags,
199313
200781
  postCommitEntryIdByRef
199314
200782
  };
199315
200783
  }
199316
200784
  function applyNoteNudges(args) {
199317
200785
  const { sessionId, db, messages, projectIdentity, entryIds, entryIdByRef } = args;
200786
+ const tNoteIndexMaps = performance.now();
199318
200787
  const messageIdByIndex = buildPiMessageIdByIndex(messages, entryIds, false, entryIdByRef);
199319
200788
  const replayMessageIdByIndex = buildPiMessageIdByIndex(messages, entryIds, true, entryIdByRef);
200789
+ logTransformTiming(sessionId, "noteIndexMaps", tNoteIndexMaps);
200790
+ const tStickyReplay = performance.now();
199320
200791
  for (const anchor of getNoteNudgeAnchors(db, sessionId)) {
199321
200792
  appendReminderToUserMessageByIdPi(messages, replayMessageIdByIndex, anchor.messageId, anchor.text);
199322
200793
  }
@@ -199325,6 +200796,7 @@ function applyNoteNudges(args) {
199325
200796
  appendReminderToUserMessageByIdPi(messages, replayMessageIdByIndex, decision.messageId, decision.text);
199326
200797
  }
199327
200798
  }
200799
+ logTransformTiming(sessionId, "stickyReplayDecisions", tStickyReplay);
199328
200800
  const latestUser = findLatestUserMessageIdPi(messages, messageIdByIndex);
199329
200801
  const latestUserId = latestUser?.messageId ?? null;
199330
200802
  const noteReadStillVisible = hasVisibleNoteReadCallPi(messages);
@@ -199393,9 +200865,9 @@ function buildPiMessageIdByIndex(messages, entryIds, includeMessageIdFallback =
199393
200865
  }
199394
200866
  continue;
199395
200867
  }
199396
- const entryId = entryIds?.[index];
199397
- if (typeof entryId === "string") {
199398
- ids.set(index, entryId);
200868
+ const entryId2 = entryIds?.[index];
200869
+ if (typeof entryId2 === "string") {
200870
+ ids.set(index, entryId2);
199399
200871
  continue;
199400
200872
  }
199401
200873
  if (includeMessageIdFallback) {
@@ -199469,6 +200941,17 @@ function clearContextHandlerSession(sessionId) {
199469
200941
  firstContextPassSeenBySession.delete(sessionId);
199470
200942
  commitSeenLastPass.delete(sessionId);
199471
200943
  liveModelBySession.delete(sessionId);
200944
+ taggedStableMessageIdsBySession.delete(sessionId);
200945
+ const tagger = taggersBySession.get(sessionId);
200946
+ if (tagger) {
200947
+ tagger.cleanup(sessionId);
200948
+ taggersBySession.delete(sessionId);
200949
+ }
200950
+ piMessageTokenCacheBySession.delete(sessionId);
200951
+ piTagTextTokenCacheBySession.delete(sessionId);
200952
+ piTagToolTokenCacheBySession.delete(sessionId);
200953
+ piBranchProjectionBySession.delete(sessionId);
200954
+ clearPiInjectionTokenCountCache(sessionId);
199472
200955
  clearPiChannel1State(sessionId);
199473
200956
  lastHeuristicsTurnIdBySession.delete(sessionId);
199474
200957
  lastSeenProjectIdentityBySession.delete(sessionId);
@@ -201904,9 +203387,8 @@ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageC
201904
203387
  const compartments = getCompartments(db, sessionId);
201905
203388
  let historyBlockTokens = 0;
201906
203389
  for (const c of compartments) {
201907
- historyBlockTokens += estimateTokens(`<compartment start="${c.startMessage}" end="${c.endMessage}" title="${c.title}">
203390
+ historyBlockTokens += estimateTokens(`## ${c.startMessage}-${c.endMessage} · ${c.title}
201908
203391
  ${c.content}
201909
- </compartment>
201910
203392
  `);
201911
203393
  }
201912
203394
  const budgetTokens = historyBudgetPercentage && displayContextLimit > 0 ? Math.floor(displayContextLimit * (Math.min(executeThresholdPercentage, 80) / 100) * historyBudgetPercentage) : null;
@@ -201958,9 +203440,8 @@ function computeM0BlockTokens(db, sessionId, args) {
201958
203440
  try {
201959
203441
  const compRows = db.prepare("SELECT content, title, start_message, end_message FROM compartments WHERE session_id = ?").all(sessionId);
201960
203442
  for (const c of compRows) {
201961
- compartmentTokens += estimateTokens(`<compartment start="${c.start_message}" end="${c.end_message}" title="${c.title}">
203443
+ compartmentTokens += estimateTokens(`## ${c.start_message}-${c.end_message} · ${c.title}
201962
203444
  ${c.content}
201963
- </compartment>
201964
203445
  `);
201965
203446
  }
201966
203447
  } catch {}
@@ -201990,7 +203471,7 @@ function formatThresholdPercent(value) {
201990
203471
  // package.json
201991
203472
  var package_default = {
201992
203473
  name: "@cortexkit/pi-magic-context",
201993
- version: "0.31.5",
203474
+ version: "0.32.0",
201994
203475
  type: "module",
201995
203476
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
201996
203477
  main: "dist/index.js",
@@ -202969,9 +204450,9 @@ function stripTagPrefixFromAssistantMessage(message) {
202969
204450
  // ../plugin/src/tools/ctx-expand/constants.ts
202970
204451
  var CTX_EXPAND_DESCRIPTION = `Recover the original conversation from your compacted history.
202971
204452
 
202972
- Older parts of this session are summarized into <compartment> blocks inside <session-history> — e.g. <compartment start="120" end="245" title="Fixed tagger collision">. Each one replaces the raw messages in that ordinal range with a summary. When the summary isn't enough — you need exact wording, a specific value, an error message, or the reasoning behind a decision — expand the range:
204453
+ Older parts of this session are summarized under \`## start-end · date · title\` headings inside <session-history> — e.g. \`## 120-245 · … · Fixed tagger collision\`. Each heading replaces the raw messages in that ordinal range with a summary. When the summary isn't enough — you need exact wording, a specific value, an error message, or the reasoning behind a decision — expand the range:
202973
204454
 
202974
- ctx_expand(start=120, end=245) ← the compartment's own start/end attributes
204455
+ ctx_expand(start=120, end=245) ← the heading's start/end range
202975
204456
 
202976
204457
  Returns the raw transcript as [N] U:/A: lines, capped at ~15K tokens; an oversized range returns the head and tells you where to continue. Also works with ordinals from ctx_search message results — expand a window around a hit (e.g. start=N-10, end=N+5). Ranges after the last compartment are your live tail — already visible in context, not expandable.
202977
204458
 
@@ -208514,6 +209995,8 @@ var STATUS_COLOR = {
208514
209995
  cancelled: "error"
208515
209996
  };
208516
209997
  var snapshotsBySession = new Map;
209998
+ var MAX_TODOWRITE_RENDER_CACHE_ENTRIES = 50;
209999
+ var cachedTodowriteTodosByToolCallId = new Map;
208517
210000
  function isTodoStatus2(value) {
208518
210001
  return TODO_STATUSES.includes(value);
208519
210002
  }
@@ -208523,6 +210006,27 @@ function isTodoPriority2(value) {
208523
210006
  function cloneTodos(todos) {
208524
210007
  return todos.map((todo) => ({ ...todo }));
208525
210008
  }
210009
+ function rememberTodowriteToolCallTodos(toolCallId, todos) {
210010
+ if (!toolCallId)
210011
+ return;
210012
+ cachedTodowriteTodosByToolCallId.delete(toolCallId);
210013
+ cachedTodowriteTodosByToolCallId.set(toolCallId, cloneTodos(todos));
210014
+ while (cachedTodowriteTodosByToolCallId.size > MAX_TODOWRITE_RENDER_CACHE_ENTRIES) {
210015
+ const oldestKey = cachedTodowriteTodosByToolCallId.keys().next().value;
210016
+ if (typeof oldestKey !== "string")
210017
+ break;
210018
+ cachedTodowriteTodosByToolCallId.delete(oldestKey);
210019
+ }
210020
+ }
210021
+ function getCachedTodowriteToolCallTodos(toolCallId) {
210022
+ if (!toolCallId)
210023
+ return null;
210024
+ const todos = cachedTodowriteTodosByToolCallId.get(toolCallId);
210025
+ return todos ? cloneTodos(todos) : null;
210026
+ }
210027
+ function clearTodowriteToolCallTodos() {
210028
+ cachedTodowriteTodosByToolCallId.clear();
210029
+ }
208526
210030
  function parseTodos(input) {
208527
210031
  if (!Array.isArray(input))
208528
210032
  return null;
@@ -208659,25 +210163,27 @@ function capTodoRows(rows) {
208659
210163
  hiddenCount: rows.length - visibleCount
208660
210164
  };
208661
210165
  }
208662
- function renderTodowriteCall(args, theme) {
208663
- const todos = parseTodos(args.todos) ?? [];
210166
+ function renderTodowriteCall(args, theme, context) {
210167
+ const todos = getCachedTodowriteToolCallTodos(context?.toolCallId) ?? parseTodos(args.todos) ?? [];
208664
210168
  const active = activeTitleCount(todos);
208665
210169
  const activeColor = active > 0 ? "warning" : "success";
208666
210170
  const line = `${theme.fg("toolTitle", theme.bold("Todos"))} ${theme.fg("muted", "—")} ${theme.fg(activeColor, `${active} active`)}`;
208667
210171
  return lineComponent((width) => [truncateToWidth2(line, width, "…")]);
208668
210172
  }
208669
- function renderTodowriteResult(result, theme) {
210173
+ function renderTodowriteResult(result, theme, context) {
208670
210174
  const detailsTodos = result.details?.todos;
208671
210175
  let todos = parseTodos(detailsTodos);
208672
- if (todos === null && Array.isArray(result.content)) {
210176
+ if ((todos === null || todos.length === 0) && Array.isArray(result.content)) {
208673
210177
  const text = result.content.find((part) => part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string")?.text;
208674
210178
  if (text) {
208675
210179
  try {
208676
- todos = parseTodos(JSON.parse(text));
210180
+ const parsed = parseTodos(JSON.parse(text));
210181
+ if (parsed !== null)
210182
+ todos = parsed;
208677
210183
  } catch {}
208678
210184
  }
208679
210185
  }
208680
- const renderedTodos = todos ?? [];
210186
+ const renderedTodos = todos !== null && todos.length > 0 ? todos : getCachedTodowriteToolCallTodos(context?.toolCallId) ?? todos ?? [];
208681
210187
  return lineComponent((width) => {
208682
210188
  if (renderedTodos.length === 0)
208683
210189
  return [theme.fg("dim", "No todos")];
@@ -208878,6 +210384,7 @@ function registerTodoOverlay(pi, deps) {
208878
210384
  overlay.hideCompletedTasksFromPreviousTurn();
208879
210385
  });
208880
210386
  pi.on("session_shutdown", async (_event, ctx) => {
210387
+ clearTodowriteToolCallTodos();
208881
210388
  const sessionId = getSessionId(ctx);
208882
210389
  if (!sessionId)
208883
210390
  return;
@@ -208885,6 +210392,7 @@ function registerTodoOverlay(pi, deps) {
208885
210392
  overlay.disposeSession(sessionId);
208886
210393
  });
208887
210394
  pi.on("session_before_switch", async (_event, ctx) => {
210395
+ clearTodowriteToolCallTodos();
208888
210396
  const sessionId = getSessionId(ctx);
208889
210397
  if (!sessionId)
208890
210398
  return;
@@ -208901,11 +210409,13 @@ function registerTodoStateLifecycle(pi, deps) {
208901
210409
  seedTodoSnapshotFromStateJson(sessionId, deps.readLastTodoState(sessionId));
208902
210410
  });
208903
210411
  pi.on("session_shutdown", async (_event, ctx) => {
210412
+ clearTodowriteToolCallTodos();
208904
210413
  const sessionId = getSessionId(ctx);
208905
210414
  if (sessionId)
208906
210415
  clearTodoSnapshot(sessionId);
208907
210416
  });
208908
210417
  pi.on("session_before_switch", async (_event, ctx) => {
210418
+ clearTodowriteToolCallTodos();
208909
210419
  const sessionId = getSessionId(ctx);
208910
210420
  if (sessionId)
208911
210421
  clearTodoSnapshot(sessionId);
@@ -208956,11 +210466,11 @@ function createTodowriteTool() {
208956
210466
  }
208957
210467
  };
208958
210468
  },
208959
- renderCall(args, theme) {
208960
- return renderTodowriteCall(args, theme);
210469
+ renderCall(args, theme, context) {
210470
+ return renderTodowriteCall(args, theme, context);
208961
210471
  },
208962
- renderResult(result, _opts, theme) {
208963
- return renderTodowriteResult(result, theme);
210472
+ renderResult(result, _opts, theme, context) {
210473
+ return renderTodowriteResult(result, theme, context);
208964
210474
  }
208965
210475
  };
208966
210476
  }
@@ -209038,9 +210548,13 @@ function getCompatiblePiTodoCapture(todos) {
209038
210548
  const normalized = normalizeTodoStateJson(todos);
209039
210549
  if (normalized === null)
209040
210550
  return null;
209041
- return { normalized, todos };
210551
+ const parsed = parseTodos(todos);
210552
+ if (parsed === null)
210553
+ return null;
210554
+ return { normalized, todos: parsed };
209042
210555
  }
209043
210556
  function applyCompatiblePiTodoCapture(args) {
210557
+ rememberTodowriteToolCallTodos(args.toolCallId, args.capture.todos);
209044
210558
  if (args.todowriteEnabled) {
209045
210559
  setTodoSnapshot(args.sessionId, args.capture.todos);
209046
210560
  args.todoOverlay?.update(args.sessionId);
@@ -209185,7 +210699,7 @@ async function persistPiPressureFromMessageEnd(args) {
209185
210699
  }
209186
210700
  var PLUGIN_VERSION = (() => {
209187
210701
  try {
209188
- const req = createRequire3(import.meta.url);
210702
+ const req = createRequire4(import.meta.url);
209189
210703
  return req("../package.json").version;
209190
210704
  } catch {
209191
210705
  return "0.0.0";
@@ -209357,11 +210871,11 @@ async function src_default2(pi) {
209357
210871
  projectIdentity: identity,
209358
210872
  memoryEnabled: cfg.memory.enabled
209359
210873
  }, sessionId, dir, identity, (text) => {
209360
- pi.sendMessage({
209361
- customType: "ctx-status",
209362
- content: text,
209363
- display: true
209364
- }, { triggerTurn: false });
210874
+ sendCtxStatusMessage(pi, {
210875
+ title: "/ctx-embed",
210876
+ text,
210877
+ level: "info"
210878
+ });
209365
210879
  });
209366
210880
  }
209367
210881
  });
@@ -209429,6 +210943,12 @@ async function src_default2(pi) {
209429
210943
  todowriteEnabled
209430
210944
  });
209431
210945
  info(todowriteEnabled ? "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, todowrite, ctx_reduce; registered /todos" : "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, ctx_reduce (todowrite disabled)");
210946
+ pi.on("session_start", async (event, ctx) => {
210947
+ await handlePiCloneSessionStart(event, ctx, {
210948
+ db,
210949
+ signalPendingMarker: signalPiDeferredCompactionMarkerDrain
210950
+ });
210951
+ });
209432
210952
  const readLastTodoState = (sessionId) => getOrCreateSessionMeta(db, sessionId).lastTodoState;
209433
210953
  if (todowriteEnabled) {
209434
210954
  registerTodoStateLifecycle(pi, { readLastTodoState });
@@ -209442,6 +210962,8 @@ async function src_default2(pi) {
209442
210962
  info(bootProjectDeps.autoSearchConfig.enabled ? `registered auto-search hint (threshold=${bootProjectDeps.autoSearchConfig.scoreThreshold}, minChars=${bootProjectDeps.autoSearchConfig.minPromptChars})` : "registered auto-search hint: DISABLED (memory.auto_search.enabled=false)");
209443
210963
  registerCtxAugCommand(pi, (ctx) => resolveCurrentProjectDeps(ctx).sidekickConfig);
209444
210964
  info(bootProjectDeps.sidekickConfig ? `registered /ctx-aug (sidekick model=${bootProjectDeps.sidekickConfig.model})` : "registered /ctx-aug (sidekick disabled — set sidekick.disable=false and sidekick.model in config)");
210965
+ const statusEntryRendererAvailable = registerCtxStatusEntryRenderer(pi);
210966
+ info(statusEntryRendererAvailable ? "registered model-invisible ctx-status entry renderer" : "ctx-status entry renderer unavailable; using legacy visible-message fallback");
209445
210967
  const recompRunner = new PiSubagentRunner;
209446
210968
  const wrapupRunner = new PiSubagentRunner;
209447
210969
  const upgradeRunner = new PiSubagentRunner;
@@ -209769,6 +211291,7 @@ ${block}` : event.systemPrompt;
209769
211291
  const sessionId = ctx.sessionManager.getSessionId();
209770
211292
  if (event.toolName === "todowrite") {
209771
211293
  const todoArgs = event.args;
211294
+ const toolCallId = typeof event.toolCallId === "string" ? event.toolCallId : undefined;
209772
211295
  const todos = todoArgs?.todos;
209773
211296
  const sessionMeta = Array.isArray(todos) ? getOrCreateSessionMeta(db, sessionId) : null;
209774
211297
  capturePiTodowriteArgsIfCompatible({
@@ -209777,7 +211300,8 @@ ${block}` : event.systemPrompt;
209777
211300
  todos,
209778
211301
  todowriteEnabled,
209779
211302
  todoOverlay,
209780
- persist: Boolean(sessionMeta && !sessionMeta.isSubagent)
211303
+ persist: Boolean(sessionMeta && !sessionMeta.isSubagent),
211304
+ toolCallId
209781
211305
  });
209782
211306
  if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
209783
211307
  if (sessionMeta && !sessionMeta.isSubagent) {