@cortexkit/pi-magic-context 0.31.4 → 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",
@@ -157588,18 +158296,48 @@ function inferAccountingSubagent(agent) {
157588
158296
  return "recomp";
157589
158297
  return "historian";
157590
158298
  }
158299
+ var ALREADY_PROCESSING_PREFIX = "Agent is already processing";
158300
+ var ISOLATED_RETRY_MODEL_UNAVAILABLE_MESSAGE = "model unavailable in isolated retry: it is provided by a disabled extension; configure it through models.json or add a built-in/provider-configured fallback";
158301
+ var MODEL_RESOLUTION_ERROR_PATTERNS = [
158302
+ /unknown model/i,
158303
+ /unknown provider/i,
158304
+ /model.+not found/i,
158305
+ /provider.+not found/i,
158306
+ /could not resolve model/i,
158307
+ /no models? (matched|available|configured)/i,
158308
+ /model.+not configured/i
158309
+ ];
157591
158310
 
157592
158311
  class PiSubagentRunner {
157593
158312
  harness = "pi";
157594
158313
  invocation;
157595
158314
  spawnImpl;
157596
158315
  platform;
158316
+ extraArgs;
157597
158317
  constructor(options = {}) {
157598
158318
  this.invocation = options.piBinary ? { command: options.piBinary, prefixArgs: [] } : resolvePiInvocation();
157599
158319
  this.spawnImpl = options.spawnImpl ?? childProcess.spawn;
157600
158320
  this.platform = options.platform ?? process.platform;
158321
+ this.extraArgs = options.extraArgs ?? [];
157601
158322
  }
157602
158323
  async run(options) {
158324
+ const primaryRunMode = { disableDiscoveredExtensions: false };
158325
+ const primaryResult = await this.runModelChain(options, primaryRunMode);
158326
+ if (this.spawnUsesNoExtensions(primaryRunMode) || !isPiExtensionCollisionFailure(primaryResult)) {
158327
+ return primaryResult;
158328
+ }
158329
+ const sessionId = options.accountingSessionId ?? "pi-subagent";
158330
+ sessionLog(sessionId, "pi subagent: a loaded Pi extension started an agent turn before the child's prompt could run; retrying with an isolated extension set (user extensions disabled for this run)");
158331
+ const isolatedResult = await this.runModelChain(options, {
158332
+ disableDiscoveredExtensions: true
158333
+ });
158334
+ if (!isolatedResult.ok && isIsolatedRetryModelUnavailable(isolatedResult)) {
158335
+ sessionLog(sessionId, ISOLATED_RETRY_MODEL_UNAVAILABLE_MESSAGE);
158336
+ return annotateIsolatedRetryModelUnavailable(isolatedResult);
158337
+ }
158338
+ return isolatedResult;
158339
+ }
158340
+ async runModelChain(options, runMode) {
157603
158341
  const models = [options.model, ...options.fallbackModels ?? []].filter((model) => typeof model === "string" && model.length > 0);
157604
158342
  const attempts = models.length > 0 ? models : [undefined];
157605
158343
  let lastResult = null;
@@ -157610,17 +158348,23 @@ class PiSubagentRunner {
157610
158348
  model,
157611
158349
  fallbackModels: undefined
157612
158350
  };
157613
- const result = await this.runOnce(attemptOptions);
158351
+ const result = await this.runOnce(attemptOptions, runMode);
157614
158352
  if (result.ok)
157615
158353
  return result;
157616
158354
  lastResult = result;
158355
+ if (!this.spawnUsesNoExtensions(runMode) && isPiExtensionCollisionFailure(result)) {
158356
+ return result;
158357
+ }
157617
158358
  if (index >= attempts.length - 1 || !isFallbackEligible(result.reason)) {
157618
158359
  return result;
157619
158360
  }
157620
158361
  }
157621
- return lastResult ?? this.runOnce({ ...options, fallbackModels: undefined });
158362
+ return lastResult ?? this.runOnce({ ...options, fallbackModels: undefined }, runMode);
157622
158363
  }
157623
- async runOnce(options) {
158364
+ spawnUsesNoExtensions(runMode) {
158365
+ return runMode.disableDiscoveredExtensions || hasNoExtensionsArg([...this.invocation.prefixArgs, ...this.extraArgs]);
158366
+ }
158367
+ async runOnce(options, runMode) {
157624
158368
  const startTime = Date.now();
157625
158369
  let recordedAccounting = false;
157626
158370
  const recordAccounting = (result, messages = []) => {
@@ -157695,6 +158439,7 @@ class PiSubagentRunner {
157695
158439
  }
157696
158440
  }
157697
158441
  const args = buildArgs(options, {
158442
+ disableDiscoveredExtensions: runMode.disableDiscoveredExtensions,
157698
158443
  omitPositionalMessage: deliverViaStdin,
157699
158444
  systemPromptPath
157700
158445
  });
@@ -157722,7 +158467,7 @@ class PiSubagentRunner {
157722
158467
  };
157723
158468
  let child;
157724
158469
  try {
157725
- child = this.spawnImpl(this.invocation.command, [...this.invocation.prefixArgs, ...args], {
158470
+ child = this.spawnImpl(this.invocation.command, [...this.invocation.prefixArgs, ...this.extraArgs, ...args], {
157726
158471
  cwd: options.cwd,
157727
158472
  env: {
157728
158473
  ...process.env,
@@ -158016,6 +158761,32 @@ class PiSubagentRunner {
158016
158761
  });
158017
158762
  }
158018
158763
  }
158764
+ function getResultStderr(result) {
158765
+ const stderr = result.meta?.stderr;
158766
+ return typeof stderr === "string" ? stderr : "";
158767
+ }
158768
+ function hasNoExtensionsArg(args) {
158769
+ return args.includes("--no-extensions");
158770
+ }
158771
+ function isPiExtensionCollisionFailure(result) {
158772
+ return !result.ok && result.reason === "non_zero_exit" && getResultStderr(result).includes(ALREADY_PROCESSING_PREFIX);
158773
+ }
158774
+ function isIsolatedRetryModelUnavailable(result) {
158775
+ if (result.ok)
158776
+ return false;
158777
+ const diagnosticText = `${result.error}
158778
+ ${getResultStderr(result)}`;
158779
+ return MODEL_RESOLUTION_ERROR_PATTERNS.some((pattern) => pattern.test(diagnosticText));
158780
+ }
158781
+ function annotateIsolatedRetryModelUnavailable(result) {
158782
+ if (result.error.startsWith(ISOLATED_RETRY_MODEL_UNAVAILABLE_MESSAGE)) {
158783
+ return result;
158784
+ }
158785
+ return {
158786
+ ...result,
158787
+ error: `${ISOLATED_RETRY_MODEL_UNAVAILABLE_MESSAGE}. Original failure: ${result.error}`
158788
+ };
158789
+ }
158019
158790
  function isFallbackEligible(reason) {
158020
158791
  return reason === "model_failed" || reason === "truncated" || reason === "non_zero_exit" || reason === "no_assistant";
158021
158792
  }
@@ -158030,9 +158801,13 @@ function buildArgs(options, opts) {
158030
158801
  "--no-prompt-templates",
158031
158802
  "--no-context-files"
158032
158803
  ];
158033
- const shouldLoadSubagentExtension = SUBAGENT_ENTRY_PATH && (SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
158804
+ if (opts?.disableDiscoveredExtensions) {
158805
+ args.push("--no-extensions");
158806
+ }
158807
+ const subagentEntryPath = opts?.subagentEntryPath ?? SUBAGENT_ENTRY_PATH;
158808
+ const shouldLoadSubagentExtension = subagentEntryPath && (SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
158034
158809
  if (shouldLoadSubagentExtension) {
158035
- args.push("--extension", SUBAGENT_ENTRY_PATH);
158810
+ args.push("--extension", subagentEntryPath);
158036
158811
  if (DREAMER_ACTION_AGENTS.has(options.agent)) {
158037
158812
  args.push("--magic-context-dreamer-actions");
158038
158813
  }
@@ -173412,8 +174187,11 @@ var MagicContextConfigSchema = exports_external.object({
173412
174187
  provider: "local",
173413
174188
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
173414
174189
  }).describe("Embedding provider configuration"),
173415
- 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).'),
173416
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."),
173417
174195
  todowrite: exports_external.object({
173418
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."),
173419
174197
  overlay: exports_external.boolean().default(true).describe("Pi only: show the persistent todo overlay above the editor while tasks are active.")
@@ -175131,7 +175909,7 @@ var GIT_SWEEP_COOLDOWN_MS = 10 * 60 * 1000;
175131
175909
  var GIT_SWEEP_LEASE_TTL_MS = 5 * 60 * 1000;
175132
175910
  var GIT_SWEEP_NON_INDEXABLE_REPROBE_MS = 24 * 60 * 60 * 1000;
175133
175911
  var GIT_SWEEP_LEASE_RENEWAL_MS = 60 * 1000;
175134
- function runImmediate(db, body) {
175912
+ function runImmediate2(db, body) {
175135
175913
  db.exec("BEGIN IMMEDIATE");
175136
175914
  let committed = false;
175137
175915
  try {
@@ -175164,7 +175942,7 @@ function getGitSweepCoordinatorState(db, projectPath) {
175164
175942
  function acquireGitSweepLease(db, projectPath, holderId, options = {}) {
175165
175943
  const cooldownMs = options.cooldownMs ?? GIT_SWEEP_COOLDOWN_MS;
175166
175944
  const leaseTtlMs = options.leaseTtlMs ?? GIT_SWEEP_LEASE_TTL_MS;
175167
- return runImmediate(db, () => {
175945
+ return runImmediate2(db, () => {
175168
175946
  const now = Date.now();
175169
175947
  const row = getGitSweepCoordinatorState(db, projectPath);
175170
175948
  if (row?.leaseHolder && row.leaseExpiresAt !== null && row.leaseExpiresAt > now) {
@@ -175212,7 +175990,7 @@ function acquireGitSweepLease(db, projectPath, holderId, options = {}) {
175212
175990
  });
175213
175991
  }
175214
175992
  function renewGitSweepLease(db, projectPath, holderId, leaseTtlMs = GIT_SWEEP_LEASE_TTL_MS) {
175215
- return runImmediate(db, () => {
175993
+ return runImmediate2(db, () => {
175216
175994
  const now = Date.now();
175217
175995
  const leaseExpiresAt = now + leaseTtlMs;
175218
175996
  const result = db.prepare(`UPDATE git_sweep_coordinator
@@ -175224,7 +176002,7 @@ function renewGitSweepLease(db, projectPath, holderId, leaseTtlMs = GIT_SWEEP_LE
175224
176002
  });
175225
176003
  }
175226
176004
  function markGitSweepSuccessAndRelease(db, projectPath, holderId) {
175227
- return runImmediate(db, () => {
176005
+ return runImmediate2(db, () => {
175228
176006
  const now = Date.now();
175229
176007
  const result = db.prepare(`UPDATE git_sweep_coordinator
175230
176008
  SET lease_holder = NULL,
@@ -175237,7 +176015,7 @@ function markGitSweepSuccessAndRelease(db, projectPath, holderId) {
175237
176015
  });
175238
176016
  }
175239
176017
  function parkGitSweepNonIndexable(db, projectPath, holderId, reprobeMs = GIT_SWEEP_NON_INDEXABLE_REPROBE_MS) {
175240
- return runImmediate(db, () => {
176018
+ return runImmediate2(db, () => {
175241
176019
  const now = Date.now();
175242
176020
  const sweptAt = now + reprobeMs - GIT_SWEEP_COOLDOWN_MS;
175243
176021
  const result = db.prepare(`UPDATE git_sweep_coordinator
@@ -175251,7 +176029,7 @@ function parkGitSweepNonIndexable(db, projectPath, holderId, reprobeMs = GIT_SWE
175251
176029
  });
175252
176030
  }
175253
176031
  function releaseGitSweepLease(db, projectPath, holderId) {
175254
- runImmediate(db, () => {
176032
+ runImmediate2(db, () => {
175255
176033
  db.prepare(`UPDATE git_sweep_coordinator
175256
176034
  SET lease_holder = NULL,
175257
176035
  lease_expires_at = NULL
@@ -177248,7 +178026,7 @@ function peekLeaseHolderAndExpiry(db, expectedHolder, leaseKey = DREAMING_LEASE_
177248
178026
  const expiry = Number(expiryStr);
177249
178027
  return Number.isFinite(expiry) && expiry >= Date.now();
177250
178028
  }
177251
- function runImmediate2(db, body) {
178029
+ function runImmediate3(db, body) {
177252
178030
  db.exec("BEGIN IMMEDIATE");
177253
178031
  let committed = false;
177254
178032
  try {
@@ -177266,7 +178044,7 @@ function runImmediate2(db, body) {
177266
178044
  }
177267
178045
  function acquireLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177268
178046
  const keys = rowKeys(leaseKey);
177269
- return runImmediate2(db, () => {
178047
+ return runImmediate3(db, () => {
177270
178048
  if (isLeaseActive(db, leaseKey)) {
177271
178049
  const existingHolder = getLeaseHolder(db, leaseKey);
177272
178050
  if (existingHolder && existingHolder !== holderId) {
@@ -177282,7 +178060,7 @@ function acquireLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177282
178060
  }
177283
178061
  function renewLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177284
178062
  const keys = rowKeys(leaseKey);
177285
- return runImmediate2(db, () => {
178063
+ return runImmediate3(db, () => {
177286
178064
  if (getLeaseHolder(db, leaseKey) !== holderId || !isLeaseActive(db, leaseKey)) {
177287
178065
  return false;
177288
178066
  }
@@ -177293,7 +178071,7 @@ function renewLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177293
178071
  });
177294
178072
  }
177295
178073
  function runLeaseGuardedWrite(db, holderId, leaseKey, fn) {
177296
- return runImmediate2(db, () => {
178074
+ return runImmediate3(db, () => {
177297
178075
  if (!peekLeaseHolderAndExpiry(db, holderId, leaseKey)) {
177298
178076
  throw new Error("Dream lease lost before guarded write");
177299
178077
  }
@@ -177347,7 +178125,7 @@ function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalMs = LEASE_
177347
178125
  }
177348
178126
  function releaseLease(db, holderId, leaseKey = DREAMING_LEASE_KEY) {
177349
178127
  const keys = rowKeys(leaseKey);
177350
- runImmediate2(db, () => {
178128
+ runImmediate3(db, () => {
177351
178129
  if (getLeaseHolder(db, leaseKey) !== holderId) {
177352
178130
  return;
177353
178131
  }
@@ -183320,6 +184098,9 @@ async function sweepGitCommits(args) {
183320
184098
  }
183321
184099
  }
183322
184100
 
184101
+ // src/embedding-bootstrap.ts
184102
+ import { statSync as statSync7 } from "node:fs";
184103
+
183323
184104
  // ../plugin/src/plugin/embedding-bootstrap-helpers.ts
183324
184105
  import { createHash as createHash11 } from "node:crypto";
183325
184106
  init_logger();
@@ -183867,6 +184648,10 @@ function stripUnsafeProjectConfigFields(projectRaw) {
183867
184648
  delete projectRaw.sqlite;
183868
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).");
183869
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
+ }
183870
184655
  const embedding2 = projectRaw.embedding;
183871
184656
  if (isPlainObject3(embedding2)) {
183872
184657
  const removed = [];
@@ -184540,8 +185325,38 @@ function loadPiConfigDetailed(opts = {}) {
184540
185325
  }
184541
185326
 
184542
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
+ }
184543
185350
  async function ensureProjectRegisteredFromPiDirectory(directory, db) {
184544
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;
184545
185360
  const detailed = loadPiConfigDetailed({ cwd: directory });
184546
185361
  if (isConfigLoadUntrusted(detailed)) {
184547
185362
  handleUntrustedLoad(db, projectIdentity, directory, detailed);
@@ -184552,315 +185367,65 @@ async function ensureProjectRegisteredFromPiDirectory(directory, db) {
184552
185367
  gitCommitEnabled: detailed.config.memory.git_commit_indexing.enabled
184553
185368
  };
184554
185369
  registerProjectEmbedding(db, projectIdentity, detailed.config.embedding, features, directory);
184555
- }
184556
-
184557
- // src/read-session-pi.ts
184558
- var SYNTH_USER_ID_PREFIX = "synth-user-";
184559
- function resolvePiStableId(msg, index, entryIds, entryIdByRef) {
184560
- if (!msg || typeof msg !== "object")
184561
- return;
184562
- const byRef = entryIdByRef?.get(msg);
184563
- if (typeof byRef === "string" && byRef.length > 0)
184564
- return byRef;
184565
- const positional = entryIds?.[index];
184566
- if (typeof positional === "string" && positional.length > 0)
184567
- return positional;
184568
- const m = msg;
184569
- const role = m.role ?? "unknown";
184570
- return typeof m.timestamp === "number" ? `pi-msg-${index}-${m.timestamp}-${role}` : `pi-msg-${index}-${role}`;
184571
- }
184572
- function isMidTurnPi(event, _sessionId) {
184573
- const messages = event?.messages;
184574
- if (!Array.isArray(messages))
184575
- return false;
184576
- let latestAssistantIndex = -1;
184577
- let latestAssistant = null;
184578
- for (let i = messages.length - 1;i >= 0; i--) {
184579
- const msg = messages[i];
184580
- if (msg !== null && typeof msg === "object") {
184581
- const record2 = msg;
184582
- if (record2.role === "assistant") {
184583
- latestAssistantIndex = i;
184584
- latestAssistant = record2;
184585
- break;
184586
- }
184587
- }
184588
- }
184589
- if (latestAssistant === null)
184590
- return false;
184591
- if (hasRealUserAfter(messages, latestAssistantIndex))
184592
- return false;
184593
- if (latestAssistant.stopReason === "toolUse")
184594
- return true;
184595
- const toolCallIds = getToolCallIds(latestAssistant.content);
184596
- if (toolCallIds.size === 0)
184597
- return false;
184598
- const pairedToolResultIds = new Set;
184599
- for (const msg of messages.slice(latestAssistantIndex + 1)) {
184600
- if (msg === null || typeof msg !== "object")
184601
- continue;
184602
- const record2 = msg;
184603
- if (record2.role !== "toolResult")
184604
- continue;
184605
- if (typeof record2.toolCallId === "string") {
184606
- pairedToolResultIds.add(record2.toolCallId);
184607
- }
184608
- }
184609
- for (const id of toolCallIds) {
184610
- if (!pairedToolResultIds.has(id))
184611
- return true;
184612
- }
184613
- return false;
184614
- }
184615
- function hasRealUserAfter(messages, latestAssistantIndex) {
184616
- for (const msg of messages.slice(latestAssistantIndex + 1)) {
184617
- if (msg === null || typeof msg !== "object")
184618
- continue;
184619
- if (msg.role === "user")
184620
- return true;
184621
- }
184622
- return false;
184623
- }
184624
- function getToolCallIds(content) {
184625
- const ids = new Set;
184626
- if (!Array.isArray(content))
184627
- return ids;
184628
- for (const item of content) {
184629
- if (item === null || typeof item !== "object")
184630
- continue;
184631
- const record2 = item;
184632
- if (record2.type === "toolCall" && typeof record2.id === "string") {
184633
- ids.add(record2.id);
184634
- }
184635
- }
184636
- return ids;
184637
- }
184638
- function readPiSessionMessages(ctx) {
184639
- const sm = ctx.sessionManager;
184640
- if (sm === undefined)
184641
- return [];
184642
- const getBranch = sm.getBranch;
184643
- if (typeof getBranch !== "function")
184644
- return [];
184645
- let entries;
184646
- try {
184647
- entries = getBranch.call(sm);
184648
- } catch {
184649
- return [];
184650
- }
184651
- if (!Array.isArray(entries))
184652
- return [];
184653
- return convertEntriesToRawMessages(entries);
184654
- }
184655
- function findLastModelKeyFromBranch(entries) {
184656
- if (!Array.isArray(entries))
184657
- return;
184658
- for (let i = entries.length - 1;i >= 0; i--) {
184659
- const e = entries[i];
184660
- if (!e || typeof e !== "object")
184661
- continue;
184662
- const v = e;
184663
- if (v.type !== "model_change")
184664
- continue;
184665
- if (typeof v.provider === "string" && v.provider.length > 0 && typeof v.modelId === "string" && v.modelId.length > 0) {
184666
- return `${v.provider}/${v.modelId}`;
184667
- }
184668
- }
184669
- return;
184670
- }
184671
- function rawEntryVersion(entry) {
184672
- const record2 = entry;
184673
- const updated = record2.updatedAt ?? record2.updated_at ?? record2.timestamp;
184674
- return typeof updated === "string" || typeof updated === "number" ? updated : entry.id;
184675
- }
184676
- function attachPiPartVersion(parts, version2) {
184677
- return parts.map((part) => {
184678
- if (part === null || typeof part !== "object" || Array.isArray(part))
184679
- return part;
184680
- try {
184681
- Object.defineProperty(part, "__magicContextPartUpdatedAt", {
184682
- value: version2,
184683
- enumerable: false,
184684
- configurable: true
184685
- });
184686
- } catch {}
184687
- return part;
185370
+ const fingerprintPaths = configCandidatePaths(directory, detailed.loadedFromPaths);
185371
+ registrationFingerprints.set(projectIdentity, {
185372
+ paths: fingerprintPaths,
185373
+ fingerprint: configFingerprint(fingerprintPaths)
184688
185374
  });
184689
185375
  }
184690
- function convertEntriesToRawMessages(entries) {
184691
- const result = [];
184692
- let nextOrdinal = 1;
184693
- let pendingToolParts = [];
184694
- let pendingFirstRealId = "";
184695
- let pendingFirstRealVersion = "";
184696
- for (const entry of entries) {
184697
- if (!isMessageEntry(entry)) {
184698
- continue;
184699
- }
184700
- const msg = entry.message;
184701
- const role = msg.role;
184702
- if (role === "toolResult") {
184703
- const version2 = rawEntryVersion(entry);
184704
- pendingToolParts.push(...attachPiPartVersion(synthesizeToolResultParts(msg), version2));
184705
- if (pendingFirstRealId === "") {
184706
- pendingFirstRealId = entry.id;
184707
- pendingFirstRealVersion = version2;
184708
- }
184709
- continue;
184710
- }
184711
- if (role === "user") {
184712
- const version2 = rawEntryVersion(entry);
184713
- const parts = [
184714
- ...pendingToolParts,
184715
- ...attachPiPartVersion(synthesizeUserParts(msg), version2)
184716
- ];
184717
- pendingToolParts = [];
184718
- pendingFirstRealId = "";
184719
- pendingFirstRealVersion = "";
184720
- result.push({
184721
- ordinal: nextOrdinal++,
184722
- id: entry.id,
184723
- role: "user",
184724
- parts,
184725
- version: version2
184726
- });
184727
- continue;
184728
- }
184729
- if (role === "assistant") {
184730
- if (pendingToolParts.length > 0) {
184731
- result.push({
184732
- ordinal: nextOrdinal++,
184733
- id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
184734
- role: "user",
184735
- parts: pendingToolParts,
184736
- version: pendingFirstRealVersion
184737
- });
184738
- pendingToolParts = [];
184739
- pendingFirstRealId = "";
184740
- 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");
184741
185392
  }
184742
- const version2 = rawEntryVersion(entry);
184743
- result.push({
184744
- ordinal: nextOrdinal++,
184745
- id: entry.id,
184746
- role: "assistant",
184747
- parts: attachPiPartVersion(synthesizeAssistantParts(msg), version2),
184748
- version: version2
184749
- });
184750
- continue;
185393
+ const require2 = createRequire3(process.argv[1]);
185394
+ const resolved = require2.resolve(PI_CODING_AGENT_MODULE);
185395
+ return await import(pathToFileURL2(resolved).href);
184751
185396
  }
184752
- result.push({
184753
- ordinal: nextOrdinal++,
184754
- id: entry.id,
184755
- role: typeof role === "string" ? role : "unknown",
184756
- parts: [],
184757
- version: rawEntryVersion(entry)
184758
- });
184759
- }
184760
- if (pendingToolParts.length > 0) {
184761
- result.push({
184762
- ordinal: nextOrdinal,
184763
- id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
184764
- role: "user",
184765
- parts: pendingToolParts,
184766
- version: pendingFirstRealVersion
184767
- });
184768
185397
  }
184769
- return result;
184770
- }
184771
- function isMessageEntry(value) {
184772
- if (value === null || typeof value !== "object")
184773
- return false;
184774
- const v = value;
184775
- if (v.type !== "message")
184776
- return false;
184777
- if (typeof v.id !== "string")
184778
- return false;
184779
- if (v.message === null || typeof v.message !== "object")
184780
- return false;
184781
- return true;
184782
- }
184783
- function synthesizeUserParts(msg) {
184784
- const m = msg;
184785
- if (typeof m.content === "string") {
184786
- if (m.content.trim().length === 0)
184787
- return [];
184788
- return [{ type: "text", text: m.content }];
184789
- }
184790
- if (!Array.isArray(m.content))
184791
- return [];
184792
- const parts = [];
184793
- for (const c of m.content) {
184794
- if (c === null || typeof c !== "object")
184795
- continue;
184796
- const cc = c;
184797
- if (cc.type === "text" && typeof cc.text === "string") {
184798
- parts.push({ type: "text", text: cc.text });
184799
- }
184800
- }
184801
- return parts;
184802
- }
184803
- function synthesizeAssistantParts(msg) {
184804
- const m = msg;
184805
- if (!Array.isArray(m.content))
184806
- return [];
184807
- const parts = [];
184808
- for (const c of m.content) {
184809
- if (c === null || typeof c !== "object")
184810
- continue;
184811
- const cc = c;
184812
- if (cc.type === "text" && typeof cc.text === "string") {
184813
- parts.push({ type: "text", text: cc.text });
184814
- } else if (cc.type === "toolCall" && typeof cc.id === "string") {
184815
- parts.push({
184816
- type: "tool",
184817
- tool: typeof cc.name === "string" ? cc.name : "unknown",
184818
- callID: cc.id,
184819
- state: {
184820
- input: cc.arguments ?? {}
184821
- }
184822
- });
184823
- }
184824
- }
184825
- return parts;
184826
- }
184827
- function synthesizeToolResultParts(msg) {
184828
- const m = msg;
184829
- const callID = typeof m.toolCallId === "string" ? m.toolCallId : "";
184830
- const tool = typeof m.toolName === "string" ? m.toolName : "unknown";
184831
- if (!callID)
184832
- return [];
184833
- let output = "";
184834
- if (Array.isArray(m.content)) {
184835
- const fragments = [];
184836
- for (const c of m.content) {
184837
- if (c === null || typeof c !== "object")
184838
- continue;
184839
- const cc = c;
184840
- if (cc.type === "text" && typeof cc.text === "string") {
184841
- 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);
184842
185412
  }
184843
185413
  }
184844
- output = fragments.join(`
184845
- `);
184846
- }
184847
- return [
184848
- {
184849
- type: "tool",
184850
- tool,
184851
- callID,
184852
- state: {
184853
- output
184854
- }
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;
184855
185423
  }
184856
- ];
185424
+ });
185425
+ return promise2;
184857
185426
  }
184858
-
184859
- // src/dreamer/pi-session-api.ts
184860
- import { readFileSync as readFileSync9 } from "node:fs";
184861
- var PI_CODING_AGENT_MODULE = "@earendil-works/pi-coding-agent";
184862
- async function loadDefaultPiSessionApi() {
184863
- const mod = await import(PI_CODING_AGENT_MODULE);
185427
+ async function loadDefaultPiSessionApi(loaders) {
185428
+ const mod = await resolvePiCodingAgentModule(loaders);
184864
185429
  const listSessions = mod.SessionManager?.listAll;
184865
185430
  if (typeof listSessions !== "function") {
184866
185431
  throw new Error("Pi session APIs unavailable: expected SessionManager.listAll on pi-coding-agent");
@@ -185261,6 +185826,8 @@ function makeMessage(role, parts) {
185261
185826
  }
185262
185827
 
185263
185828
  // src/commands/pi-command-utils.ts
185829
+ init_logger();
185830
+ import { Box, Text } from "@earendil-works/pi-tui";
185264
185831
  var CTX_STATUS_CUSTOM_TYPE = "ctx-status";
185265
185832
  function resolveSessionId(ctx) {
185266
185833
  const sm = ctx.sessionManager;
@@ -185274,13 +185841,49 @@ function resolveSessionId(ctx) {
185274
185841
  return;
185275
185842
  }
185276
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
+ }
185277
185878
  function sendCtxStatusMessage(pi, content, details) {
185278
- pi.sendMessage({
185279
- customType: CTX_STATUS_CUSTOM_TYPE,
185280
- content: content.text,
185281
- display: true,
185282
- details: { ...content, details }
185283
- }, { 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}`);
185284
185887
  }
185285
185888
 
185286
185889
  // src/commands/ctx-dream.ts
@@ -185740,8 +186343,8 @@ function makeToolCompositeKey(ownerMsgId, callId) {
185740
186343
  return `${ownerMsgId}${TOOL_COMPOSITE_KEY_SEP}${callId}`;
185741
186344
  }
185742
186345
  var GET_COUNTER_SQL = `SELECT counter FROM session_meta WHERE session_id = ?`;
185743
- var GET_ASSIGNMENTS_SQL = "SELECT message_id, tag_number, type, tool_owner_message_id FROM tags WHERE session_id = ? ORDER BY tag_number ASC";
185744
- 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";
185745
186348
  var PROBE_DATA_VERSION_SQL = "PRAGMA main.data_version";
185746
186349
  var probeDataVersionStatements = new WeakMap;
185747
186350
  function getProbeDataVersionStatement(db) {
@@ -185765,6 +186368,14 @@ function isAssignmentRow(row) {
185765
186368
  return false;
185766
186369
  if (candidate.tool_owner_message_id !== null && typeof candidate.tool_owner_message_id !== "string")
185767
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;
185768
186379
  return true;
185769
186380
  }
185770
186381
  var UPSERT_COUNTER_SQL = `
@@ -185799,6 +186410,7 @@ var MAX_TAG_ALLOC_RETRIES = 5;
185799
186410
  function createTagger() {
185800
186411
  const counters = new Map;
185801
186412
  const assignments = new Map;
186413
+ const toolAccountingBySession = new Map;
185802
186414
  const loadSignatures = new Map;
185803
186415
  function getSessionAssignments(sessionId) {
185804
186416
  let map2 = assignments.get(sessionId);
@@ -185808,6 +186420,14 @@ function createTagger() {
185808
186420
  }
185809
186421
  return map2;
185810
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
+ }
185811
186431
  function isUniqueConstraintError(error51) {
185812
186432
  return error51 instanceof Error && "code" in error51 && error51.code === "SQLITE_CONSTRAINT_UNIQUE";
185813
186433
  }
@@ -185861,6 +186481,14 @@ function createTagger() {
185861
186481
  }
185862
186482
  counters.set(sessionId, next);
185863
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
+ }
185864
186492
  return next;
185865
186493
  }
185866
186494
  throw new Error(`tagger.allocateTag: failed to allocate tag for session=${sessionId} key=${mapKey} after ${MAX_TAG_ALLOC_RETRIES} retries`);
@@ -185921,6 +186549,13 @@ function createTagger() {
185921
186549
  function getToolTag(sessionId, callId, ownerMsgId) {
185922
186550
  return assignments.get(sessionId)?.get(makeToolCompositeKey(ownerMsgId, callId));
185923
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
+ }
185924
186559
  function bindTag(sessionId, messageId, tagNumber) {
185925
186560
  getSessionAssignments(sessionId).set(messageId, tagNumber);
185926
186561
  }
@@ -185939,6 +186574,7 @@ function createTagger() {
185939
186574
  function resetCounter(sessionId, db) {
185940
186575
  counters.set(sessionId, 0);
185941
186576
  assignments.delete(sessionId);
186577
+ toolAccountingBySession.delete(sessionId);
185942
186578
  loadSignatures.delete(sessionId);
185943
186579
  getResetCounterStatement(db).run(sessionId, getHarness());
185944
186580
  }
@@ -185961,11 +186597,19 @@ function createTagger() {
185961
186597
  const assignmentRows = (floor > 0 ? db.prepare(GET_ASSIGNMENTS_SCOPED_SQL).all(sessionId, floor) : db.prepare(GET_ASSIGNMENTS_SQL).all(sessionId)).filter(isAssignmentRow);
185962
186598
  const sessionAssignments = getSessionAssignments(sessionId);
185963
186599
  sessionAssignments.clear();
186600
+ const sessionToolAccounting = getSessionToolAccounting(sessionId);
186601
+ sessionToolAccounting.clear();
185964
186602
  let maxTagNumber = 0;
185965
186603
  for (const assignment of assignmentRows) {
185966
186604
  if (assignment.type === "tool") {
185967
186605
  if (assignment.tool_owner_message_id !== null) {
185968
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
+ });
185969
186613
  }
185970
186614
  } else {
185971
186615
  sessionAssignments.set(assignment.message_id, assignment.tag_number);
@@ -185985,6 +186629,7 @@ function createTagger() {
185985
186629
  function cleanup(sessionId) {
185986
186630
  counters.delete(sessionId);
185987
186631
  assignments.delete(sessionId);
186632
+ toolAccountingBySession.delete(sessionId);
185988
186633
  loadSignatures.delete(sessionId);
185989
186634
  }
185990
186635
  return {
@@ -185992,6 +186637,8 @@ function createTagger() {
185992
186637
  assignToolTag,
185993
186638
  getTag,
185994
186639
  getToolTag,
186640
+ getToolTagAccounting,
186641
+ setToolTagAccounting,
185995
186642
  bindTag,
185996
186643
  unbindTag,
185997
186644
  bindToolTag,
@@ -187967,26 +188614,28 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
187967
188614
  sessionLog(sessionId, `compartment trigger: skipped — historian already in progress (usage=${usage.percentage.toFixed(1)}%)`);
187968
188615
  return { shouldFire: false };
187969
188616
  }
187970
- if (inMemoryTail) {
188617
+ const lazyInMemoryTail = typeof inMemoryTail === "function" ? inMemoryTail : undefined;
188618
+ let resolvedInMemoryTail = typeof inMemoryTail === "function" ? undefined : inMemoryTail;
188619
+ if (resolvedInMemoryTail) {
187971
188620
  try {
187972
188621
  const policyVersion = loadProtectedTailMeta(db, sessionId).protectedTailPolicyVersion;
187973
188622
  if (policyVersion < 3)
187974
- inMemoryTail = undefined;
188623
+ resolvedInMemoryTail = undefined;
187975
188624
  } catch {
187976
- inMemoryTail = undefined;
188625
+ resolvedInMemoryTail = undefined;
187977
188626
  }
187978
188627
  }
187979
- 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;
187980
188629
  const proactiveFloorForGate = getProactiveCompartmentTriggerPercentage(executeThresholdPercentage);
187981
188630
  if (usage.percentage < proactiveFloorForGate) {
187982
188631
  try {
187983
- const boundFloor = inMemoryTail ? taggerFloor : 0;
188632
+ const boundFloor = resolvedInMemoryTail || lazyInMemoryTail ? taggerFloor : 0;
187984
188633
  const { bound: persistedBound, nullCount } = getTriggerTagTokenUpperBound(db, sessionId, boundFloor);
187985
188634
  if (nullCount === 0) {
187986
- const untaggedUpperBound = inMemoryTail ? estimateUntaggedInMemoryTailUpperBound(db, sessionId, inMemoryTail, taggerFloor) : 0;
188635
+ const untaggedUpperBound = resolvedInMemoryTail ? estimateUntaggedInMemoryTailUpperBound(db, sessionId, resolvedInMemoryTail, taggerFloor) : 0;
187987
188636
  const eligibleUpperBound = persistedBound + untaggedUpperBound;
187988
188637
  if (eligibleUpperBound < triggerBudget) {
187989
- const memorySuffix = inMemoryTail ? ` (persisted=${persistedBound}, untagged-memory≤${untaggedUpperBound})` : "";
188638
+ const memorySuffix = resolvedInMemoryTail ? ` (persisted=${persistedBound}, untagged-memory≤${untaggedUpperBound})` : "";
187990
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`);
187991
188640
  return { shouldFire: false };
187992
188641
  }
@@ -187995,7 +188644,16 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
187995
188644
  sessionLog(sessionId, `compartment trigger: cheap-gate skipped (falling through to full read): ${error51 instanceof Error ? error51.message : String(error51)}`);
187996
188645
  }
187997
188646
  }
187998
- 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);
187999
188657
  if (!tailInfo.hasNewRawHistory) {
188000
188658
  try {
188001
188659
  const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
@@ -188022,11 +188680,11 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
188022
188680
  }
188023
188681
  const scale = usage.percentage >= BLOCK_UNTIL_DONE_PERCENTAGE ? 0.25 : 0.5;
188024
188682
  const scaledBoundary = withRawSessionMessageCache(() => {
188025
- if (inMemoryTail) {
188683
+ if (resolvedInMemoryTail) {
188026
188684
  primeInMemoryTailRawMessageCache({
188027
188685
  sessionId,
188028
- messages: inMemoryTail.messages,
188029
- absoluteMessageCount: inMemoryTail.absoluteMessageCount
188686
+ messages: resolvedInMemoryTail.messages,
188687
+ absoluteMessageCount: resolvedInMemoryTail.absoluteMessageCount
188030
188688
  });
188031
188689
  }
188032
188690
  return resolveOpenCodeProtectedTailBoundary({
@@ -188313,12 +188971,26 @@ function computeBudgetPressure(compartments, historyBudget) {
188313
188971
 
188314
188972
  // ../plugin/src/hooks/magic-context/decay-render.ts
188315
188973
  var DEFAULT_HISTORY_BUDGET_TOKENS = 60000;
188316
- function escapeXmlAttr2(s) {
188317
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
188318
- }
188319
188974
  function escapeXmlContent2(s) {
188320
188975
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
188321
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
+ }
188322
188994
  function isTieredRow(c) {
188323
188995
  return typeof c.p1 === "string" && c.p1.length > 0;
188324
188996
  }
@@ -188348,25 +189020,22 @@ function renderCompartmentAtTier(c, tier2) {
188348
189020
  return renderOneCompartment(c, tier2);
188349
189021
  }
188350
189022
  function renderOneCompartment(c, tier2) {
188351
- const baseAttrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr2(c.title)}"`;
188352
189023
  if (tier2 >= 5)
188353
189024
  return "";
189025
+ const heading = compartmentHeading(c);
188354
189026
  if (c.legacy === 1 || !isTieredRow(c)) {
188355
189027
  const flat = (c.content ?? "").trim();
188356
189028
  if (tier2 >= 4 || flat.length === 0)
188357
- return `<compartment ${baseAttrs} />`;
188358
- return [
188359
- `<compartment ${baseAttrs}>`,
188360
- escapeXmlContent2(legacyBodyForTier(flat, tier2)),
188361
- "</compartment>"
188362
- ].join(`
188363
- `);
189029
+ return heading;
189030
+ const body2 = guardCompartmentBody(escapeXmlContent2(legacyBodyForTier(flat, tier2)));
189031
+ return `${heading}
189032
+ ${body2}`;
188364
189033
  }
188365
189034
  const body = tierBody(c, tier2);
188366
189035
  if (body.length === 0)
188367
- return `<compartment ${baseAttrs} />`;
188368
- return [`<compartment ${baseAttrs}>`, escapeXmlContent2(body), "</compartment>"].join(`
188369
- `);
189036
+ return heading;
189037
+ return `${heading}
189038
+ ${guardCompartmentBody(escapeXmlContent2(body))}`;
188370
189039
  }
188371
189040
  function computeTiers(compartments, historyBudgetTokens) {
188372
189041
  const v2Compartments = compartments.map((c, originalIndex) => ({ c, originalIndex })).filter(({ c }) => c.legacy !== 1);
@@ -188489,33 +189158,9 @@ function getVisibleMemoryIds(db, sessionId) {
188489
189158
  }
188490
189159
  }
188491
189160
  function renderMemoryBlock(memories) {
188492
- const byCategory = new Map;
188493
- for (const m of memories) {
188494
- const existing = byCategory.get(m.category);
188495
- if (existing) {
188496
- existing.push(m);
188497
- } else {
188498
- byCategory.set(m.category, [m]);
188499
- }
188500
- }
188501
- const sections = [];
188502
- for (const category of CATEGORY_PRIORITY) {
188503
- const categoryMemories = byCategory.get(category);
188504
- if (!categoryMemories || categoryMemories.length === 0) {
188505
- continue;
188506
- }
188507
- sections.push(`<${category}>`, ...categoryMemories.map((m) => `- ${escapeXmlContent(m.content)}`), `</${category}>`);
188508
- }
188509
- if (sections.length === 0) {
188510
- return null;
188511
- }
188512
- return `<project-memory>
188513
- ${sections.join(`
188514
- `)}
188515
- </project-memory>`;
189161
+ return renderMemoryBlockV2(memories) || null;
188516
189162
  }
188517
189163
  var DEFAULT_MEMORY_BUDGET_TOKENS = 8000;
188518
- var MEMORY_BLOCK_WRAPPER_TOKENS = 6;
188519
189164
  var DEFAULT_USER_PROFILE_BUDGET_TOKENS = 4000;
188520
189165
  function memoryCanonicalIdentity(memory, workspace) {
188521
189166
  return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
@@ -188533,11 +189178,18 @@ function memorySelectionOrder(left, right) {
188533
189178
  return left.id - right.id;
188534
189179
  }
188535
189180
  function memoryRenderOrder(left, right) {
188536
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
188537
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
188538
- const categoryDiff = aPriority - bPriority;
188539
- if (categoryDiff !== 0)
188540
- 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
+ }
188541
189193
  return left.id - right.id;
188542
189194
  }
188543
189195
  var maxCompartmentSeqStatements = new WeakMap;
@@ -188548,13 +189200,13 @@ var newCompartmentStatements = new WeakMap;
188548
189200
  function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
188549
189201
  const selectionOrder = [...memories].sort(memorySelectionOrder);
188550
189202
  const selected = [];
188551
- let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
189203
+ const accounting = createMemoryBlockAccounting(renderOptions);
188552
189204
  for (const memory of selectionOrder) {
188553
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
188554
- if (usedTokens + memoryTokens > budgetTokens)
189205
+ const cost = accounting.candidateCost(memory);
189206
+ if (accounting.usedTokens + cost > budgetTokens)
188555
189207
  continue;
189208
+ accounting.admit(memory, cost);
188556
189209
  selected.push(memory);
188557
- usedTokens += memoryTokens;
188558
189210
  }
188559
189211
  if (selected.length < memories.length) {
188560
189212
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
@@ -188568,23 +189220,22 @@ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, work
188568
189220
  }
188569
189221
  const selected = [];
188570
189222
  const selectedIds = new Set;
188571
- let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
188572
- const tokenCost = (memory) => estimateTokens(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
189223
+ const accounting = createMemoryBlockAccounting(renderOptions);
188573
189224
  const trySelect = (memory) => {
188574
189225
  if (selectedIds.has(memory.id))
188575
189226
  return false;
188576
- const tokens = tokenCost(memory);
188577
- if (usedTokens + tokens > budgetTokens)
189227
+ const cost = accounting.candidateCost(memory);
189228
+ if (accounting.usedTokens + cost > budgetTokens)
188578
189229
  return false;
188579
189230
  selected.push(memory);
188580
189231
  selectedIds.add(memory.id);
188581
- usedTokens += tokens;
189232
+ accounting.admit(memory, cost);
188582
189233
  return true;
188583
189234
  };
188584
189235
  for (const memory of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
188585
189236
  trySelect(memory);
188586
189237
  }
188587
- const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
189238
+ const remainingAfterPermanent = Math.max(0, budgetTokens - accounting.usedTokens);
188588
189239
  const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
188589
189240
  const byIdentity = new Map;
188590
189241
  for (const memory of memories) {
@@ -188603,15 +189254,15 @@ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, work
188603
189254
  for (const memory of candidates) {
188604
189255
  if (selectedIds.has(memory.id))
188605
189256
  continue;
188606
- const tokens = tokenCost(memory);
188607
- if (memberTokens + tokens > floorTokens)
189257
+ const cost = accounting.candidateCost(memory);
189258
+ if (memberTokens + cost > floorTokens)
188608
189259
  continue;
188609
- if (usedTokens + tokens > budgetTokens)
189260
+ if (accounting.usedTokens + cost > budgetTokens)
188610
189261
  continue;
188611
189262
  selected.push(memory);
188612
189263
  selectedIds.add(memory.id);
188613
- usedTokens += tokens;
188614
- memberTokens += tokens;
189264
+ accounting.admit(memory, cost);
189265
+ memberTokens += cost;
188615
189266
  }
188616
189267
  }
188617
189268
  const remaining = memories.filter((memory) => !selectedIds.has(memory.id)).sort(memorySelectionOrder);
@@ -188635,17 +189286,55 @@ function trimUserMemoriesToBudget(memories, budgetTokens) {
188635
189286
  }
188636
189287
  return selected;
188637
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
+ }
188638
189317
  function renderMemoryLineV2(memory, sourceName) {
188639
- const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
188640
- 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)}`;
188641
189320
  }
188642
189321
  function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
188643
189322
  if (memories.length === 0)
188644
189323
  return "";
189324
+ const ordered = [...memories].sort(memoryRenderOrder);
188645
189325
  const lines = [`<${wrapper}>`];
188646
- 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
+ }
188647
189334
  lines.push(renderMemoryLineV2(memory, renderOptions.sourceNameByMemoryId?.get(memory.id)));
188648
189335
  }
189336
+ if (openCategory !== undefined)
189337
+ lines.push(`</${escapeXmlAttr(openCategory)}>`);
188649
189338
  lines.push(`</${wrapper}>`);
188650
189339
  return lines.join(`
188651
189340
  `);
@@ -188939,6 +189628,7 @@ function readUint32BE(b, offset) {
188939
189628
  function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
188940
189629
  const skipPrefixInjection = options.skipPrefixInjection === true;
188941
189630
  const targets = new Map;
189631
+ const timing = options.onTiming ? { identity: 0, prefix: 0, targets: 0, tokenCounting: 0 } : undefined;
188942
189632
  const toolAggregates = new Map;
188943
189633
  const openToolAggregateKeysByCallId = new Map;
188944
189634
  let activeToolResultRun;
@@ -188948,6 +189638,7 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
188948
189638
  continue;
188949
189639
  activeToolResultRun = undefined;
188950
189640
  const messageId = message.info.id;
189641
+ const reuseIdentity = messageId !== undefined && options.reuseMessageIds?.has(messageId) === true;
188951
189642
  let textOrdinal = 0;
188952
189643
  const parts = message.parts;
188953
189644
  for (let partIndex = 0;partIndex < parts.length; partIndex += 1) {
@@ -188973,7 +189664,10 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
188973
189664
  db,
188974
189665
  targets,
188975
189666
  skipPrefixInjection,
188976
- entryFingerprint: options.entryFingerprintByMessageId?.get(messageId) ?? null
189667
+ entryFingerprint: options.entryFingerprintByMessageId?.get(messageId) ?? null,
189668
+ reuseIdentity,
189669
+ timing,
189670
+ textTokenCache: options.textTokenCache
188977
189671
  });
188978
189672
  textOrdinal += 1;
188979
189673
  continue;
@@ -188983,12 +189677,8 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
188983
189677
  activeToolResultRun = undefined;
188984
189678
  continue;
188985
189679
  }
189680
+ const identityStart = timing ? performance.now() : 0;
188986
189681
  const callId = part.id;
188987
- const text = part.getText() ?? "";
188988
- const toolByteSize = getToolPartByteSize(part, text);
188989
- const toolTokenCount = getToolPartTokenCount(part, text);
188990
- const meta3 = part.getToolMetadata();
188991
- const inputTokenCount = meta3.inputTokenCount;
188992
189682
  if (typeof callId !== "string" || callId.length === 0) {
188993
189683
  activeToolResultRun = undefined;
188994
189684
  tagToolPart({
@@ -189001,7 +189691,9 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
189001
189691
  tagger,
189002
189692
  db,
189003
189693
  targets,
189004
- skipPrefixInjection
189694
+ skipPrefixInjection,
189695
+ reuseIdentity,
189696
+ timing
189005
189697
  });
189006
189698
  continue;
189007
189699
  }
@@ -189017,79 +189709,234 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
189017
189709
  const aggregateKey = existingKey ?? makeToolCompositeKey(messageId, callId);
189018
189710
  const existing = toolAggregates.get(aggregateKey);
189019
189711
  if (existing) {
189020
- existing.occurrences.push({
189021
- 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,
189022
189767
  part,
189023
- kind: part.kind
189768
+ text: text2,
189769
+ tagId: existing.tagId,
189770
+ aggregate: existing,
189771
+ targets,
189772
+ timing
189024
189773
  });
189025
- if (part.kind === "tool_result" && toolByteSize > existing.maxByteSize) {
189026
- existing.maxByteSize = toolByteSize;
189027
- existing.maxTokenCount = toolTokenCount;
189028
- updateTagByteSize(db, sessionId, existing.tagId, toolByteSize);
189029
- updateTagTokenCount(db, sessionId, existing.tagId, toolTokenCount);
189030
- }
189031
- if (existing.toolName === null && meta3.toolName) {
189032
- existing.toolName = meta3.toolName;
189033
- }
189034
- if (existing.inputByteSize === 0 && part.kind === "tool_use" && meta3.inputByteSize > 0) {
189035
- existing.inputByteSize = meta3.inputByteSize;
189036
- updateTagInputByteSize(db, sessionId, existing.tagId, meta3.inputByteSize);
189037
- }
189038
- if (!existing.inputTokenStored && part.kind === "tool_use" && inputTokenCount > 0) {
189039
- existing.inputTokenStored = true;
189040
- updateTagInputTokenCount(db, sessionId, existing.tagId, inputTokenCount);
189041
- }
189042
- if (!skipPrefixInjection && part.kind === "tool_result") {
189043
- part.setText(prependTag(existing.tagId, text));
189044
- }
189045
- targets.set(existing.tagId, buildAggregateTarget(existing.tagId, existing.occurrences));
189046
189774
  if (part.kind === "tool_result") {
189047
189775
  markToolAggregateResolved(callId, aggregateKey, openToolAggregateKeysByCallId);
189048
189776
  activeToolResultRun = { callId, aggregateKey };
189049
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;
189050
189813
  } else {
189051
- const outputByteSize = part.kind === "tool_result" ? toolByteSize : 0;
189052
- const outputTokenCount = part.kind === "tool_result" ? toolTokenCount : 0;
189053
- const firstInputTokenCount = part.kind === "tool_use" ? inputTokenCount : 0;
189054
- 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, () => ({
189055
189820
  tokenCount: outputTokenCount,
189056
189821
  inputTokenCount: firstInputTokenCount,
189057
189822
  reasoningTokenCount: null
189058
189823
  }));
189059
- const aggregate = {
189824
+ const persistedAccounting = tagger.getToolTagAccounting(sessionId, callId, messageId);
189825
+ aggregate = {
189060
189826
  callId,
189061
189827
  tagId,
189062
- occurrences: [
189063
- {
189064
- message,
189065
- part,
189066
- kind: part.kind
189067
- }
189068
- ],
189069
- maxByteSize: outputByteSize,
189070
- maxTokenCount: outputTokenCount,
189071
- toolName: meta3.toolName ?? null,
189072
- inputByteSize: part.kind === "tool_use" ? meta3.inputByteSize : 0,
189073
- 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)
189074
189835
  };
189075
- toolAggregates.set(aggregateKey, aggregate);
189076
- if (part.kind === "tool_use") {
189077
- openToolAggregateKeysByCallId.set(callId, [...pendingKeys, aggregateKey]);
189078
- }
189079
- if (!skipPrefixInjection && part.kind === "tool_result") {
189080
- part.setText(prependTag(tagId, text));
189081
- }
189082
- targets.set(tagId, buildAggregateTarget(tagId, aggregate.occurrences));
189083
189836
  if (part.kind === "tool_result") {
189084
- markToolAggregateResolved(callId, aggregateKey, openToolAggregateKeysByCallId);
189085
- 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
+ });
189086
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 };
189087
189869
  }
189088
189870
  }
189089
189871
  }
189090
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
+ }
189091
189879
  return { targets };
189092
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
+ }
189093
189940
  function findLastUnresolvedToolAggregateKey(pendingKeys, toolAggregates) {
189094
189941
  for (let i = pendingKeys.length - 1;i >= 0; i -= 1) {
189095
189942
  const key = pendingKeys[i];
@@ -189140,6 +189987,15 @@ function getToolPartTokenCount(part, text) {
189140
189987
  }
189141
189988
  return 0;
189142
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
+ }
189143
189999
  function getNonTextToolResultByteSize(part) {
189144
190000
  const raw = part.rawByteSize?.();
189145
190001
  if (typeof raw === "number" && raw > 0)
@@ -189160,39 +190016,97 @@ function isRecord2(value) {
189160
190016
  return typeof value === "object" && value !== null;
189161
190017
  }
189162
190018
  function tagTextPart(args) {
190019
+ const identityStart = args.timing ? performance.now() : 0;
189163
190020
  const text = args.part.getText() ?? "";
189164
190021
  const contentId = `${args.messageId}:p${args.textOrdinal}`;
189165
- const tagId = args.tagger.assignTag(args.sessionId, contentId, "message", byteSize(text), args.db, 0, null, 0, args.entryFingerprint, () => ({
189166
- tokenCount: estimateTagTextTokens(stripTagPrefix(text)),
189167
- inputTokenCount: null,
189168
- reasoningTokenCount: null
189169
- }));
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
+ });
189170
190045
  const sourceContent = stripTagPrefix(text);
189171
190046
  if (sourceContent.trim().length > 0) {
189172
190047
  saveSourceContent(args.db, args.sessionId, tagId, sourceContent);
189173
190048
  }
190049
+ if (args.timing)
190050
+ args.timing.identity += performance.now() - identityStart;
190051
+ applyTextPrefixAndTarget(args, tagId, text);
190052
+ }
190053
+ function applyTextPrefixAndTarget(args, tagId, text) {
189174
190054
  if (!args.skipPrefixInjection) {
190055
+ const prefixStart = args.timing ? performance.now() : 0;
189175
190056
  args.part.setText(prependTag(tagId, text));
190057
+ if (args.timing)
190058
+ args.timing.prefix += performance.now() - prefixStart;
189176
190059
  }
190060
+ const targetStart = args.timing ? performance.now() : 0;
189177
190061
  args.targets.set(tagId, buildTextTarget(args.part, args.message));
190062
+ if (args.timing)
190063
+ args.timing.targets += performance.now() - targetStart;
189178
190064
  }
189179
190065
  function tagToolPart(args) {
190066
+ const identityStart = args.timing ? performance.now() : 0;
189180
190067
  const stableId = args.part.id;
189181
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
+ }
189182
190077
  const text = args.part.getText() ?? "";
189183
190078
  const toolByteSize = getToolPartByteSize(args.part, text);
189184
- const toolTokenCount = getToolPartTokenCount(args.part, text);
189185
190079
  const meta3 = args.part.getToolMetadata();
189186
- const tagId = args.tagger.assignToolTag(args.sessionId, contentId, contentId, toolByteSize, args.db, 0, meta3.toolName ?? null, meta3.inputByteSize, () => ({
189187
- tokenCount: toolTokenCount,
189188
- inputTokenCount: meta3.inputTokenCount,
189189
- reasoningTokenCount: null
189190
- }));
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) {
189191
190100
  if (!args.skipPrefixInjection && args.part.kind === "tool_result") {
189192
- const tagged = prependTag(tagId, text);
189193
- 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;
189194
190105
  }
190106
+ const targetStart = args.timing ? performance.now() : 0;
189195
190107
  args.targets.set(tagId, buildToolTarget(args.part, args.message, tagId));
190108
+ if (args.timing)
190109
+ args.timing.targets += performance.now() - targetStart;
189196
190110
  }
189197
190111
  function setToolContentOrText(part, content) {
189198
190112
  try {
@@ -190472,6 +191386,7 @@ async function runAutoSearchHintForPi(args) {
190472
191386
  sessionLog(sessionId, "Pi auto-search: strict entry-id resolution failed; replayed persisted decisions only");
190473
191387
  return messages;
190474
191388
  }
191389
+ await args.ensureProjectRegistered?.();
190475
191390
  const writeNoHintAndReconcile = (reason) => {
190476
191391
  const outcome2 = appendAutoSearchHintDecision(db, sessionId, {
190477
191392
  messageId: userMsgId,
@@ -190619,8 +191534,17 @@ function getEntryId(entry) {
190619
191534
  const id = entry.id;
190620
191535
  return typeof id === "string" ? id : null;
190621
191536
  }
190622
- function findEntryIndex(branchEntries, entryId) {
190623
- 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);
190624
191548
  }
190625
191549
 
190626
191550
  // src/ctx-reduce-nudge-pi.ts
@@ -191319,7 +192243,7 @@ init_logger();
191319
192243
  function resolveStableId(msg, index, entryIds) {
191320
192244
  return resolvePiStableId(msg, index, entryIds) ?? "";
191321
192245
  }
191322
- function trimPiMessagesToBoundary(piMessages, entryIds, cutoffMessageId) {
192246
+ function trimPiMessagesToBoundary(piMessages, entryIds, cutoffMessageId, trimMutableEntryIds = false) {
191323
192247
  if (cutoffMessageId.length === 0)
191324
192248
  return 0;
191325
192249
  const effectiveCutoffId = cutoffMessageId.startsWith(SYNTH_USER_ID_PREFIX) ? cutoffMessageId.slice(SYNTH_USER_ID_PREFIX.length) : cutoffMessageId;
@@ -191386,6 +192310,10 @@ function trimPiMessagesToBoundary(piMessages, entryIds, cutoffMessageId) {
191386
192310
  const kept = piMessages.filter((_, index) => !remove.has(index));
191387
192311
  const removed = piMessages.length - kept.length;
191388
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
+ }
191389
192317
  return removed;
191390
192318
  }
191391
192319
  function toolPairKey(callId, assistantIndex) {
@@ -191496,6 +192424,23 @@ var EMPTY_PI_HARD_SIGNALS = {
191496
192424
  cacheExpired: false,
191497
192425
  lastResponseTime: 0
191498
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
+ }
191499
192444
  function decodeCachedM0(value) {
191500
192445
  if (!value)
191501
192446
  return null;
@@ -191514,6 +192459,20 @@ function getCachedBoundary(db, sessionId) {
191514
192459
  const row = db.prepare("SELECT cached_m0_last_baseline_end_message_id AS boundary FROM session_meta WHERE session_id = ?").get(sessionId);
191515
192460
  return typeof row?.boundary === "string" && row.boundary.length > 0 ? row.boundary : null;
191516
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
+ }
191517
192476
  function setCachedBoundary(db, sessionId, boundary) {
191518
192477
  db.prepare("UPDATE session_meta SET cached_m0_last_baseline_end_message_id = ? WHERE session_id = ?").run(boundary, sessionId);
191519
192478
  }
@@ -191583,7 +192542,7 @@ function readCurrentMarkersFromCompartments(db, state, compartments, projectDocs
191583
192542
  function mustMaterializePi(state, db, currentCompartmentsOverride) {
191584
192543
  const meta3 = getOrCreateSessionMeta(db, state.sessionId);
191585
192544
  const currentCompartments = currentCompartmentsOverride ?? getCompartments(db, state.sessionId);
191586
- const current = readCurrentMarkersFromCompartments(db, state, currentCompartments);
192545
+ const current = readCurrentMarkersFromCompartments(db, state, currentCompartments, meta3.cachedM0ProjectDocsHash ?? undefined);
191587
192546
  if (!meta3.cachedM0Bytes)
191588
192547
  return { value: true, reason: "first_render" };
191589
192548
  if (!meta3.cachedM1Bytes)
@@ -192217,8 +193176,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
192217
193176
  const M1_ABSOLUTE_CAP_RATIO = 0.2;
192218
193177
  const m1AbsoluteBudget = (state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS) * M1_ABSOLUTE_CAP_RATIO;
192219
193178
  const m1HasContent = m1 !== PI_M1_PLACEHOLDER;
192220
- const m1Tokens = m1HasContent ? estimateTokens(m1) : 0;
192221
- const m0Tokens = estimateTokens(m0);
193179
+ const { m0Tokens, m1Tokens } = cachedInjectionTokenCounts(state.sessionId, m0, m1);
192222
193180
  const m1OverAbsoluteCap = m1HasContent && m1Tokens > m1AbsoluteBudget;
192223
193181
  if (!materialized && !contentionExhausted && m1Recomputed && recomputeM1ThisPass && (memoryUpdateCount > 40 || m1OverAbsoluteCap || m1HasContent && m0Tokens >= M0_DRIFT_RATIO_FLOOR_TOKENS && m1Tokens > m0Tokens * M1_DRIFT_RATIO)) {
192224
193182
  decision = { value: true, reason: "drift" };
@@ -196644,7 +197602,6 @@ function stripPiDroppedPlaceholderMessages(args) {
196644
197602
  const { db, sessionId, messages, isCacheBusting, stableIdByRef } = args;
196645
197603
  const persistedIds = getStrippedPlaceholderIds(db, sessionId);
196646
197604
  const idsToStrip = new Set(persistedIds);
196647
- let discovered = 0;
196648
197605
  const idOf = (msg, index) => {
196649
197606
  const m = msg && typeof msg === "object" ? msg : undefined;
196650
197607
  const carried = m ? stableIdByRef?.get(m) : undefined;
@@ -196663,15 +197620,36 @@ function stripPiDroppedPlaceholderMessages(args) {
196663
197620
  if (!id)
196664
197621
  continue;
196665
197622
  presentIds?.add(id);
196666
- if (!messageIsPlaceholderOnly(messages[i]))
196667
- continue;
196668
- if (!idsToStrip.has(id)) {
196669
- idsToStrip.add(id);
197623
+ if (messageIsPlaceholderOnly(messages[i]) && !persistedIds.has(id)) {
196670
197624
  discoveredIds.push(id);
196671
- discovered++;
196672
197625
  }
196673
197626
  }
196674
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
+ }
196675
197653
  let removed = 0;
196676
197654
  for (let i = messages.length - 1;i >= 0; i--) {
196677
197655
  const id = idOf(messages[i], i);
@@ -196680,29 +197658,76 @@ function stripPiDroppedPlaceholderMessages(args) {
196680
197658
  messages.splice(i, 1);
196681
197659
  removed++;
196682
197660
  }
196683
- let pruned = 0;
196684
- if (presentIds) {
196685
- const removedIds = [];
196686
- for (const id of persistedIds) {
196687
- if (!presentIds.has(id))
196688
- removedIds.push(id);
196689
- }
196690
- pruned = removedIds.length;
196691
- if (discoveredIds.length > 0 || removedIds.length > 0) {
196692
- applyStrippedPlaceholderDelta(db, sessionId, {
196693
- add: discoveredIds,
196694
- remove: removedIds
196695
- });
196696
- }
196697
- } else if (discoveredIds.length > 0) {
196698
- applyStrippedPlaceholderDelta(db, sessionId, { add: discoveredIds });
196699
- }
196700
197661
  if (removed > 0 || discovered > 0 || pruned > 0) {
196701
197662
  sessionLog(sessionId, `placeholder strip: removed=${removed} discovered=${discovered} pruned=${pruned}`);
196702
197663
  }
196703
197664
  return { removed, discovered };
196704
197665
  }
196705
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
+
196706
197731
  // src/system-prompt.ts
196707
197732
  import { createHash as createHash12 } from "node:crypto";
196708
197733
 
@@ -196723,6 +197748,7 @@ Context is managed for you entirely automatically — there's nothing to prune a
196723
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).`;
196724
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.`;
196725
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.
196726
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:
196727
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")\`
196728
197754
  - Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="PROJECT_RULES", content="Always use scripts/release.sh for releases")\`
@@ -196736,7 +197762,7 @@ Use \`ctx_reduce\` to mark spent tagged content as discardable and reclaim space
196736
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.
196737
197763
  ${CTX_NOTE_GUIDANCE}
196738
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.
196739
- 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).
196740
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:
196741
197767
  - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
196742
197768
  - Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
@@ -196751,7 +197777,7 @@ NEVER drop assistant text messages unless they are exceptionally large. Your con
196751
197777
  Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
196752
197778
  var BASE_INTRO_NO_REDUCE = (memoryEnabled) => `${CTX_NOTE_GUIDANCE}
196753
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.
196754
- 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).
196755
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:
196756
197782
  - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
196757
197783
  - Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
@@ -196951,74 +197977,222 @@ function withTimeout(p, ms) {
196951
197977
  }
196952
197978
 
196953
197979
  // src/tokenize-pi-messages.ts
196954
- function tokenizePiMessages(messages) {
197980
+ function tokenizePiMessages(messages, options) {
196955
197981
  let conversation = 0;
196956
197982
  let toolCall = 0;
197983
+ let cacheValidationMs = 0;
197984
+ let bpeMs = 0;
197985
+ const liveIds = options ? new Set : undefined;
196957
197986
  for (const raw of messages) {
196958
197987
  if (!raw || typeof raw !== "object")
196959
197988
  continue;
196960
- const msg = raw;
196961
- const content = msg.content;
196962
- if (msg.role === "user" || msg.role === "assistant") {
196963
- if (typeof content === "string") {
196964
- 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;
196965
198000
  continue;
196966
198001
  }
196967
- 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
+ }
196968
198048
  continue;
196969
- for (const part of content) {
196970
- if (!part || typeof part !== "object")
198049
+ }
198050
+ if (msg.role === "toolResult") {
198051
+ if (typeof content === "string") {
198052
+ toolCall += estimateTokens(content);
196971
198053
  continue;
196972
- const p = part;
196973
- switch (p.type) {
196974
- case "text":
196975
- if (typeof p.text === "string")
196976
- conversation += estimateTokens(p.text);
196977
- if (typeof p.textSignature === "string")
196978
- conversation += estimateTokens(p.textSignature);
196979
- break;
196980
- case "thinking":
196981
- if (typeof p.thinking === "string")
196982
- conversation += estimateTokens(p.thinking);
196983
- if (typeof p.thinkingSignature === "string")
196984
- conversation += estimateTokens(p.thinkingSignature);
196985
- break;
196986
- case "image":
196987
- conversation += 1200;
196988
- break;
196989
- case "toolCall":
196990
- if (typeof p.name === "string")
196991
- toolCall += estimateTokens(p.name);
196992
- if (p.arguments !== undefined) {
196993
- const s = typeof p.arguments === "string" ? p.arguments : safeJsonStringify2(p.arguments);
196994
- if (s)
196995
- toolCall += estimateTokens(s);
196996
- }
196997
- break;
196998
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");
196999
198118
  }
197000
198119
  continue;
197001
198120
  }
197002
- if (msg.role === "toolResult") {
197003
- if (typeof content === "string") {
197004
- toolCall += estimateTokens(content);
197005
- 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;
197006
198135
  }
197007
- 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")
197008
198182
  continue;
197009
- for (const part of content) {
197010
- if (!part || typeof part !== "object")
197011
- continue;
197012
- const p = part;
197013
- if (p.type === "text" && typeof p.text === "string") {
197014
- toolCall += estimateTokens(p.text);
197015
- } else if (p.type === "image") {
197016
- toolCall += 1200;
197017
- }
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;
197018
198188
  }
198189
+ if (!isPlainJsonData(descriptor.value, seen))
198190
+ return false;
197019
198191
  }
198192
+ return true;
198193
+ } finally {
198194
+ seen.delete(value);
197020
198195
  }
197021
- return { conversation, toolCall };
197022
198196
  }
197023
198197
  function safeJsonStringify2(value) {
197024
198198
  try {
@@ -197522,6 +198696,9 @@ function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUs
197522
198696
  } : { percentage: trailingPercentage, inputTokens: trailingInputTokens };
197523
198697
  }
197524
198698
  var injectM0M1PiForRun = injectM0M1Pi;
198699
+ var persistReasoningWatermarkForRun = updateSessionMeta;
198700
+ var persistStableIdSchemeForRun = updateSessionMeta;
198701
+ var afterFallbackAdoptionForTests;
197525
198702
  var DEFAULT_CLEAR_REASONING_AGE = 50;
197526
198703
  var PI_STABLE_ID_SCHEME = 1;
197527
198704
  var lastEmergencyNotificationAtMs = new Map;
@@ -197539,9 +198716,26 @@ var activeContextHandlerSessions = new Set;
197539
198716
  var lastHeuristicsTurnIdBySession = new Map;
197540
198717
  var firstContextPassSeenBySession = new Set;
197541
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;
197542
198734
  function logTransformTiming(sessionId, stage, start, extra) {
197543
- const elapsed = (performance.now() - start).toFixed(1);
198735
+ const elapsedMs = performance.now() - start;
198736
+ const elapsed = elapsedMs.toFixed(1);
197544
198737
  const suffix = extra ? ` ${extra}` : "";
198738
+ recordPiTransformTiming({ sessionId, stage, elapsedMs, extra });
197545
198739
  sessionLog(sessionId, `transform stage: stage=${stage} elapsed=${elapsed}ms${suffix}`);
197546
198740
  }
197547
198741
  function resolvePiContextModelKey(ctx) {
@@ -197558,6 +198752,35 @@ function resolvePiContextModelKey(ctx) {
197558
198752
  function readPiSessionMessageById(ctx, messageId) {
197559
198753
  return readPiSessionMessages(ctx).find((message) => message.id === messageId) ?? null;
197560
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
+ }
197561
198784
  function signalPiHistoryRefresh(sessionId) {
197562
198785
  historyRefreshSessions.add(sessionId);
197563
198786
  }
@@ -197709,29 +198932,7 @@ function collectMessageEntryIdsByRef(ctx, messages, sessionId, preloadedBranchEn
197709
198932
  return null;
197710
198933
  }
197711
198934
  }
197712
- const entryIdByMsgRef = new Map;
197713
- const entryIdsByFingerprint = new Map;
197714
- for (const entry of entries) {
197715
- if (!entry || typeof entry !== "object")
197716
- continue;
197717
- const e = entry;
197718
- if (e.type !== "message")
197719
- continue;
197720
- if (typeof e.id !== "string")
197721
- continue;
197722
- if (!e.message || typeof e.message !== "object")
197723
- continue;
197724
- const message = e.message;
197725
- entryIdByMsgRef.set(message, e.id);
197726
- const fingerprint = piMessageEntryFingerprint(e.message);
197727
- if (fingerprint) {
197728
- const bucket = entryIdsByFingerprint.get(fingerprint);
197729
- if (bucket)
197730
- bucket.push(e.id);
197731
- else
197732
- entryIdsByFingerprint.set(fingerprint, [e.id]);
197733
- }
197734
- }
198935
+ const { entryIdByMessageRef, entryIdsByFingerprint } = getPiBranchEntryLookup(entries);
197735
198936
  const result = new Array(messages.length);
197736
198937
  let resolved = 0;
197737
198938
  let fingerprintResolved = 0;
@@ -197742,7 +198943,7 @@ function collectMessageEntryIdsByRef(ctx, messages, sessionId, preloadedBranchEn
197742
198943
  result[i] = undefined;
197743
198944
  continue;
197744
198945
  }
197745
- const id = entryIdByMsgRef.get(msg);
198946
+ const id = entryIdByMessageRef.get(msg);
197746
198947
  if (typeof id === "string") {
197747
198948
  result[i] = id;
197748
198949
  resolved += 1;
@@ -197761,38 +198962,183 @@ function collectMessageEntryIdsByRef(ctx, messages, sessionId, preloadedBranchEn
197761
198962
  }
197762
198963
  }
197763
198964
  if (resolved < messages.length) {
197764
- 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`);
197765
198966
  }
197766
198967
  return result;
197767
198968
  }
197768
- function buildEntryIdByRefMap(branchEntries) {
197769
- const map2 = new Map;
197770
- if (!branchEntries)
197771
- return map2;
197772
- for (const entry of branchEntries) {
197773
- if (!entry || typeof entry !== "object")
197774
- continue;
197775
- const e = entry;
197776
- if (e.type !== "message")
197777
- continue;
197778
- if (typeof e.id !== "string")
197779
- continue;
197780
- 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")
197781
199000
  continue;
197782
- map2.set(e.message, e.id);
199001
+ compactionIndex = index;
199002
+ firstKeptEntryId = typeof row.firstKeptEntryId === "string" ? row.firstKeptEntryId : undefined;
199003
+ break;
197783
199004
  }
197784
- 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;
197785
199043
  }
197786
199044
  function readPiBranchEntriesForContext(ctx, sessionId) {
197787
199045
  const sm = ctx.sessionManager;
197788
- if (typeof sm?.getBranch !== "function")
197789
- return null;
197790
- 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;
197791
199065
  const entries = sm.getBranch.call(sm);
197792
- 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);
197793
199134
  } catch (error51) {
197794
- sessionLog(sessionId, `Pi branch pre-read failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
197795
- 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
+ }
197796
199142
  }
197797
199143
  }
197798
199144
  function piMessageEntryFingerprint(message) {
@@ -197826,13 +199172,15 @@ function firstPiTextContent(content) {
197826
199172
  }
197827
199173
  return null;
197828
199174
  }
197829
- function buildEntryFingerprintMap(messages, resolveStableId2) {
199175
+ function buildEntryFingerprintMap(messages, resolveStableId2, reusableMessageIds, includeReusable = true) {
197830
199176
  const map2 = new Map;
197831
199177
  for (let i = 0;i < messages.length; i++) {
197832
199178
  const msg = messages[i];
197833
199179
  const id = resolveStableId2(msg, i);
197834
199180
  if (!id)
197835
199181
  continue;
199182
+ if (!includeReusable && reusableMessageIds?.has(id))
199183
+ continue;
197836
199184
  const fp = piMessageEntryFingerprint(msg);
197837
199185
  if (fp)
197838
199186
  map2.set(id, fp);
@@ -197916,8 +199264,10 @@ function hasAdoptablePiFallbackMessageTags(db, sessionId, fingerprintById) {
197916
199264
  return false;
197917
199265
  }
197918
199266
  function adoptPiFallbackTags(db, sessionId, tagger, fingerprintById, options = {}) {
197919
- const shouldRunMessageMigration = options.stampStableIdScheme !== undefined || hasAdoptablePiFallbackMessageTags(db, sessionId, fingerprintById);
197920
- 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);
197921
199271
  if (!shouldRunMessageMigration && !shouldRunToolOwnerMigration)
197922
199272
  return;
197923
199273
  runImmediateTransaction(db, () => {
@@ -197970,14 +199320,13 @@ function adoptPiFallbackTags(db, sessionId, tagger, fingerprintById, options = {
197970
199320
  tagger.unbindToolTag(sessionId, realOwnerId, row.callId);
197971
199321
  }
197972
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
+ }
197973
199327
  }
197974
199328
  }
197975
199329
  }
197976
- if (options.stampStableIdScheme !== undefined) {
197977
- updateSessionMeta(db, sessionId, {
197978
- piStableIdScheme: options.stampStableIdScheme
197979
- });
197980
- }
197981
199330
  });
197982
199331
  }
197983
199332
  function registerPiContextHandler(pi, baseOptions) {
@@ -198010,12 +199359,14 @@ function registerPiContextHandler(pi, baseOptions) {
198010
199359
  }
198011
199360
  sessionIdForError = sessionId;
198012
199361
  const projectDirectory = ctx.cwd;
199362
+ const fullWireMessageCount = event.messages.length;
198013
199363
  const options = baseOptions.resolveForProject?.(projectDirectory) ?? baseOptions;
198014
199364
  const schedulerConfig = options.scheduler ?? DEFAULT_SCHEDULER_CONFIG;
198015
199365
  const scheduler = schedulerFor(options);
198016
199366
  const projectIdentity = resolveProjectIdentity(projectDirectory);
198017
199367
  updateSessionProjectTracking(sessionId, projectIdentity, options.db);
198018
199368
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
199369
+ const tEntryBranch = performance.now();
198019
199370
  const branchEntries = readPiBranchEntriesForContext(ctx, sessionId);
198020
199371
  schedulePiTransformDecisionResolve({
198021
199372
  db: options.db,
@@ -198030,15 +199381,39 @@ function registerPiContextHandler(pi, baseOptions) {
198030
199381
  const unregisterRaw = setRawMessageProvider(sessionId, rawMessageProvider);
198031
199382
  rawMessageProviderUnregistersBySession.set(sessionId, unregisterRaw);
198032
199383
  scheduleReconciliation(options.db, sessionId, readRawSessionMessages);
198033
- 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
+ }
198034
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}`);
198035
199405
  const tLastUser = performance.now();
198036
199406
  const latestUser = findLatestUserMessageIdPi(event.messages, buildPiMessageIdByIndex(event.messages, strictEntryIds));
198037
199407
  logTransformTiming(sessionId, "findLastUserMessageId", tLastUser);
199408
+ const tMessageIndexScheduling = performance.now();
198038
199409
  if (latestUser) {
198039
- 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)));
198040
199412
  }
198041
- 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);
198042
199417
  const isFirstContextPassForSession = !firstContextPassSeenBySession.has(sessionId);
198043
199418
  firstContextPassSeenBySession.add(sessionId);
198044
199419
  const piUsage = ctx.getContextUsage?.();
@@ -198142,7 +199517,7 @@ function registerPiContextHandler(pi, baseOptions) {
198142
199517
  schedulerDecision = "defer";
198143
199518
  }
198144
199519
  logTransformTiming(sessionId, "schedulerAndUsage", tScheduler);
198145
- const piMessageCount = Array.isArray(event.messages) ? event.messages.length : 0;
199520
+ const piMessageCount = fullWireMessageCount;
198146
199521
  const looksLikeImportedSession = schedulerDecision === "defer" && usagePercentage === 0 && sessionMeta.lastResponseTime === 0 && piMessageCount >= 50;
198147
199522
  if (looksLikeImportedSession) {
198148
199523
  schedulerDecision = "execute";
@@ -198160,8 +199535,9 @@ function registerPiContextHandler(pi, baseOptions) {
198160
199535
  signalPiPendingMaterialization(sessionId);
198161
199536
  sessionLog(sessionId, `stable-id scheme cutover: stored=${storedStableIdScheme} < current=${PI_STABLE_ID_SCHEME} — forcing execute+materialize this pass`);
198162
199537
  }
199538
+ const tBoundaryChecks = performance.now();
198163
199539
  const schedulerDecisionEarly = schedulerDecision;
198164
- const midTurn = isMidTurnPi(event, sessionId);
199540
+ const midTurn = isMidTurnPi(event, sessionId, branchEntries);
198165
199541
  const bypassReason = detectMidTurnBypassReason({
198166
199542
  contextUsage: { percentage: usagePercentage },
198167
199543
  sessionMeta,
@@ -198209,6 +199585,7 @@ function registerPiContextHandler(pi, baseOptions) {
198209
199585
  }
198210
199586
  }
198211
199587
  const isCacheBusting = historyRefreshSessions.has(sessionId);
199588
+ logTransformTiming(sessionId, "boundaryTriggerChecks", tBoundaryChecks);
198212
199589
  sessionLog(sessionId, `transform: usage=${usagePercentage.toFixed(1)}% (${usageInputTokens} tokens, limit=${usageContextLimit ?? "?"}) decision=${schedulerDecision}${forceMaterialization ? " force=true" : ""}${isEmergency ? " EMERGENCY=true" : ""}${isCacheBusting ? " busting=true" : ""}`);
198213
199590
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tEmergencyRecovery);
198214
199591
  const entryIds = strictEntryIds ?? undefined;
@@ -198217,12 +199594,15 @@ function registerPiContextHandler(pi, baseOptions) {
198217
199594
  contextLimit: usageContextLimit,
198218
199595
  sessionId
198219
199596
  }) / 100)) : undefined;
199597
+ logTransformTiming(sessionId, "prePipelineTotal", transformStartTime);
199598
+ const tRunPipeline = performance.now();
198220
199599
  const result = await runPipeline({
198221
199600
  db: options.db,
198222
199601
  tagger,
198223
199602
  sessionId,
198224
199603
  projectIdentity,
198225
199604
  projectDirectory,
199605
+ sessionMeta,
198226
199606
  messages: event.messages,
198227
199607
  smartDrops: options.smartDrops === true,
198228
199608
  protectedTags: options.protectedTags ?? 20,
@@ -198243,6 +199623,7 @@ function registerPiContextHandler(pi, baseOptions) {
198243
199623
  } : undefined,
198244
199624
  entryIds,
198245
199625
  entryIdByRef,
199626
+ reusableMessageIds,
198246
199627
  stableIdSchemeCutover,
198247
199628
  schedulerDecision,
198248
199629
  forceMaterialization: forceMaterialization || isEmergency,
@@ -198260,8 +199641,14 @@ function registerPiContextHandler(pi, baseOptions) {
198260
199641
  readBranchEntries: resolvePiReadBranchEntries(ctx),
198261
199642
  isSubagent: sessionMeta.isSubagent
198262
199643
  });
198263
- const piDecisionSnapshotNewestAssistant = branchEntries === null ? undefined : findNewestPiAssistantEntryId(branchEntries);
198264
- 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) {
198265
199652
  recordPendingPiTransformDecision(sessionId, {
198266
199653
  tsMs: Date.now(),
198267
199654
  decision: schedulerDecision,
@@ -198274,6 +199661,8 @@ function registerPiContextHandler(pi, baseOptions) {
198274
199661
  bustedThisPass: true
198275
199662
  }, piDecisionSnapshotNewestAssistant);
198276
199663
  }
199664
+ logTransformTiming(sessionId, "transformDecisionAndReuseState", tTransformDecision);
199665
+ const tHistorianScheduling = performance.now();
198277
199666
  if (options.historian) {
198278
199667
  maybeFireHistorian({
198279
199668
  pi,
@@ -198283,11 +199672,14 @@ function registerPiContextHandler(pi, baseOptions) {
198283
199672
  historian: options.historian,
198284
199673
  isFirstContextPassForSession,
198285
199674
  activeTags: result.activeTags,
198286
- rawMessageProvider
199675
+ rawMessageProvider,
199676
+ taggerFloor
198287
199677
  });
198288
199678
  }
199679
+ logTransformTiming(sessionId, "historianScheduling", tHistorianScheduling);
198289
199680
  const tPostTransform = performance.now();
198290
199681
  let outputMessages = result.messages;
199682
+ const tNoteNudges = performance.now();
198291
199683
  try {
198292
199684
  outputMessages = applyNoteNudges({
198293
199685
  sessionId,
@@ -198302,15 +199694,17 @@ function registerPiContextHandler(pi, baseOptions) {
198302
199694
  } catch (err) {
198303
199695
  sessionLog(sessionId, `note nudges failed: ${err instanceof Error ? err.message : String(err)}`);
198304
199696
  }
199697
+ logTransformTiming(sessionId, "noteNudges", tNoteNudges);
199698
+ const tAutoSearch = performance.now();
198305
199699
  if (options.autoSearch?.enabled) {
198306
199700
  try {
198307
- await ensureProjectRegisteredFromPiDirectory(projectDirectory, options.db);
198308
199701
  outputMessages = await runAutoSearchHintForPi({
198309
199702
  sessionId,
198310
199703
  db: options.db,
198311
199704
  messages: outputMessages,
198312
199705
  entryIds: strictEntryIds,
198313
199706
  entryIdByRef: result.postCommitEntryIdByRef,
199707
+ ensureProjectRegistered: () => ensureProjectRegisteredFromPiDirectory(projectDirectory, options.db),
198314
199708
  options: {
198315
199709
  enabled: true,
198316
199710
  scoreThreshold: options.autoSearch.scoreThreshold,
@@ -198323,6 +199717,8 @@ function registerPiContextHandler(pi, baseOptions) {
198323
199717
  sessionLog(sessionId, `auto-search failed: ${err instanceof Error ? err.message : String(err)}`);
198324
199718
  }
198325
199719
  }
199720
+ logTransformTiming(sessionId, "autoSearch", tAutoSearch);
199721
+ const tTodoCapture = performance.now();
198326
199722
  try {
198327
199723
  const sessionMetaForTodo = getOrCreateSessionMeta(options.db, sessionId);
198328
199724
  if (!sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
@@ -198339,6 +199735,8 @@ function registerPiContextHandler(pi, baseOptions) {
198339
199735
  } catch (err) {
198340
199736
  sessionLog(sessionId, `synthetic todowrite injection failed: ${err instanceof Error ? err.message : String(err)}`);
198341
199737
  }
199738
+ logTransformTiming(sessionId, "todoCapture", tTodoCapture);
199739
+ const tChannelAccounting = performance.now();
198342
199740
  try {
198343
199741
  const sessionMetaForCh1 = getOrCreateSessionMeta(options.db, sessionId);
198344
199742
  if (!sessionMetaForCh1.isSubagent) {
@@ -198398,16 +199796,39 @@ function registerPiContextHandler(pi, baseOptions) {
198398
199796
  } catch (err) {
198399
199797
  sessionLog(sessionId, `channel1 baseline / channel2 trigger failed: ${err instanceof Error ? err.message : String(err)}`);
198400
199798
  }
199799
+ logTransformTiming(sessionId, "channelNudgeAccounting", tChannelAccounting);
199800
+ const tWorkMetrics = performance.now();
198401
199801
  try {
198402
199802
  const metrics = computePiWorkMetrics(outputMessages);
198403
199803
  setSessionWorkMetrics(options.db, sessionId, metrics.newWorkTokens, metrics.totalInputTokens);
198404
199804
  } catch (err) {
198405
199805
  sessionLog(sessionId, `work-metrics update failed: ${err instanceof Error ? err.message : String(err)}`);
198406
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);
198407
199820
  logTransformTiming(sessionId, "postTransformPhase", tPostTransform);
198408
- sessionLog(sessionId, `transform completed in ${(performance.now() - transformStartTime).toFixed(1)}ms (${outputMessages.length} messages, ${result.targetCount} targets, watermark: ${result.reasoningWatermark})`);
198409
199821
  clearLastTransformErrorIfSet(options.db, sessionId);
198410
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})`);
198411
199832
  return { messages: outputMessages };
198412
199833
  } catch (err) {
198413
199834
  const message = err instanceof Error ? err.message : String(err);
@@ -198708,7 +200129,7 @@ function maybeFireHistorian(args) {
198708
200129
  }
198709
200130
  return snapshot;
198710
200131
  };
198711
- const boundarySnapshot = resolveRunnablePiBoundarySnapshot();
200132
+ let boundarySnapshot;
198712
200133
  let triggered = false;
198713
200134
  try {
198714
200135
  if (isFirstContextPassForSession) {
@@ -198718,7 +200139,10 @@ function maybeFireHistorian(args) {
198718
200139
  sessionLog(sessionId, "historian: cleared stale compartmentInProgress flag on first context pass after restart");
198719
200140
  }
198720
200141
  const failureState = getHistorianFailureState(db, sessionId);
198721
- 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);
198722
200146
  if (shouldRecoverOnFirstPass) {
198723
200147
  triggered = true;
198724
200148
  sessionLog(sessionId, `historian recovery triggered on session load after ${failureState.failureCount} failure(s)`);
@@ -198741,12 +200165,18 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
198741
200165
  return;
198742
200166
  }
198743
200167
  }
198744
- 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);
198745
200172
  if (!trigger.shouldFire) {
198746
200173
  sessionLog(sessionId, `historian trigger eval: shouldFire=false (no trigger condition met)`);
198747
200174
  try {
198748
200175
  const overflowState = getOverflowState(db, sessionId);
198749
- 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)) {
198750
200180
  clearEmergencyRecovery(db, sessionId);
198751
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)`);
198752
200182
  }
@@ -198766,7 +200196,7 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
198766
200196
  provider: provider2,
198767
200197
  unregister,
198768
200198
  boundarySnapshot: selectPiHistorianRunBoundarySnapshot({
198769
- resolvedBoundarySnapshot: boundarySnapshot,
200199
+ resolvedBoundarySnapshot: boundarySnapshot ?? trigger.boundarySnapshot ?? resolveRunnablePiBoundarySnapshot(),
198770
200200
  triggerBoundarySnapshot: trigger.boundarySnapshot
198771
200201
  }),
198772
200202
  refreshBoundarySnapshot: resolveRunnablePiBoundarySnapshot,
@@ -198789,6 +200219,44 @@ function pendingPiMarkerCoveredByRenderedBoundary(pending, injection) {
198789
200219
  return true;
198790
200220
  return boundary.ordinal !== null && pending.ordinal <= boundary.ordinal;
198791
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
+ }
198792
200260
  async function runPipeline(args) {
198793
200261
  let executedWorkThisPass = false;
198794
200262
  let historyWasConsumedThisPass = false;
@@ -198817,7 +200285,9 @@ async function runPipeline(args) {
198817
200285
  }
198818
200286
  logTransformTiming(args.sessionId, "injectTemporalMarkers", tTemporal);
198819
200287
  }
200288
+ const tTranscriptBuild = performance.now();
198820
200289
  const transcript = createPiTranscript(args.messages, args.sessionId, args.entryIds);
200290
+ logTransformTiming(args.sessionId, "transcriptBuild", tTranscriptBuild);
198821
200291
  const workingMessages = transcript.getWorkingMessages();
198822
200292
  const stableIdResolver = (msg, index) => resolvePiStableId(msg, index, args.entryIds, args.entryIdByRef ?? undefined);
198823
200293
  const currentTurnId = (() => {
@@ -198825,12 +200295,11 @@ async function runPipeline(args) {
198825
200295
  return findLatestUserMessageIdPi(args.messages, ids)?.messageId ?? null;
198826
200296
  })();
198827
200297
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
198828
- const sessionMetaForAvailability = getOrCreateSessionMeta(args.db, args.sessionId);
198829
- const ctxReduceCallable = !sessionMetaForAvailability.isSubagent;
200298
+ const ctxReduceCallable = !args.sessionMeta.isSubagent;
198830
200299
  const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
198831
200300
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
198832
200301
  const piHardSignals = args.injection ? (() => {
198833
- const hardMeta = getOrCreateSessionMeta(args.db, args.sessionId);
200302
+ const hardMeta = args.sessionMeta;
198834
200303
  let piTtlMs = 5 * 60 * 1000;
198835
200304
  try {
198836
200305
  piTtlMs = parseCacheTtl(hardMeta.cacheTtl);
@@ -198855,28 +200324,44 @@ async function runPipeline(args) {
198855
200324
  const bypassHistorianGate = args.forceMaterialization === true || m0HardFoldThisPass;
198856
200325
  const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
198857
200326
  const shouldRunHeuristics = args.heuristics !== undefined && (!historianRunning || bypassHistorianGate) && (args.forceMaterialization === true || hasPendingMaterializeSignal || deferredMaterializeEligible || m0HardFoldThisPass || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
198858
- 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);
198859
200330
  adoptPiFallbackTags(args.db, args.sessionId, args.tagger, entryFingerprintByMessageId, {
198860
200331
  messages: args.messages,
198861
200332
  resolveStableId: stableIdResolver,
198862
- stampStableIdScheme: args.stableIdSchemeCutover ? PI_STABLE_ID_SCHEME : undefined
200333
+ hasFallbackMessageTags
198863
200334
  });
198864
- if (args.stableIdSchemeCutover === true) {
198865
- invalidateTrueRawTokenCache({
198866
- sessionId: args.sessionId,
198867
- reason: "pi.stable-id-scheme.changed"
198868
- });
198869
- sessionLog(args.sessionId, `stable-id scheme cutover complete — stamped scheme=${PI_STABLE_ID_SCHEME}`);
198870
- }
200335
+ logTransformTiming(args.sessionId, "fallbackIdentityAndAdoption", tFallbackIdentity);
200336
+ afterFallbackAdoptionForTests?.(args.stableIdSchemeCutover === true);
198871
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
+ }
198872
200348
  const { targets } = tagTranscript(args.sessionId, transcript, args.tagger, args.db, {
198873
200349
  skipPrefixInjection: !ctxReduceCallable,
198874
- 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
198875
200361
  });
198876
200362
  logTransformTiming(args.sessionId, "tagMessages", tTag);
198877
200363
  try {
198878
- const sessionMeta = getOrCreateSessionMeta(args.db, args.sessionId);
198879
- if (!sessionMeta.isSubagent) {
200364
+ if (!args.sessionMeta.isSubagent) {
198880
200365
  const hasRecentCommit = detectRecentCommit(args.messages);
198881
200366
  const hadPriorCommitState = commitSeenLastPass.has(args.sessionId);
198882
200367
  const sawCommitLastPass = commitSeenLastPass.get(args.sessionId) ?? false;
@@ -198891,6 +200376,7 @@ async function runPipeline(args) {
198891
200376
  const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
198892
200377
  const deferredHistoryRefreshWasPending = deferredHistoryWasPendingAtPassStart;
198893
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);
198894
200380
  const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal || m0HardFoldThisPass;
198895
200381
  const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
198896
200382
  const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
@@ -198900,7 +200386,7 @@ async function runPipeline(args) {
198900
200386
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
198901
200387
  try {
198902
200388
  const tApplyPending = performance.now();
198903
- pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags, undefined, pendingOps);
200389
+ pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags, pendingOperationTags, pendingOps);
198904
200390
  if (pendingOpsDidMutate) {
198905
200391
  droppedCount += pendingOps.length;
198906
200392
  }
@@ -198956,6 +200442,7 @@ async function runPipeline(args) {
198956
200442
  }
198957
200443
  }
198958
200444
  if (args.heuristics?.caveman?.enabled && !args.isSubagent) {
200445
+ const tCavemanReplay = performance.now();
198959
200446
  try {
198960
200447
  const tags = getTagsByNumbers(args.db, args.sessionId, targetTagNumbers);
198961
200448
  const replayed = replayCavemanCompression(args.sessionId, args.db, targets, tags);
@@ -198965,6 +200452,7 @@ async function runPipeline(args) {
198965
200452
  } catch (err) {
198966
200453
  sessionLog(args.sessionId, `caveman replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
198967
200454
  }
200455
+ logTransformTiming(args.sessionId, "cavemanReplay", tCavemanReplay);
198968
200456
  }
198969
200457
  let heuristicsExecuted = false;
198970
200458
  let heuristicsResult = null;
@@ -199015,10 +200503,10 @@ async function runPipeline(args) {
199015
200503
  }
199016
200504
  }
199017
200505
  if (args.reasoningClearing && shouldRunHeuristics) {
200506
+ const rollbackReasoning = captureReasoningMutationRollback(workingMessages);
199018
200507
  try {
199019
200508
  const tClearReasoning = performance.now();
199020
- const meta3 = getOrCreateSessionMeta(args.db, args.sessionId);
199021
- const prevWatermark = meta3.clearedReasoningThroughTag ?? 0;
200509
+ const prevWatermark = args.sessionMeta.clearedReasoningThroughTag ?? 0;
199022
200510
  const clearOutcome = clearOldReasoningPi({
199023
200511
  messages: workingMessages,
199024
200512
  messageIdToMaxTag,
@@ -199033,9 +200521,10 @@ async function runPipeline(args) {
199033
200521
  });
199034
200522
  const combinedWatermark = Math.max(clearOutcome.newWatermark, stripOutcome.newWatermark);
199035
200523
  if (combinedWatermark > prevWatermark) {
199036
- updateSessionMeta(args.db, args.sessionId, {
200524
+ persistReasoningWatermarkForRun(args.db, args.sessionId, {
199037
200525
  clearedReasoningThroughTag: combinedWatermark
199038
200526
  });
200527
+ args.sessionMeta.clearedReasoningThroughTag = combinedWatermark;
199039
200528
  sessionLog(args.sessionId, `reasoning cleanup: cleared=${clearOutcome.cleared} inlineStripped=${stripOutcome.stripped} watermark=${prevWatermark}→${combinedWatermark}`);
199040
200529
  }
199041
200530
  logTransformTiming(args.sessionId, "clearOldReasoning", tClearReasoning);
@@ -199048,7 +200537,8 @@ async function runPipeline(args) {
199048
200537
  executedWorkThisPass = true;
199049
200538
  }
199050
200539
  } catch (err) {
199051
- 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)}`);
199052
200542
  }
199053
200543
  }
199054
200544
  const toolReclaimExecutePass = args.schedulerDecision === "execute";
@@ -199057,7 +200547,7 @@ async function runPipeline(args) {
199057
200547
  let autoReclaimTargetCount = 0;
199058
200548
  let autoReclaimDidMutate = false;
199059
200549
  if (toolReclaimExecutePass && alreadyMutatingThisPass && !emergencyDropEligible) {
199060
- const reclaimMeta = getOrCreateSessionMeta(args.db, args.sessionId);
200550
+ const reclaimMeta = args.sessionMeta;
199061
200551
  const syntheticPendingOps = buildSyntheticToolReclaimOps({
199062
200552
  db: args.db,
199063
200553
  sessionId: args.sessionId,
@@ -199103,13 +200593,38 @@ async function runPipeline(args) {
199103
200593
  }
199104
200594
  }
199105
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();
199106
200619
  transcript.commit();
200620
+ logTransformTiming(args.sessionId, "transcriptCommit", tTranscriptCommit);
199107
200621
  if (toolReclaimExecutePass) {
199108
200622
  advanceToolReclaimWatermarkToCurrentMax(args.db, args.sessionId);
199109
200623
  }
199110
200624
  if (autoReclaimTargetCount > 0) {
199111
200625
  sessionLog(args.sessionId, `tool reclaim auto-drop: targets=${autoReclaimTargetCount} mutated=${autoReclaimDidMutate}`);
199112
200626
  }
200627
+ const tPostCommitStableIdMaps = performance.now();
199113
200628
  const postCommitStableIdByRef = new Map;
199114
200629
  const postCommitEntryIdByRef = new Map;
199115
200630
  for (let i = 0;i < args.messages.length; i++) {
@@ -199124,6 +200639,7 @@ async function runPipeline(args) {
199124
200639
  postCommitEntryIdByRef.set(m, realId);
199125
200640
  }
199126
200641
  }
200642
+ logTransformTiming(args.sessionId, "postCommitStableIdMaps", tPostCommitStableIdMaps);
199127
200643
  let injectionResult = null;
199128
200644
  if (args.injection) {
199129
200645
  try {
@@ -199151,6 +200667,7 @@ async function runPipeline(args) {
199151
200667
  sessionLog(args.sessionId, `compartment injection failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
199152
200668
  }
199153
200669
  }
200670
+ const tDroppedPlaceholders = performance.now();
199154
200671
  stripPiDroppedPlaceholderMessages({
199155
200672
  db: args.db,
199156
200673
  sessionId: args.sessionId,
@@ -199159,6 +200676,7 @@ async function runPipeline(args) {
199159
200676
  stableIdByRef: postCommitStableIdByRef,
199160
200677
  forceDiscovery: args.stableIdSchemeCutover === true
199161
200678
  });
200679
+ logTransformTiming(args.sessionId, "stripDroppedPlaceholders", tDroppedPlaceholders);
199162
200680
  const deferredHistoryDrainEligible = historyWasConsumedThisPass && materializationSatisfiedThisPass && (deferredHistoryWasPendingAtPassStart || hasPendingMaterializeSignal) && !suppressDeferredHistoryDrain && !casLost;
199163
200681
  let preserveDeferredMaterializationForMarkerDrain = false;
199164
200682
  if (deferredHistoryDrainEligible) {
@@ -199215,11 +200733,28 @@ async function runPipeline(args) {
199215
200733
  logTransformTiming(args.sessionId, "batchFinalize:heuristics", performance.now());
199216
200734
  const outputMessages = transcript.getOutputMessages();
199217
200735
  try {
199218
- 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
+ });
199219
200753
  updateSessionMeta(args.db, args.sessionId, {
199220
200754
  conversationTokens: counts.conversation,
199221
200755
  toolCallTokens: counts.toolCall
199222
200756
  });
200757
+ logTransformTiming(args.sessionId, "tokenAccounting", tTokenAccounting);
199223
200758
  } catch (err) {
199224
200759
  sessionLog(args.sessionId, `token accounting failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
199225
200760
  }
@@ -199241,15 +200776,18 @@ async function runPipeline(args) {
199241
200776
  emergency,
199242
200777
  bustedThisPass,
199243
200778
  targetCount: targets.size,
199244
- reasoningWatermark: getOrCreateSessionMeta(args.db, args.sessionId).clearedReasoningThroughTag ?? 0,
200779
+ reasoningWatermark: args.sessionMeta.clearedReasoningThroughTag ?? 0,
199245
200780
  activeTags,
199246
200781
  postCommitEntryIdByRef
199247
200782
  };
199248
200783
  }
199249
200784
  function applyNoteNudges(args) {
199250
200785
  const { sessionId, db, messages, projectIdentity, entryIds, entryIdByRef } = args;
200786
+ const tNoteIndexMaps = performance.now();
199251
200787
  const messageIdByIndex = buildPiMessageIdByIndex(messages, entryIds, false, entryIdByRef);
199252
200788
  const replayMessageIdByIndex = buildPiMessageIdByIndex(messages, entryIds, true, entryIdByRef);
200789
+ logTransformTiming(sessionId, "noteIndexMaps", tNoteIndexMaps);
200790
+ const tStickyReplay = performance.now();
199253
200791
  for (const anchor of getNoteNudgeAnchors(db, sessionId)) {
199254
200792
  appendReminderToUserMessageByIdPi(messages, replayMessageIdByIndex, anchor.messageId, anchor.text);
199255
200793
  }
@@ -199258,6 +200796,7 @@ function applyNoteNudges(args) {
199258
200796
  appendReminderToUserMessageByIdPi(messages, replayMessageIdByIndex, decision.messageId, decision.text);
199259
200797
  }
199260
200798
  }
200799
+ logTransformTiming(sessionId, "stickyReplayDecisions", tStickyReplay);
199261
200800
  const latestUser = findLatestUserMessageIdPi(messages, messageIdByIndex);
199262
200801
  const latestUserId = latestUser?.messageId ?? null;
199263
200802
  const noteReadStillVisible = hasVisibleNoteReadCallPi(messages);
@@ -199326,9 +200865,9 @@ function buildPiMessageIdByIndex(messages, entryIds, includeMessageIdFallback =
199326
200865
  }
199327
200866
  continue;
199328
200867
  }
199329
- const entryId = entryIds?.[index];
199330
- if (typeof entryId === "string") {
199331
- ids.set(index, entryId);
200868
+ const entryId2 = entryIds?.[index];
200869
+ if (typeof entryId2 === "string") {
200870
+ ids.set(index, entryId2);
199332
200871
  continue;
199333
200872
  }
199334
200873
  if (includeMessageIdFallback) {
@@ -199402,6 +200941,17 @@ function clearContextHandlerSession(sessionId) {
199402
200941
  firstContextPassSeenBySession.delete(sessionId);
199403
200942
  commitSeenLastPass.delete(sessionId);
199404
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);
199405
200955
  clearPiChannel1State(sessionId);
199406
200956
  lastHeuristicsTurnIdBySession.delete(sessionId);
199407
200957
  lastSeenProjectIdentityBySession.delete(sessionId);
@@ -201837,9 +203387,8 @@ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageC
201837
203387
  const compartments = getCompartments(db, sessionId);
201838
203388
  let historyBlockTokens = 0;
201839
203389
  for (const c of compartments) {
201840
- historyBlockTokens += estimateTokens(`<compartment start="${c.startMessage}" end="${c.endMessage}" title="${c.title}">
203390
+ historyBlockTokens += estimateTokens(`## ${c.startMessage}-${c.endMessage} · ${c.title}
201841
203391
  ${c.content}
201842
- </compartment>
201843
203392
  `);
201844
203393
  }
201845
203394
  const budgetTokens = historyBudgetPercentage && displayContextLimit > 0 ? Math.floor(displayContextLimit * (Math.min(executeThresholdPercentage, 80) / 100) * historyBudgetPercentage) : null;
@@ -201891,9 +203440,8 @@ function computeM0BlockTokens(db, sessionId, args) {
201891
203440
  try {
201892
203441
  const compRows = db.prepare("SELECT content, title, start_message, end_message FROM compartments WHERE session_id = ?").all(sessionId);
201893
203442
  for (const c of compRows) {
201894
- 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}
201895
203444
  ${c.content}
201896
- </compartment>
201897
203445
  `);
201898
203446
  }
201899
203447
  } catch {}
@@ -201923,7 +203471,7 @@ function formatThresholdPercent(value) {
201923
203471
  // package.json
201924
203472
  var package_default = {
201925
203473
  name: "@cortexkit/pi-magic-context",
201926
- version: "0.31.4",
203474
+ version: "0.32.0",
201927
203475
  type: "module",
201928
203476
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
201929
203477
  main: "dist/index.js",
@@ -202902,9 +204450,9 @@ function stripTagPrefixFromAssistantMessage(message) {
202902
204450
  // ../plugin/src/tools/ctx-expand/constants.ts
202903
204451
  var CTX_EXPAND_DESCRIPTION = `Recover the original conversation from your compacted history.
202904
204452
 
202905
- 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:
202906
204454
 
202907
- 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
202908
204456
 
202909
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.
202910
204458
 
@@ -208447,6 +209995,8 @@ var STATUS_COLOR = {
208447
209995
  cancelled: "error"
208448
209996
  };
208449
209997
  var snapshotsBySession = new Map;
209998
+ var MAX_TODOWRITE_RENDER_CACHE_ENTRIES = 50;
209999
+ var cachedTodowriteTodosByToolCallId = new Map;
208450
210000
  function isTodoStatus2(value) {
208451
210001
  return TODO_STATUSES.includes(value);
208452
210002
  }
@@ -208456,6 +210006,27 @@ function isTodoPriority2(value) {
208456
210006
  function cloneTodos(todos) {
208457
210007
  return todos.map((todo) => ({ ...todo }));
208458
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
+ }
208459
210030
  function parseTodos(input) {
208460
210031
  if (!Array.isArray(input))
208461
210032
  return null;
@@ -208592,25 +210163,27 @@ function capTodoRows(rows) {
208592
210163
  hiddenCount: rows.length - visibleCount
208593
210164
  };
208594
210165
  }
208595
- function renderTodowriteCall(args, theme) {
208596
- const todos = parseTodos(args.todos) ?? [];
210166
+ function renderTodowriteCall(args, theme, context) {
210167
+ const todos = getCachedTodowriteToolCallTodos(context?.toolCallId) ?? parseTodos(args.todos) ?? [];
208597
210168
  const active = activeTitleCount(todos);
208598
210169
  const activeColor = active > 0 ? "warning" : "success";
208599
210170
  const line = `${theme.fg("toolTitle", theme.bold("Todos"))} ${theme.fg("muted", "—")} ${theme.fg(activeColor, `${active} active`)}`;
208600
210171
  return lineComponent((width) => [truncateToWidth2(line, width, "…")]);
208601
210172
  }
208602
- function renderTodowriteResult(result, theme) {
210173
+ function renderTodowriteResult(result, theme, context) {
208603
210174
  const detailsTodos = result.details?.todos;
208604
210175
  let todos = parseTodos(detailsTodos);
208605
- if (todos === null && Array.isArray(result.content)) {
210176
+ if ((todos === null || todos.length === 0) && Array.isArray(result.content)) {
208606
210177
  const text = result.content.find((part) => part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string")?.text;
208607
210178
  if (text) {
208608
210179
  try {
208609
- todos = parseTodos(JSON.parse(text));
210180
+ const parsed = parseTodos(JSON.parse(text));
210181
+ if (parsed !== null)
210182
+ todos = parsed;
208610
210183
  } catch {}
208611
210184
  }
208612
210185
  }
208613
- const renderedTodos = todos ?? [];
210186
+ const renderedTodos = todos !== null && todos.length > 0 ? todos : getCachedTodowriteToolCallTodos(context?.toolCallId) ?? todos ?? [];
208614
210187
  return lineComponent((width) => {
208615
210188
  if (renderedTodos.length === 0)
208616
210189
  return [theme.fg("dim", "No todos")];
@@ -208811,6 +210384,7 @@ function registerTodoOverlay(pi, deps) {
208811
210384
  overlay.hideCompletedTasksFromPreviousTurn();
208812
210385
  });
208813
210386
  pi.on("session_shutdown", async (_event, ctx) => {
210387
+ clearTodowriteToolCallTodos();
208814
210388
  const sessionId = getSessionId(ctx);
208815
210389
  if (!sessionId)
208816
210390
  return;
@@ -208818,6 +210392,7 @@ function registerTodoOverlay(pi, deps) {
208818
210392
  overlay.disposeSession(sessionId);
208819
210393
  });
208820
210394
  pi.on("session_before_switch", async (_event, ctx) => {
210395
+ clearTodowriteToolCallTodos();
208821
210396
  const sessionId = getSessionId(ctx);
208822
210397
  if (!sessionId)
208823
210398
  return;
@@ -208834,11 +210409,13 @@ function registerTodoStateLifecycle(pi, deps) {
208834
210409
  seedTodoSnapshotFromStateJson(sessionId, deps.readLastTodoState(sessionId));
208835
210410
  });
208836
210411
  pi.on("session_shutdown", async (_event, ctx) => {
210412
+ clearTodowriteToolCallTodos();
208837
210413
  const sessionId = getSessionId(ctx);
208838
210414
  if (sessionId)
208839
210415
  clearTodoSnapshot(sessionId);
208840
210416
  });
208841
210417
  pi.on("session_before_switch", async (_event, ctx) => {
210418
+ clearTodowriteToolCallTodos();
208842
210419
  const sessionId = getSessionId(ctx);
208843
210420
  if (sessionId)
208844
210421
  clearTodoSnapshot(sessionId);
@@ -208889,11 +210466,11 @@ function createTodowriteTool() {
208889
210466
  }
208890
210467
  };
208891
210468
  },
208892
- renderCall(args, theme) {
208893
- return renderTodowriteCall(args, theme);
210469
+ renderCall(args, theme, context) {
210470
+ return renderTodowriteCall(args, theme, context);
208894
210471
  },
208895
- renderResult(result, _opts, theme) {
208896
- return renderTodowriteResult(result, theme);
210472
+ renderResult(result, _opts, theme, context) {
210473
+ return renderTodowriteResult(result, theme, context);
208897
210474
  }
208898
210475
  };
208899
210476
  }
@@ -208971,9 +210548,13 @@ function getCompatiblePiTodoCapture(todos) {
208971
210548
  const normalized = normalizeTodoStateJson(todos);
208972
210549
  if (normalized === null)
208973
210550
  return null;
208974
- return { normalized, todos };
210551
+ const parsed = parseTodos(todos);
210552
+ if (parsed === null)
210553
+ return null;
210554
+ return { normalized, todos: parsed };
208975
210555
  }
208976
210556
  function applyCompatiblePiTodoCapture(args) {
210557
+ rememberTodowriteToolCallTodos(args.toolCallId, args.capture.todos);
208977
210558
  if (args.todowriteEnabled) {
208978
210559
  setTodoSnapshot(args.sessionId, args.capture.todos);
208979
210560
  args.todoOverlay?.update(args.sessionId);
@@ -209118,7 +210699,7 @@ async function persistPiPressureFromMessageEnd(args) {
209118
210699
  }
209119
210700
  var PLUGIN_VERSION = (() => {
209120
210701
  try {
209121
- const req = createRequire3(import.meta.url);
210702
+ const req = createRequire4(import.meta.url);
209122
210703
  return req("../package.json").version;
209123
210704
  } catch {
209124
210705
  return "0.0.0";
@@ -209290,11 +210871,11 @@ async function src_default2(pi) {
209290
210871
  projectIdentity: identity,
209291
210872
  memoryEnabled: cfg.memory.enabled
209292
210873
  }, sessionId, dir, identity, (text) => {
209293
- pi.sendMessage({
209294
- customType: "ctx-status",
209295
- content: text,
209296
- display: true
209297
- }, { triggerTurn: false });
210874
+ sendCtxStatusMessage(pi, {
210875
+ title: "/ctx-embed",
210876
+ text,
210877
+ level: "info"
210878
+ });
209298
210879
  });
209299
210880
  }
209300
210881
  });
@@ -209362,6 +210943,12 @@ async function src_default2(pi) {
209362
210943
  todowriteEnabled
209363
210944
  });
209364
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
+ });
209365
210952
  const readLastTodoState = (sessionId) => getOrCreateSessionMeta(db, sessionId).lastTodoState;
209366
210953
  if (todowriteEnabled) {
209367
210954
  registerTodoStateLifecycle(pi, { readLastTodoState });
@@ -209375,6 +210962,8 @@ async function src_default2(pi) {
209375
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)");
209376
210963
  registerCtxAugCommand(pi, (ctx) => resolveCurrentProjectDeps(ctx).sidekickConfig);
209377
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");
209378
210967
  const recompRunner = new PiSubagentRunner;
209379
210968
  const wrapupRunner = new PiSubagentRunner;
209380
210969
  const upgradeRunner = new PiSubagentRunner;
@@ -209702,6 +211291,7 @@ ${block}` : event.systemPrompt;
209702
211291
  const sessionId = ctx.sessionManager.getSessionId();
209703
211292
  if (event.toolName === "todowrite") {
209704
211293
  const todoArgs = event.args;
211294
+ const toolCallId = typeof event.toolCallId === "string" ? event.toolCallId : undefined;
209705
211295
  const todos = todoArgs?.todos;
209706
211296
  const sessionMeta = Array.isArray(todos) ? getOrCreateSessionMeta(db, sessionId) : null;
209707
211297
  capturePiTodowriteArgsIfCompatible({
@@ -209710,7 +211300,8 @@ ${block}` : event.systemPrompt;
209710
211300
  todos,
209711
211301
  todowriteEnabled,
209712
211302
  todoOverlay,
209713
- persist: Boolean(sessionMeta && !sessionMeta.isSubagent)
211303
+ persist: Boolean(sessionMeta && !sessionMeta.isSubagent),
211304
+ toolCallId
209714
211305
  });
209715
211306
  if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
209716
211307
  if (sessionMeta && !sessionMeta.isSubagent) {