@retaw106/mmc-pi 2.5.0 → 2.7.1

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
@@ -139,13 +139,13 @@ import {
139
139
  getToolTagNumberByOwner,
140
140
  getNullOwnerToolTag,
141
141
  adoptNullOwnerToolTag,
142
+ BoundedSessionMap,
142
143
  resolveOpenCodeDbPath,
143
144
  hasV1MessageTables,
144
145
  assertOpenCodeStoreGeneration,
145
146
  withReadOnlySessionDb,
146
147
  expandToolPart,
147
148
  retreatPastHostUnservedRows,
148
- isCoveredInstructionRow,
149
149
  completedToolArcCrossesBoundary,
150
150
  estimateTrueRawMessageTokens,
151
151
  buildToolArcs,
@@ -182,12 +182,13 @@ import {
182
182
  getCachedAbsoluteMessageCount2,
183
183
  primeInMemoryTailRawMessageCache2,
184
184
  readRawSessionMessageIdOrdinalsForRange2,
185
+ compareRawSessionMessageOrder2,
185
186
  readRawSessionMessageById2,
186
187
  getRawSessionMessageCount2,
187
188
  getRawSessionTagKeysThrough2,
188
189
  getLegacyProtectedTailStartOrdinal2,
189
190
  readSessionChunk2
190
- } from "./index-t2aaz8cn.js";
191
+ } from "./index-07rh0nr2.js";
191
192
  import {
192
193
  inRootAsyncContext,
193
194
  registerSlowWriteReporter,
@@ -8911,7 +8912,7 @@ function parseFrozenCalibration(value) {
8911
8912
  if (typeof value !== "object" || value === null || Array.isArray(value))
8912
8913
  return null;
8913
8914
  const candidate = value;
8914
- if (typeof candidate.revision !== "string" || candidate.revision.length === 0 || typeof candidate.providerId !== "string" || typeof candidate.modelId !== "string" || !finitePositive(candidate.systemRatio) || !finitePositive(candidate.toolsRatio) || !finitePositive(candidate.proseRatio) || candidate.source !== "seed" && candidate.source !== "family-fallback") {
8915
+ if (typeof candidate.revision !== "string" || candidate.revision.length === 0 || typeof candidate.providerId !== "string" || typeof candidate.modelId !== "string" || !finitePositive(candidate.systemRatio) || !finitePositive(candidate.toolsRatio) || !finitePositive(candidate.proseRatio) || candidate.source !== "seed" && candidate.source !== "family-fallback" && candidate.source !== "model-id") {
8915
8916
  return null;
8916
8917
  }
8917
8918
  return {
@@ -10709,7 +10710,11 @@ function parsePersistedCompactionMarkerState(raw, storedTargetEndMessageId) {
10709
10710
  if (parsed && typeof parsed === "object" && typeof parsed.boundaryMessageId === "string" && typeof parsed.summaryMessageId === "string" && typeof parsed.compactionPartId === "string" && typeof parsed.summaryPartId === "string" && typeof parsed.boundaryOrdinal === "number") {
10710
10711
  const targetEndMessageId = typeof storedTargetEndMessageId === "string" && storedTargetEndMessageId.length > 0 ? storedTargetEndMessageId : typeof parsed.targetEndMessageId === "string" && parsed.targetEndMessageId.length > 0 ? parsed.targetEndMessageId : null;
10711
10712
  return {
10712
- ...parsed,
10713
+ boundaryMessageId: parsed.boundaryMessageId,
10714
+ summaryMessageId: parsed.summaryMessageId,
10715
+ compactionPartId: parsed.compactionPartId,
10716
+ summaryPartId: parsed.summaryPartId,
10717
+ boundaryOrdinal: parsed.boundaryOrdinal,
10713
10718
  targetEndMessageId
10714
10719
  };
10715
10720
  }
@@ -10720,12 +10725,32 @@ function getPersistedCompactionMarkerState(db, sessionId) {
10720
10725
  const row = db.prepare("SELECT compaction_marker_state, compaction_marker_target_end_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
10721
10726
  return parsePersistedCompactionMarkerState(row?.compaction_marker_state, row?.compaction_marker_target_end_message_id);
10722
10727
  }
10723
- function setPersistedCompactionMarkerState(db, sessionId, state) {
10724
- db.transaction(() => {
10728
+ function getCompactionMarkerPublicationState(db, sessionId) {
10729
+ const row = db.prepare("SELECT compaction_marker_state FROM session_meta WHERE session_id = ?").get(sessionId);
10730
+ const raw = row?.compaction_marker_state ?? "";
10731
+ let pending = null;
10732
+ try {
10733
+ pending = parsePersistedCompactionMarkerState(JSON.stringify(JSON.parse(raw).pending));
10734
+ } catch {}
10735
+ return { raw, pending };
10736
+ }
10737
+ function compareAndSetCompactionMarkerPublicationState(db, sessionId, expectedRaw, state, pending) {
10738
+ return db.transaction(() => {
10725
10739
  ensureSessionMetaRow(db, sessionId);
10726
- const previous = state === null ? getPersistedCompactionMarkerState(db, sessionId) ?? getDeferredClearedCompactionMarkerState(db, sessionId) : null;
10727
- const json = state ? JSON.stringify(state) : previous ? JSON.stringify({ deferredClear: previous }) : "";
10728
- db.prepare("UPDATE session_meta SET compaction_marker_state = ?, compaction_marker_target_end_message_id = ? WHERE session_id = ?").run(json, state?.targetEndMessageId ?? null, sessionId);
10740
+ let envelope = {};
10741
+ if (pending) {
10742
+ try {
10743
+ const parsed = JSON.parse(expectedRaw);
10744
+ if (parsed && typeof parsed === "object")
10745
+ envelope = parsed;
10746
+ } catch {}
10747
+ }
10748
+ const raw = JSON.stringify(pending ? { ...envelope, pending } : state);
10749
+ const result = db.prepare(`UPDATE session_meta SET compaction_marker_state = ?, compaction_marker_target_end_message_id = ?
10750
+ WHERE session_id = ? AND COALESCE(compaction_marker_state, '') = ?`).run(raw, state?.targetEndMessageId ?? null, sessionId, expectedRaw);
10751
+ if (result.changes !== 1)
10752
+ throw new Error("marker publication state changed concurrently");
10753
+ return raw;
10729
10754
  }).immediate();
10730
10755
  }
10731
10756
  function getDeferredClearedCompactionMarkerState(db, sessionId) {
@@ -11144,51 +11169,6 @@ import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypt
11144
11169
  import { lstatSync as lstatSync2, readdirSync as readdirSync3, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync3 } from "node:fs";
11145
11170
  import { basename, dirname as dirname3, join as join6 } from "node:path";
11146
11171
 
11147
- // ../plugin/src/shared/bounded-session-map.ts
11148
- class BoundedSessionMap {
11149
- maxEntries;
11150
- store = new Map;
11151
- constructor(maxEntries) {
11152
- if (!Number.isFinite(maxEntries) || maxEntries < 1) {
11153
- throw new Error(`BoundedSessionMap: maxEntries must be >= 1, got ${maxEntries}`);
11154
- }
11155
- this.maxEntries = maxEntries;
11156
- }
11157
- get(sessionId) {
11158
- const value = this.store.get(sessionId);
11159
- if (value === undefined)
11160
- return;
11161
- this.store.delete(sessionId);
11162
- this.store.set(sessionId, value);
11163
- return value;
11164
- }
11165
- peek(sessionId) {
11166
- return this.store.get(sessionId);
11167
- }
11168
- has(sessionId) {
11169
- return this.store.has(sessionId);
11170
- }
11171
- set(sessionId, value) {
11172
- if (this.store.has(sessionId)) {
11173
- this.store.delete(sessionId);
11174
- } else if (this.store.size >= this.maxEntries) {
11175
- const oldest = this.store.keys().next().value;
11176
- if (oldest !== undefined)
11177
- this.store.delete(oldest);
11178
- }
11179
- this.store.set(sessionId, value);
11180
- }
11181
- delete(sessionId) {
11182
- return this.store.delete(sessionId);
11183
- }
11184
- clear() {
11185
- this.store.clear();
11186
- }
11187
- get size() {
11188
- return this.store.size;
11189
- }
11190
- }
11191
-
11192
11172
  // ../plugin/src/shared/storage-file-mutex.ts
11193
11173
  import { execFileSync as execFileSync3 } from "node:child_process";
11194
11174
  import { createHash as createHash4, randomUUID } from "node:crypto";
@@ -12448,9 +12428,10 @@ function closeConnection(connection) {
12448
12428
  counters.readersClosed += 1;
12449
12429
  }
12450
12430
  }
12451
- function messageTimesQuery(count) {
12431
+ function messageTimesQuery(count, withVersions = false) {
12452
12432
  return `SELECT id, COALESCE(time_created, json_extract(data, '$.time.created')) AS created,
12453
12433
  json_extract(data, '$.time.completed') AS completed
12434
+ ${withVersions ? ", type, seq, time_created AS version_created, time_updated AS version_updated" : ""}
12454
12435
  FROM session_message WHERE +session_id = ? AND id IN (${Array.from({ length: count }, () => "?").join(",")})`;
12455
12436
  }
12456
12437
 
@@ -12570,9 +12551,6 @@ class V2StoreReader {
12570
12551
  return null;
12571
12552
  return typeof row.parent_id === "string" && row.parent_id.length > 0;
12572
12553
  }
12573
- sessionIds() {
12574
- return this.prepare("SELECT id FROM session_v2").all().map((row) => row.id);
12575
- }
12576
12554
  storedRowsAfter(sessionID, after) {
12577
12555
  const row = this.prepare("SELECT COUNT(*) AS count FROM session_message WHERE session_id = ? AND seq > ?").get(sessionID, after);
12578
12556
  return row.count;
@@ -12591,16 +12569,46 @@ class V2StoreReader {
12591
12569
  return row ? decode2(row) : null;
12592
12570
  });
12593
12571
  }
12594
- messageTimes(sessionID, ids) {
12595
- const times = new Map;
12572
+ messageTimeVersions(sessionID, ids, span) {
12573
+ const versions = new Map;
12574
+ if (ids.length > 0 && span && span.last - span.first < ids.length * 2) {
12575
+ const wanted = new Set(ids);
12576
+ const rows = this.prepare(`SELECT id, type, seq, time_created AS created, time_updated AS updated
12577
+ FROM session_message WHERE session_id = ? AND seq >= ? AND seq <= ?`).all(sessionID, span.first, span.last);
12578
+ for (const row of rows)
12579
+ if (wanted.has(row.id))
12580
+ versions.set(row.id, row);
12581
+ for (const [id, version] of this.messageTimeVersions(sessionID, ids.filter((id) => !versions.has(id))))
12582
+ versions.set(id, version);
12583
+ return versions;
12584
+ }
12596
12585
  for (let offset = 0;offset < ids.length; offset += 500) {
12597
12586
  const batch = ids.slice(offset, offset + 500);
12598
- const rows = this.prepare(messageTimesQuery(batch.length)).all(sessionID, ...batch);
12587
+ const rows = this.prepare(`SELECT id, type, seq, time_created AS created, time_updated AS updated
12588
+ FROM session_message WHERE +session_id = ? AND id IN (${batch.map(() => "?").join(",")})`).all(sessionID, ...batch);
12599
12589
  for (const row of rows)
12590
+ versions.set(row.id, row);
12591
+ }
12592
+ return versions;
12593
+ }
12594
+ messageTimes(sessionID, ids, versions) {
12595
+ const times = new Map;
12596
+ for (let offset = 0;offset < ids.length; offset += 500) {
12597
+ const batch = ids.slice(offset, offset + 500);
12598
+ const rows = this.prepare(messageTimesQuery(batch.length, versions !== undefined)).all(sessionID, ...batch);
12599
+ for (const row of rows) {
12600
12600
  times.set(row.id, {
12601
12601
  created: typeof row.created === "number" ? row.created : undefined,
12602
12602
  completed: typeof row.completed === "number" ? row.completed : undefined
12603
12603
  });
12604
+ versions?.set(row.id, {
12605
+ id: row.id,
12606
+ type: row.type,
12607
+ seq: row.seq,
12608
+ created: row.version_created,
12609
+ updated: row.version_updated
12610
+ });
12611
+ }
12604
12612
  }
12605
12613
  return times;
12606
12614
  }
@@ -13002,9 +13010,6 @@ function deleteUnmappedMessageFtsRows(db, sessionIds) {
13002
13010
  WHERE rowid > ? AND session_id IN (${placeholders})`).run(state.watermarkRowid, ...sessionIds);
13003
13011
  }
13004
13012
 
13005
- // ../plugin/src/features/magic-context/storage-session-tables.ts
13006
- var cleanupStatements = new WeakMap;
13007
-
13008
13013
  // ../plugin/src/features/magic-context/storage-meta-session.ts
13009
13014
  var SESSION_META_FALLBACK_SELECTS = {
13010
13015
  cache_ttl: "'5m' AS cache_ttl",
@@ -13910,6 +13915,39 @@ function keywordRelevance(query, content) {
13910
13915
  const words = new Set(terms(cleanSearchText(content)));
13911
13916
  return 0.8 * needles.filter((term) => words.has(term)).length / needles.length;
13912
13917
  }
13918
+ var CJK_RUN = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+$/u;
13919
+ var CJK_RUN_SPLIT = /([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+)/u;
13920
+ function hintTerms(text) {
13921
+ return [
13922
+ ...new Set(terms(text).flatMap((term) => term.split(CJK_RUN_SPLIT)).filter((term) => term.length > 0 && !QUERY_STOP_WORDS.has(term)))
13923
+ ];
13924
+ }
13925
+ function characterBigrams(run) {
13926
+ const characters = [...run];
13927
+ return characters.length === 1 ? characters : characters.slice(1).map((character, index) => characters[index] + character);
13928
+ }
13929
+ function hintKeywordRelevance(query, content) {
13930
+ const needles = hintTerms(query);
13931
+ if (needles.length === 0)
13932
+ return 0;
13933
+ const words = new Set(hintTerms(cleanSearchText(content)));
13934
+ const runs = [...words].filter((word) => CJK_RUN.test(word));
13935
+ const bigrams = new Set(runs.flatMap(characterBigrams));
13936
+ let matched = 0;
13937
+ for (const needle of needles) {
13938
+ if (!CJK_RUN.test(needle)) {
13939
+ matched += Number(words.has(needle));
13940
+ continue;
13941
+ }
13942
+ const grams = characterBigrams(needle);
13943
+ if ([...needle].length === 1) {
13944
+ matched += Number(runs.some((run) => run.includes(needle)));
13945
+ continue;
13946
+ }
13947
+ matched += Math.min(1, grams.filter((gram) => bigrams.has(gram)).length / Math.ceil(grams.length / 2));
13948
+ }
13949
+ return 0.8 * matched / needles.length;
13950
+ }
13913
13951
  function matchedSearchSnippet(content, query, cap = 220) {
13914
13952
  const text = cleanSearchText(content);
13915
13953
  const needles = new Set(terms(query));
@@ -13936,9 +13974,6 @@ function matchedSearchSnippet(content, query, cap = 220) {
13936
13974
  }
13937
13975
 
13938
13976
  // ../plugin/src/features/magic-context/message-index.ts
13939
- var MESSAGE_HISTORY_ORPHAN_SAFETY_AGE_MS = 24 * 60 * 60 * 1000;
13940
- var MESSAGE_HISTORY_ORPHAN_SWEEP_COOLDOWN_MS = 10 * 60 * 1000;
13941
- var MESSAGE_HISTORY_ORPHAN_UNAVAILABLE_REPROBE_MS = 24 * 60 * 60 * 1000;
13942
13977
  var lastIndexedStatements = new WeakMap;
13943
13978
  var insertMessageStatements = new WeakMap;
13944
13979
  var upsertProgressStatements = new WeakMap;
@@ -15747,6 +15782,12 @@ function noteLkgCapturedPrefix(sessionId) {
15747
15782
  }
15748
15783
 
15749
15784
  // ../plugin/src/hooks/magic-context/lkg-slot.ts
15785
+ var POSITIONAL_SYSTEM_KEY = "\x00mc-system:";
15786
+ function lkgInputIdentity(id, role, index) {
15787
+ if (typeof id === "string" && id.length > 0)
15788
+ return id.startsWith(POSITIONAL_SYSTEM_KEY) ? null : id;
15789
+ return role === "system" ? `${POSITIONAL_SYSTEM_KEY}${index}` : null;
15790
+ }
15750
15791
  var LKG_SINGLE_SLOT_BYTES = 72 * 1024 * 1024;
15751
15792
  var LKG_TOTAL_BYTES = 160 * 1024 * 1024;
15752
15793
  var LKG_METADATA_BYTES = 256;
@@ -16175,11 +16216,8 @@ function noteEntry(sessionId, input) {
16175
16216
  const slot = getSlot(sessionId);
16176
16217
  if (!slot)
16177
16218
  return null;
16178
- const messages = input.some((message) => isCoveredInstructionRow(message.info)) ? input.filter((message) => !isCoveredInstructionRow(message.info)) : input;
16179
- const entryInputIds = messages.map((message) => {
16180
- const id = message.info?.id;
16181
- return typeof id === "string" ? id : "";
16182
- });
16219
+ const messages = input;
16220
+ const entryInputIds = messages.map((message, index) => lkgInputIdentity(message.info?.id, message.info?.role, index) ?? "");
16183
16221
  const anchorIndex = entryInputIds.indexOf(slot.lastInputMessageId);
16184
16222
  if (anchorIndex < 0)
16185
16223
  return null;
@@ -16488,6 +16526,24 @@ function freezePiContentDecision(db, sessionId, kind, messageId) {
16488
16526
  // ../plugin/src/features/magic-context/storage-native-replay.ts
16489
16527
  var NATIVE_TOOL_INPUTS = "native tool inputs";
16490
16528
  var NATIVE_REASONING_IDS = "native reasoning ids";
16529
+ var nativeWriteBarriers = new WeakMap;
16530
+ function withNativeReplayWriteBarrier(db, sessionId, beforeWrite, edit) {
16531
+ let sessions = nativeWriteBarriers.get(db);
16532
+ if (!sessions) {
16533
+ sessions = new Map;
16534
+ nativeWriteBarriers.set(db, sessions);
16535
+ }
16536
+ const prior = sessions.get(sessionId);
16537
+ sessions.set(sessionId, beforeWrite);
16538
+ try {
16539
+ return edit();
16540
+ } finally {
16541
+ if (prior)
16542
+ sessions.set(sessionId, prior);
16543
+ else
16544
+ sessions.delete(sessionId);
16545
+ }
16546
+ }
16491
16547
  function invalidPersistedReplayState(lane, sessionId) {
16492
16548
  return new Error(`invalid persisted ${lane} state for session ${sessionId}`);
16493
16549
  }
@@ -16572,6 +16628,7 @@ function saveNativeToolInputs(db, sessionId, inputs) {
16572
16628
  }
16573
16629
  if (requested.size === 0)
16574
16630
  return;
16631
+ nativeWriteBarriers.get(db)?.get(sessionId)?.();
16575
16632
  const persisted = updateReplayDocument(db, sessionId, (doc) => {
16576
16633
  const current = parseNativeReplayState(doc, sessionId);
16577
16634
  let changed = false;
@@ -16598,6 +16655,7 @@ function addNativeReasoningIds(db, sessionId, ids) {
16598
16655
  }
16599
16656
  if (requested.size === 0)
16600
16657
  return;
16658
+ nativeWriteBarriers.get(db)?.get(sessionId)?.();
16601
16659
  const persisted = updateReplayDocument(db, sessionId, (doc) => {
16602
16660
  const current = parseNativeReplayState(doc, sessionId);
16603
16661
  let changed = false;
@@ -17799,7 +17857,7 @@ function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion)
17799
17857
  startMessageFtsRowidMapBackfill(db).then(async () => {
17800
17858
  if (!readsOpenCodeStore)
17801
17859
  return;
17802
- const { readRawSessionMessagePage, readRawSessionMessages } = await import("./read-session-chunk-hcecqvhm.js");
17860
+ const { readRawSessionMessagePage, readRawSessionMessages } = await import("./read-session-chunk-qg5jfqjg.js");
17803
17861
  const { startMessageTimeBackfill } = await import("./message-time-backfill-y26yy5pp.js");
17804
17862
  await startMessageTimeBackfill(db, Object.assign(readRawSessionMessages, {
17805
17863
  readPage: readRawSessionMessagePage
@@ -21055,7 +21113,7 @@ function registerStandalonePluginMarker(version) {
21055
21113
  // package.json
21056
21114
  var package_default = {
21057
21115
  name: "@retaw106/mmc-pi",
21058
- version: "2.5.0",
21116
+ version: "2.7.1",
21059
21117
  type: "module",
21060
21118
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
21061
21119
  main: "dist/index.js",
@@ -23532,6 +23590,14 @@ var RECENT_TOOL_SKELETON_WINDOW = 20;
23532
23590
  function buildReplacementContent(tagId) {
23533
23591
  return `[dropped §${tagId}§]`;
23534
23592
  }
23593
+ var FROZEN_TEXT_ANNOTATION_SUFFIX = /(?:\n\n(?:<instruction name="deferred_notes">(?:(?!<\/instruction>)[\s\S])*<\/instruction>|<ctx-search-hint>(?:(?!<\/ctx-search-hint>)[\s\S])*<\/ctx-search-hint>))+$/;
23594
+ function renderDroppedText(tagId, renderedText) {
23595
+ const placeholder = buildReplacementContent(tagId);
23596
+ if (renderedText?.startsWith(placeholder))
23597
+ return renderedText;
23598
+ const annotations = renderedText?.match(FROZEN_TEXT_ANNOTATION_SUFFIX)?.[0] ?? "";
23599
+ return placeholder + annotations;
23600
+ }
23535
23601
  function applyNewToolDrop(target, options) {
23536
23602
  const plan = planNewToolDrop(target, options, !!options.persist);
23537
23603
  if (!plan)
@@ -23618,6 +23684,7 @@ function prepareLegacyToolSkeletonConversions(db, sessionId, targets, options =
23618
23684
  AND drop_mode IN ('truncated', 'full')
23619
23685
  ORDER BY tag_number`).all(sessionId);
23620
23686
  const rows = readRows(db);
23687
+ options.prepareRemovals?.(rows);
23621
23688
  const identity = JSON.stringify(rows);
23622
23689
  const converted = new Map;
23623
23690
  for (const row of rows) {
@@ -23647,13 +23714,21 @@ function prepareLegacyToolSkeletonConversions(db, sessionId, targets, options =
23647
23714
  };
23648
23715
  }
23649
23716
  var PENDING_OPERATION_CHUNK_SIZE = 64;
23717
+ var PENDING_OPERATION_REJECTED_LOG_CHUNK = 20;
23650
23718
  function applyPendingOperations(sessionId, db, targets, protectedTagIds, preloadedTags, preloadedPendingOps, syntheticPendingOps = [], editMarkerTagIds = new Set, onTagReduced, onBatchComplete, onEligibilitySnapshot) {
23651
23719
  let didMutateMessage = false;
23652
23720
  let diagnostics;
23653
- const reject = (reason) => {
23721
+ const reject = (tagNumber, reason, state = {}) => {
23654
23722
  if (!diagnostics)
23655
23723
  return;
23656
23724
  diagnostics.reasons[reason] = (diagnostics.reasons[reason] ?? 0) + 1;
23725
+ const persisted = state.persisted === true;
23726
+ (diagnostics.rejected ??= []).push({
23727
+ tagNumber,
23728
+ reason,
23729
+ persisted,
23730
+ queueState: state.queueState ?? (persisted ? "removed" : "retained")
23731
+ });
23657
23732
  };
23658
23733
  let admitted = false;
23659
23734
  let startedAt = 0;
@@ -23713,29 +23788,39 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23713
23788
  for (const { op: pendingOp, synthetic } of chunk) {
23714
23789
  const tagStatus = tagStatusById.get(pendingOp.tagId);
23715
23790
  if (tagStatus === "compacted" || tagStatus === "dropped") {
23716
- reject("already_reduced");
23791
+ reject(pendingOp.tagId, "already_reduced", {
23792
+ queueState: synthetic ? "none" : "removed"
23793
+ });
23717
23794
  if (!synthetic)
23718
23795
  removePendingOp(db, sessionId, pendingOp.tagId);
23719
23796
  continue;
23720
23797
  }
23721
23798
  if (removedTagIds.has(pendingOp.tagId)) {
23722
- reject("drop_incomplete");
23799
+ reject(pendingOp.tagId, "drop_incomplete", {
23800
+ queueState: synthetic ? "none" : "retained"
23801
+ });
23723
23802
  continue;
23724
23803
  }
23725
23804
  const target = targets.get(pendingOp.tagId);
23726
23805
  const veto = pendingDropVeto(pendingOp.tagId, target, protectedTagIds);
23727
23806
  if (veto) {
23728
- reject(veto);
23807
+ reject(pendingOp.tagId, veto, {
23808
+ queueState: synthetic ? "none" : "retained"
23809
+ });
23729
23810
  continue;
23730
23811
  }
23731
23812
  const isToolTag = tagTypeById.get(pendingOp.tagId) === "tool";
23732
23813
  if (synthetic) {
23733
23814
  if (!isToolTag) {
23734
- reject("synthetic_non_tool");
23815
+ reject(pendingOp.tagId, "synthetic_non_tool", {
23816
+ queueState: "none"
23817
+ });
23735
23818
  continue;
23736
23819
  }
23737
23820
  if (target?.canDrop?.() !== true) {
23738
- reject("synthetic_target_not_droppable");
23821
+ reject(pendingOp.tagId, "synthetic_target_not_droppable", {
23822
+ queueState: "none"
23823
+ });
23739
23824
  continue;
23740
23825
  }
23741
23826
  }
@@ -23748,14 +23833,19 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23748
23833
  if (editMarkerTagIds.has(pendingOp.tagId)) {
23749
23834
  const strip = target?.hasAttachments?.() === true;
23750
23835
  if (target?.canDrop?.() === false || target?.prepareDrop?.() === false || !target) {
23751
- reject("edit_marker_incomplete");
23836
+ reject(pendingOp.tagId, "edit_marker_incomplete", {
23837
+ queueState: synthetic ? "none" : "retained"
23838
+ });
23752
23839
  continue;
23753
23840
  }
23754
23841
  persistDrop(strip ? "edit_marker_stripped" : "edit_marker");
23755
23842
  mutations.push(() => {
23756
23843
  const markResult = (strip ? target?.editMarkerStripped?.() : target?.editMarker?.()) ?? "absent";
23757
23844
  if (markResult === "incomplete" || markResult === "absent") {
23758
- reject(`edit_marker_${markResult}`);
23845
+ reject(pendingOp.tagId, `edit_marker_${markResult}`, {
23846
+ persisted: true,
23847
+ queueState: synthetic ? "none" : "removed"
23848
+ });
23759
23849
  return;
23760
23850
  }
23761
23851
  record(true);
@@ -23767,7 +23857,9 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23767
23857
  } else {
23768
23858
  const plan = planNewToolDrop(target, { inWindow: skeletonWindow.has(pendingOp.tagId) }, true, plannedRemovals);
23769
23859
  if (!plan) {
23770
- reject("drop_incomplete");
23860
+ reject(pendingOp.tagId, "drop_incomplete", {
23861
+ queueState: synthetic ? "none" : "retained"
23862
+ });
23771
23863
  continue;
23772
23864
  }
23773
23865
  persistDrop(plan.mode);
@@ -23778,7 +23870,10 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23778
23870
  mutations.push(() => {
23779
23871
  const { result: dropResult, mode: appliedMode } = plan.apply();
23780
23872
  if (dropResult === "incomplete" || synthetic && dropResult !== "removed" && dropResult !== "truncated") {
23781
- reject(`drop_${dropResult}`);
23873
+ reject(pendingOp.tagId, `drop_${dropResult}`, {
23874
+ persisted: true,
23875
+ queueState: synthetic ? "none" : "removed"
23876
+ });
23782
23877
  return;
23783
23878
  }
23784
23879
  if (dropResult === "removed" || dropResult === "truncated") {
@@ -23788,15 +23883,18 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23788
23883
  mode: appliedMode === "full" ? "full" : "truncated"
23789
23884
  });
23790
23885
  } else {
23791
- reject(`drop_${dropResult}`);
23886
+ reject(pendingOp.tagId, `drop_${dropResult}`, {
23887
+ persisted: true,
23888
+ queueState: synthetic ? "none" : "removed"
23889
+ });
23792
23890
  record(false);
23793
23891
  }
23794
23892
  });
23795
23893
  }
23796
23894
  } else if (target) {
23797
- const replacement = buildReplacementContent(pendingOp.tagId);
23895
+ const replacement = renderDroppedText(pendingOp.tagId, target.getContent?.());
23798
23896
  if (target.canSetContent?.(replacement) === false) {
23799
- reject("content_unchanged");
23897
+ reject(pendingOp.tagId, "content_unchanged");
23800
23898
  continue;
23801
23899
  }
23802
23900
  persistDrop();
@@ -23812,12 +23910,18 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23812
23910
  removedCharacters: Math.max(0, originalCharacters - replacement.length)
23813
23911
  });
23814
23912
  } else {
23815
- reject("content_unchanged");
23913
+ reject(pendingOp.tagId, "content_unchanged", {
23914
+ persisted: true,
23915
+ queueState: "removed"
23916
+ });
23816
23917
  }
23817
23918
  });
23818
23919
  } else if (!synthetic) {
23819
- reject("target_absent");
23820
23920
  persistDrop();
23921
+ reject(pendingOp.tagId, "target_absent", {
23922
+ persisted: true,
23923
+ queueState: "removed"
23924
+ });
23821
23925
  mutations.push(() => record(false));
23822
23926
  }
23823
23927
  }
@@ -23844,6 +23948,15 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23844
23948
  const reasons = Object.entries(diagnostics.reasons).sort(([left], [right]) => left.localeCompare(right)).map(([reason, count]) => `${reason}:${count}`).join(",");
23845
23949
  sessionLog(sessionId, `pending operations no-op: source=${diagnostics.source} total=${diagnostics.total} persisted=${diagnostics.persistedWithoutMutation} reasons=${reasons || "none"}`);
23846
23950
  }
23951
+ const rejected = diagnostics.rejected;
23952
+ if (rejected && rejected.length > 0) {
23953
+ const lineCount = Math.ceil(rejected.length / PENDING_OPERATION_REJECTED_LOG_CHUNK);
23954
+ for (let offset = 0;offset < rejected.length; offset += PENDING_OPERATION_REJECTED_LOG_CHUNK) {
23955
+ const entries = rejected.slice(offset, offset + PENDING_OPERATION_REJECTED_LOG_CHUNK).map((entry) => `${entry.tagNumber}:${entry.reason}:${entry.queueState}${entry.persisted ? ":persisted" : ""}`).join(",");
23956
+ const chunkLabel = lineCount > 1 ? ` chunk=${Math.floor(offset / PENDING_OPERATION_REJECTED_LOG_CHUNK) + 1}/${lineCount}` : "";
23957
+ sessionLog(sessionId, `pending operations rejected: source=${diagnostics.source} total=${diagnostics.total} mutated=${diagnostics.mutated} persisted=${diagnostics.persistedWithoutMutation}${chunkLabel} rejected=${entries}`);
23958
+ }
23959
+ }
23847
23960
  }
23848
23961
  return didMutateMessage;
23849
23962
  }
@@ -23884,7 +23997,7 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
23884
23997
  }
23885
23998
  }
23886
23999
  } else if (target) {
23887
- const changed = target.setContent(buildReplacementContent(tag.tagNumber));
24000
+ const changed = target.setContent(renderDroppedText(tag.tagNumber, target.getContent?.()));
23888
24001
  if (changed)
23889
24002
  didMutateMessage = true;
23890
24003
  }
@@ -23892,6 +24005,15 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
23892
24005
  }
23893
24006
  return didMutateMessage;
23894
24007
  }
24008
+ function prepareFlushedToolRemovals(targets, tags) {
24009
+ for (const tag of tags) {
24010
+ if (tag.status !== "dropped" || tag.type !== "tool")
24011
+ continue;
24012
+ if (tag.dropMode != null && tag.dropMode !== "full")
24013
+ continue;
24014
+ targets.get(tag.tagNumber)?.prepareReplayDrop?.();
24015
+ }
24016
+ }
23895
24017
 
23896
24018
  // ../plugin/src/hooks/magic-context/cache-busting-signals.ts
23897
24019
  function hasReclaimRide(signals) {
@@ -25565,6 +25687,56 @@ function isHistorianDrainBudgetSpent(args) {
25565
25687
  return true;
25566
25688
  }
25567
25689
 
25690
+ // ../plugin/src/hooks/magic-context/historian-history-wait.ts
25691
+ var historyWaits = new Map;
25692
+ function newestMessageId(snapshot) {
25693
+ try {
25694
+ const count = getRawSessionMessageOrdinalCount2(snapshot.sessionId);
25695
+ const ids = readRawSessionMessageIdOrdinalsForRange2(snapshot.sessionId, count, count);
25696
+ const newest = ids.keys().next().value;
25697
+ if (newest)
25698
+ return newest;
25699
+ } catch {}
25700
+ return snapshot.rawLastMessageIdAtTrigger;
25701
+ }
25702
+ function recoveryWindow(snapshot, percentage) {
25703
+ return percentage >= escalationBands(snapshot.executeThresholdPercentage).forceMaterializationPercentage || snapshot.emergencyTailScale !== undefined;
25704
+ }
25705
+ function clearHistorianHistoryWait(sessionId) {
25706
+ historyWaits.delete(sessionId);
25707
+ }
25708
+ function recordHistorianNoProgress(snapshot, emergency = false) {
25709
+ const newest = newestMessageId(snapshot);
25710
+ if (!newest)
25711
+ return;
25712
+ const previous = historyWaits.get(snapshot.sessionId);
25713
+ const sameHistory = previous?.newestMessageId === newest;
25714
+ historyWaits.set(snapshot.sessionId, {
25715
+ newestMessageId: newest,
25716
+ recoveryAttempted: sameHistory && previous.recoveryAttempted || emergency || recoveryWindow(snapshot, snapshot.usagePercentage)
25717
+ });
25718
+ }
25719
+ function shouldWaitForNewHistorianHistory(snapshot, options = {}) {
25720
+ const wait = historyWaits.get(snapshot.sessionId);
25721
+ if (!wait)
25722
+ return false;
25723
+ const newest = newestMessageId(snapshot);
25724
+ if (newest && newest !== wait.newestMessageId) {
25725
+ try {
25726
+ const order = compareRawSessionMessageOrder2(snapshot.sessionId, newest, wait.newestMessageId);
25727
+ if (order === null || order > 0)
25728
+ return false;
25729
+ } catch {}
25730
+ }
25731
+ const recovery = options.emergency || recoveryWindow(snapshot, options.usagePercentage ?? snapshot.usagePercentage);
25732
+ if (recovery && !wait.recoveryAttempted) {
25733
+ if (options.claim)
25734
+ wait.recoveryAttempted = true;
25735
+ return false;
25736
+ }
25737
+ return true;
25738
+ }
25739
+
25568
25740
  // ../plugin/src/hooks/magic-context/active-anthropic-turn.ts
25569
25741
  function isInActiveAnthropicTurn(messages, index, anthropic) {
25570
25742
  const start = activeAnthropicTurnStart(messages, anthropic);
@@ -25948,6 +26120,13 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
25948
26120
  }
25949
26121
  return { shouldFire: false };
25950
26122
  }
26123
+ if (tailInfo.boundarySnapshot && shouldWaitForNewHistorianHistory(tailInfo.boundarySnapshot, {
26124
+ usagePercentage: usage.percentage,
26125
+ emergency: getOverflowState(db, sessionId).needsEmergencyRecovery
26126
+ })) {
26127
+ sessionLog(sessionId, "compartment trigger: waiting for new history after no forward progress");
26128
+ return { shouldFire: false };
26129
+ }
25951
26130
  const canClearReasoning = reasoningProjection?.canClearReasoning ?? modelAcceptsEmptyContent(reasoningProjection?.providerID);
25952
26131
  const projectedPostDropPercentage = estimateProjectedPostDropPercentage(db, sessionId, usage, preloadedActiveTags ?? getActiveTagsBySession(db, sessionId), keepReasoningTokens, sessionMeta.clearedReasoningThroughTag, canClearReasoning, reasoningProjection?.budgetCutoff, eligiblePendingDropTagIds);
25953
26132
  const relativePostDropTarget = executeThresholdPercentage * POST_DROP_TARGET_RATIO;
@@ -27139,6 +27318,93 @@ function compareOpenCodeMessagesByCanonicalOrder(sessionId, leftMessageId, right
27139
27318
  return 1;
27140
27319
  return 0;
27141
27320
  }
27321
+ function planCompactionMarker(args, boundary) {
27322
+ const identity = `${args.sessionId}\x00${args.endMessageId}`;
27323
+ return {
27324
+ boundaryMessageId: boundary.id,
27325
+ summaryMessageId: generateMessageId(boundary.timeCreated + 1, 1n, `${identity}\x00summary-message`),
27326
+ compactionPartId: generatePartId(boundary.timeCreated, 1n, `${identity}\x00compaction-part`),
27327
+ summaryPartId: generatePartId(boundary.timeCreated + 1, 2n, `${identity}\x00summary-part`)
27328
+ };
27329
+ }
27330
+ function withCompactionMarkerPublicationLock(operation) {
27331
+ return runMarkerWriteTransaction(getWritableOpenCodeDb(), "compaction-marker-replace", operation);
27332
+ }
27333
+ var MARKER_PAIR_FROM = `FROM message m
27334
+ JOIN part p ON +p.session_id = m.session_id
27335
+ AND p.message_id = json_extract(m.data, '$.parentID')
27336
+ WHERE m.session_id = ?
27337
+ AND CASE WHEN json_valid(m.data)
27338
+ THEN COALESCE(json_extract(m.data, '$.summary'), 0) ELSE 0 END = 1
27339
+ AND COALESCE(json_extract(m.data, '$.finish'), '') <> ''
27340
+ AND COALESCE(json_extract(p.data, '$.type'), '') = 'compaction'`;
27341
+ var MC_MARKER_IDENTITY = `(COALESCE(json_extract(m.data, '$.providerID'), '') = 'magic-context'
27342
+ OR COALESCE(json_extract(m.data, '$.modelID'), '') = 'magic-context'
27343
+ OR EXISTS (SELECT 1 FROM part t
27344
+ WHERE t.message_id = m.id AND +t.session_id = m.session_id
27345
+ AND COALESCE(json_extract(t.data, '$.type'), '') = 'text'
27346
+ AND COALESCE(json_extract(t.data, '$.text'), '') = ?))`;
27347
+ function inspectCompactionMarkerPublication(sessionId, recognized, summaryText) {
27348
+ const db = getWritableOpenCodeDb();
27349
+ const ids = [...new Set(recognized.map((marker) => marker.summaryMessageId))];
27350
+ const placeholders = ids.map(() => "?").join(", ");
27351
+ const rogue = db.prepare(`SELECT 1 ${MARKER_PAIR_FROM}
27352
+ AND ${MC_MARKER_IDENTITY}
27353
+ AND m.id NOT IN (${placeholders})
27354
+ LIMIT 1`).get(sessionId, summaryText, ...ids);
27355
+ if (rogue)
27356
+ return { kind: "ambiguous" };
27357
+ const pairs = db.prepare(`SELECT m.id AS summaryId, p.message_id AS boundaryId, p.id AS partId
27358
+ ${MARKER_PAIR_FROM}
27359
+ AND m.id IN (${placeholders})
27360
+ LIMIT 2`).all(sessionId, ...ids);
27361
+ if (pairs.length > 1)
27362
+ return { kind: "ambiguous" };
27363
+ const rowById = db.prepare(`SELECT 1 FROM message WHERE id = ?
27364
+ UNION ALL SELECT 1 FROM part WHERE id IN (?, ?) LIMIT 1`);
27365
+ if (pairs.length === 0) {
27366
+ for (const marker of recognized) {
27367
+ if (rowById.get(marker.summaryMessageId, marker.compactionPartId, marker.summaryPartId))
27368
+ return { kind: "ambiguous" };
27369
+ }
27370
+ return { kind: "verified", marker: null };
27371
+ }
27372
+ const pair = pairs[0];
27373
+ const marker = recognized.find((item) => item.summaryMessageId === pair.summaryId && item.boundaryMessageId === pair.boundaryId && item.compactionPartId === pair.partId);
27374
+ if (!marker)
27375
+ return { kind: "ambiguous" };
27376
+ for (const other of recognized) {
27377
+ if (other.summaryMessageId === marker.summaryMessageId && other.compactionPartId === marker.compactionPartId && other.summaryPartId === marker.summaryPartId)
27378
+ continue;
27379
+ if (rowById.get(other.summaryMessageId, other.compactionPartId, other.summaryPartId))
27380
+ return { kind: "ambiguous" };
27381
+ }
27382
+ const row = db.prepare(`SELECT c.data AS compactionData FROM message m
27383
+ JOIN message b ON b.id = ? AND b.session_id = m.session_id
27384
+ JOIN part c ON c.id = ? AND c.message_id = b.id AND c.session_id = m.session_id
27385
+ JOIN part t ON t.id = ? AND t.message_id = m.id AND t.session_id = m.session_id
27386
+ WHERE m.id = ? AND m.session_id = ?
27387
+ AND json_extract(b.data, '$.role') = 'user'
27388
+ AND json_extract(m.data, '$.role') = 'assistant'
27389
+ AND json_extract(m.data, '$.parentID') = b.id
27390
+ AND json_extract(m.data, '$.summary') = 1
27391
+ AND json_extract(m.data, '$.finish') = 'stop'
27392
+ AND json_extract(m.data, '$.error') IS NULL
27393
+ AND json_extract(m.data, '$.providerID') = 'magic-context'
27394
+ AND json_extract(m.data, '$.modelID') = 'magic-context'
27395
+ AND json_extract(t.data, '$.type') = 'text' AND json_extract(t.data, '$.text') = ?
27396
+ AND (SELECT COUNT(*) FROM part p WHERE p.message_id = m.id AND +p.session_id = m.session_id) = 1`).get(marker.boundaryMessageId, marker.compactionPartId, marker.summaryPartId, marker.summaryMessageId, sessionId, summaryText);
27397
+ if (!row || !isMcCanonicalCompactionPartData(JSON.parse(row.compactionData)) || findBoundaryUserMessage(sessionId, marker.boundaryMessageId)?.id !== marker.boundaryMessageId)
27398
+ return { kind: "ambiguous" };
27399
+ return { kind: "verified", marker };
27400
+ }
27401
+ function isCompactionMarkerIntentAbsent(marker, targetEndMessageId) {
27402
+ if (!targetEndMessageId)
27403
+ return false;
27404
+ const db = getWritableOpenCodeDb();
27405
+ return !db.prepare(`SELECT 1 FROM message WHERE id IN (?, ?)
27406
+ UNION ALL SELECT 1 FROM part WHERE id IN (?, ?) LIMIT 1`).get(targetEndMessageId, marker.summaryMessageId, marker.compactionPartId, marker.summaryPartId);
27407
+ }
27142
27408
  function removeLegacyMarkerLineageRows(db, args) {
27143
27409
  const legacySummaries = db.prepare(`SELECT m.id
27144
27410
  FROM message m
@@ -27182,10 +27448,11 @@ function injectCompactionMarker(args) {
27182
27448
  return null;
27183
27449
  }
27184
27450
  const boundaryTime = boundary.timeCreated;
27185
- const markerIdentity = `${args.sessionId}\x00${args.endMessageId}`;
27186
- const summaryMsgId = generateMessageId(boundaryTime + 1, 1n, `${markerIdentity}\x00summary-message`);
27187
- const compactionPartId = generatePartId(boundaryTime, 1n, `${markerIdentity}\x00compaction-part`);
27188
- const summaryPartId = generatePartId(boundaryTime + 1, 2n, `${markerIdentity}\x00summary-part`);
27451
+ const {
27452
+ summaryMessageId: summaryMsgId,
27453
+ compactionPartId,
27454
+ summaryPartId
27455
+ } = planCompactionMarker(args, boundary);
27189
27456
  const summaryMsgData = JSON.stringify({
27190
27457
  role: "assistant",
27191
27458
  parentID: boundary.id,
@@ -27276,25 +27543,122 @@ function replaceCompactionMarker(existing, args) {
27276
27543
  };
27277
27544
  }
27278
27545
  }
27546
+ function isMcCanonicalCompactionPartData(data) {
27547
+ if (typeof data !== "object" || data === null || Array.isArray(data))
27548
+ return false;
27549
+ const record = data;
27550
+ const keys = Object.keys(record).sort();
27551
+ return keys.length === 2 && keys[0] === "auto" && keys[1] === "type" && record.type === "compaction" && record.auto === true;
27552
+ }
27553
+
27554
+ // ../plugin/src/hooks/magic-context/compaction-marker-publication.ts
27555
+ function samePair(left, right) {
27556
+ return left.boundaryMessageId === right.boundaryMessageId && left.summaryMessageId === right.summaryMessageId && left.compactionPartId === right.compactionPartId && left.summaryPartId === right.summaryPartId;
27557
+ }
27558
+ function publishRecoverableCompactionMarker(db, args, targetIsCurrent) {
27559
+ const { sessionId } = args;
27560
+ let raw = "";
27561
+ let previous = null;
27562
+ let oldSummary = null;
27563
+ let intended;
27564
+ let reason = "publication-failed";
27565
+ let recovered;
27566
+ let cut = "uncertain";
27567
+ let writingState = false;
27568
+ const persist = (state) => {
27569
+ writingState = true;
27570
+ db.transaction(() => {
27571
+ raw = compareAndSetCompactionMarkerPublicationState(db, sessionId, raw, state, null);
27572
+ if (previous && previous.summaryMessageId !== state.summaryMessageId) {
27573
+ const tag = getTagNumberByMessageId(db, sessionId, `${previous.summaryMessageId}:p0`);
27574
+ if (tag !== null)
27575
+ updateTagStatus(db, sessionId, tag, "dropped");
27576
+ }
27577
+ }).immediate();
27578
+ writingState = false;
27579
+ previous = state;
27580
+ };
27581
+ try {
27582
+ cut = getCompactionMarkerPublicationState(db, sessionId).pending ? "uncertain" : "definitely-no-cut";
27583
+ const result = withCompactionMarkerPublicationLock(() => {
27584
+ const snapshot = getCompactionMarkerPublicationState(db, sessionId);
27585
+ if (snapshot.pending)
27586
+ cut = "uncertain";
27587
+ raw = snapshot.raw;
27588
+ previous = getPersistedCompactionMarkerState(db, sessionId) ?? getDeferredClearedCompactionMarkerState(db, sessionId);
27589
+ oldSummary = previous?.summaryMessageId ?? null;
27590
+ if (snapshot.pending && (snapshot.pending.boundaryOrdinal > args.endOrdinal || snapshot.pending.boundaryOrdinal === args.endOrdinal && snapshot.pending.targetEndMessageId !== args.endMessageId) && !isCompactionMarkerIntentAbsent(snapshot.pending, snapshot.pending.targetEndMessageId))
27591
+ throw new Error("a newer marker publication intent is pending");
27592
+ const boundary = findBoundaryUserMessage(sessionId, args.endMessageId);
27593
+ if (!targetIsCurrent() || !boundary || boundary.id !== args.resolvedBoundary?.id || boundary.timeCreated !== args.resolvedBoundary.timeCreated)
27594
+ throw new Error("marker publication frontier changed before publication");
27595
+ intended = {
27596
+ ...planCompactionMarker(args, boundary),
27597
+ boundaryOrdinal: args.endOrdinal,
27598
+ targetEndMessageId: args.endMessageId
27599
+ };
27600
+ let pending = snapshot.pending;
27601
+ if (pending) {
27602
+ const pendingTarget = pending.targetEndMessageId;
27603
+ const pendingBoundary = pendingTarget ? findBoundaryUserMessage(sessionId, pendingTarget) : null;
27604
+ const compartments = pendingTarget ? getCompartmentsByEndMessageId(db, sessionId, pendingTarget) : [];
27605
+ const validTarget = pending.targetEndMessageId === args.endMessageId && pending.boundaryOrdinal === args.endOrdinal || compartments.length === 1 && compartments[0].endMessage === pending.boundaryOrdinal;
27606
+ if (!validTarget || !pendingBoundary || !pendingTarget || !samePair(pending, planCompactionMarker({ ...args, endMessageId: pendingTarget }, pendingBoundary)))
27607
+ pending = null;
27608
+ }
27609
+ const inspection = inspectCompactionMarkerPublication(sessionId, [intended, ...previous ? [previous] : [], ...pending ? [pending] : []], args.summaryText);
27610
+ if (inspection.kind === "ambiguous") {
27611
+ reason = "ambiguous-left-alone";
27612
+ return {
27613
+ kind: cut,
27614
+ error: new Error("unverified or ambiguous host marker pairs; left alone")
27615
+ };
27616
+ }
27617
+ cut = "definitely-no-cut";
27618
+ const host = inspection.marker;
27619
+ if (host && (pending && samePair(host, pending) || samePair(host, intended))) {
27620
+ const adopted = pending && samePair(host, pending) ? pending : intended;
27621
+ recovered = adopted === pending ? "pending-intent-recovered" : "adopted-existing-pair";
27622
+ cut = "uncertain";
27623
+ if (samePair(host, intended))
27624
+ return { kind: "committed", marker: host };
27625
+ persist(adopted);
27626
+ }
27627
+ writingState = true;
27628
+ raw = compareAndSetCompactionMarkerPublicationState(db, sessionId, raw, previous, intended);
27629
+ writingState = false;
27630
+ cut = "uncertain";
27631
+ const replacement = replaceCompactionMarker(host, {
27632
+ ...args,
27633
+ resolvedBoundary: boundary
27634
+ });
27635
+ if (recovered && replacement.kind !== "committed")
27636
+ return { kind: "uncertain", error: replacement.error };
27637
+ if (replacement.kind !== "committed")
27638
+ cut = replacement.kind === "uncertain" ? "uncertain" : "definitely-no-cut";
27639
+ return replacement;
27640
+ });
27641
+ if (result.kind === "committed") {
27642
+ if (!intended)
27643
+ throw new Error("committed marker has no publication intent");
27644
+ persist(intended);
27645
+ reason = recovered ?? "published";
27646
+ }
27647
+ return result;
27648
+ } catch (error) {
27649
+ if (writingState)
27650
+ reason = "state-write-failed";
27651
+ return {
27652
+ kind: cut,
27653
+ error: error instanceof Error ? error : new Error(String(error))
27654
+ };
27655
+ } finally {
27656
+ sessionLog(sessionId, `compaction-marker publication reason=${reason} oldSummary=${oldSummary ?? "none"} newSummary=${intended?.summaryMessageId ?? "none"} target=${args.endMessageId} at=${Date.now()}`);
27657
+ }
27658
+ }
27279
27659
 
27280
27660
  // ../plugin/src/hooks/magic-context/compaction-marker-manager.ts
27281
27661
  var MARKER_SUMMARY_TEXT = "[Compacted by magic-context — session history is managed by the plugin]";
27282
- function dropMarkerSummaryTag(db, sessionId, summaryMessageId) {
27283
- const tagNumber = getTagNumberByMessageId(db, sessionId, `${summaryMessageId}:p0`);
27284
- if (tagNumber !== null)
27285
- updateTagStatus(db, sessionId, tagNumber, "dropped");
27286
- }
27287
- function persistMarkerStateAndDropReplacedTag(db, sessionId, state, replacedSummaryMessageId) {
27288
- let transactionStartedAt = 0;
27289
- db.transaction(() => {
27290
- transactionStartedAt = performance.now();
27291
- setPersistedCompactionMarkerState(db, sessionId, state);
27292
- if (replacedSummaryMessageId !== null) {
27293
- dropMarkerSummaryTag(db, sessionId, replacedSummaryMessageId);
27294
- }
27295
- }).immediate();
27296
- logSlowWriteTransaction("marker-drain", transactionStartedAt);
27297
- }
27298
27662
  function getCompartmentEndMessageIdForOrdinal(db, sessionId, endOrdinal) {
27299
27663
  const row = db.prepare(`SELECT end_message_id
27300
27664
  FROM compartments
@@ -27335,8 +27699,7 @@ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEn
27335
27699
  return false;
27336
27700
  }
27337
27701
  const existing = getPersistedCompactionMarkerState(db, sessionId);
27338
- const removedSummaryMessageId = existing?.summaryMessageId ?? null;
27339
- if (existing) {
27702
+ if (existing && !getCompactionMarkerPublicationState(db, sessionId).pending) {
27340
27703
  if (existingMarkerAlreadyCoversTarget(sessionId, existing, lastCompartmentEnd, targetEndMessageId)) {
27341
27704
  return true;
27342
27705
  }
@@ -27346,22 +27709,15 @@ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEn
27346
27709
  sessionLog(sessionId, `compaction-marker: no user boundary found at or before endMessageId ${targetEndMessageId} (ordinal ${lastCompartmentEnd}); preserving existing marker`);
27347
27710
  return false;
27348
27711
  }
27349
- const replacement = replaceCompactionMarker(existing, {
27712
+ const replacement = publishRecoverableCompactionMarker(db, {
27350
27713
  sessionId,
27351
27714
  endOrdinal: lastCompartmentEnd,
27352
27715
  endMessageId: targetEndMessageId,
27353
27716
  summaryText: MARKER_SUMMARY_TEXT,
27354
27717
  directory: directory ?? process.cwd(),
27355
27718
  resolvedBoundary: boundary
27356
- });
27719
+ }, () => JSON.stringify(getPersistedCompactionMarkerState(db, sessionId)) === JSON.stringify(existing) && getCompartmentEndMessageIdForOrdinal(db, sessionId, lastCompartmentEnd) === targetEndMessageId);
27357
27720
  if (replacement.kind === "committed") {
27358
- const result = replacement.marker;
27359
- persistMarkerStateAndDropReplacedTag(db, sessionId, {
27360
- ...result,
27361
- boundaryOrdinal: lastCompartmentEnd,
27362
- targetEndMessageId
27363
- }, removedSummaryMessageId);
27364
- sessionLog(sessionId, `compaction-marker: injected at ordinal ${lastCompartmentEnd}, boundary user msg ${result.boundaryMessageId}`);
27365
27721
  return true;
27366
27722
  }
27367
27723
  return false;
@@ -28636,7 +28992,7 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
28636
28992
  targets,
28637
28993
  skipPrefixInjection,
28638
28994
  entryFingerprint: options.entryFingerprintByMessageId?.get(messageId) ?? null,
28639
- reuseIdentity: reuseIdentity || contentDerivedTextIds !== undefined,
28995
+ reuseIdentity: reuseIdentity || options.reuseTextMessageIds?.has(messageId) === true || contentDerivedTextIds !== undefined,
28640
28996
  timing,
28641
28997
  textIdentitySourceCache: options.textIdentitySourceCache,
28642
28998
  textTokenCache: options.textTokenCache
@@ -28705,7 +29061,11 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
28705
29061
  if (timing)
28706
29062
  timing.identity += performance.now() - identityStart;
28707
29063
  } else {
28708
- const accounting = readAggregateToolAccounting(part, timing, options.toolTokenCache, tokenCacheKey);
29064
+ const accounting = readAggregateToolAccounting(part, timing, options.toolTokenCache, tokenCacheKey, {
29065
+ byteSize: existing.maxByteSize,
29066
+ tokenCount: existing.tokenCountKnown ? existing.maxTokenCount : null,
29067
+ inputTokenCount: existing.inputTokenCount
29068
+ });
28709
29069
  text = accounting.text;
28710
29070
  if (part.kind === "tool_result") {
28711
29071
  applyGrownToolResultAccounting({
@@ -28764,6 +29124,7 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
28764
29124
  occurrences: [{ message, part, kind: part.kind }],
28765
29125
  maxByteSize: reusableAccounting.byteSize,
28766
29126
  maxTokenCount: reusableAccounting.tokenCount ?? 0,
29127
+ tokenCountKnown: reusableAccounting.tokenCount !== null,
28767
29128
  toolName: null,
28768
29129
  inputByteSize: reusableAccounting.inputByteSize,
28769
29130
  inputTokenCount: reusableAccounting.inputTokenCount,
@@ -28787,7 +29148,7 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
28787
29148
  if (timing)
28788
29149
  timing.identity += performance.now() - identityStart;
28789
29150
  } else {
28790
- const accounting = readAggregateToolAccounting(part, timing, options.toolTokenCache, tokenCacheKey);
29151
+ const accounting = readAggregateToolAccounting(part, timing, options.toolTokenCache, tokenCacheKey, tagger.getToolTagAccounting(sessionId, callId, messageId));
28791
29152
  text = accounting.text;
28792
29153
  const outputByteSize = part.kind === "tool_result" ? accounting.byteSize : 0;
28793
29154
  const outputTokenCount = part.kind === "tool_result" ? accounting.tokenCount : 0;
@@ -28805,6 +29166,7 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
28805
29166
  occurrences: [{ message, part, kind: part.kind }],
28806
29167
  maxByteSize: persistedAccounting?.byteSize ?? outputByteSize,
28807
29168
  maxTokenCount: persistedAccounting?.tokenCount ?? outputTokenCount,
29169
+ tokenCountKnown: persistedAccounting ? persistedAccounting.tokenCount !== null : true,
28808
29170
  toolName: accounting.toolName,
28809
29171
  inputByteSize: persistedAccounting?.inputByteSize ?? (part.kind === "tool_use" ? accounting.inputByteSize : 0),
28810
29172
  inputTokenCount: persistedAccounting?.inputTokenCount ?? (part.kind === "tool_use" ? firstInputTokenCount : null),
@@ -28859,7 +29221,7 @@ function tagTranscript(sessionId, transcript, tagger, db, options = {}) {
28859
29221
  function syncToolAggregateAccounting(tagger, sessionId, aggregate) {
28860
29222
  tagger.setToolTagAccounting(sessionId, aggregate.tagId, {
28861
29223
  byteSize: aggregate.maxByteSize,
28862
- tokenCount: aggregate.maxTokenCount,
29224
+ tokenCount: aggregate.tokenCountKnown ? aggregate.maxTokenCount : null,
28863
29225
  inputByteSize: aggregate.inputByteSize,
28864
29226
  inputTokenCount: aggregate.inputTokenCount
28865
29227
  });
@@ -28879,16 +29241,19 @@ function applyGrownToolResultAccounting(args) {
28879
29241
  }
28880
29242
  args.aggregate.maxByteSize = args.byteSize;
28881
29243
  args.aggregate.maxTokenCount = tokenCount;
29244
+ args.aggregate.tokenCountKnown = true;
28882
29245
  updateTagByteSize(args.db, args.sessionId, args.aggregate.tagId, args.byteSize);
28883
29246
  updateTagTokenCount(args.db, args.sessionId, args.aggregate.tagId, tokenCount);
28884
29247
  syncToolAggregateAccounting(args.tagger, args.sessionId, args.aggregate);
28885
29248
  }
28886
- function readAggregateToolAccounting(part, timing, tokenCache, tokenCacheKey) {
29249
+ function readAggregateToolAccounting(part, timing, tokenCache, tokenCacheKey, persisted) {
28887
29250
  const text = part.getText() ?? "";
28888
29251
  const byteSize = getToolPartByteSize(part, text);
28889
- const metadata = part.getToolMetadata();
29252
+ const metadata = part.getToolMetadata({
29253
+ skipInputTokenCount: persisted?.inputTokenCount != null
29254
+ });
28890
29255
  let tokenCount = 0;
28891
- if (part.kind === "tool_result") {
29256
+ if (part.kind === "tool_result" && (!persisted || persisted.tokenCount === null || byteSize > persisted.byteSize)) {
28892
29257
  const tokenStart = timing ? performance.now() : 0;
28893
29258
  tokenCount = getCachedToolPartTokenCount(part, text, tokenCache, tokenCacheKey);
28894
29259
  if (timing)
@@ -29273,6 +29638,12 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
29273
29638
  return any ? "truncated" : "absent";
29274
29639
  },
29275
29640
  prepareDrop: () => !occurrences.some((occ) => occ.part.canRemove?.() === "defer"),
29641
+ prepareReplayDrop: () => {
29642
+ if (complete() && !requiresToolArcSkeleton) {
29643
+ for (const occ of occurrences)
29644
+ occ.part.canRemove?.();
29645
+ }
29646
+ },
29276
29647
  canDrop: () => complete() && !occurrences.some((occ) => occ.part.hasUserAnswer?.()),
29277
29648
  requiresToolArcSkeleton,
29278
29649
  readInput() {
@@ -29343,6 +29714,38 @@ function buildToolTarget(part, message, tagId) {
29343
29714
  };
29344
29715
  }
29345
29716
 
29717
+ // ../plugin/src/features/magic-context/auto-search-diagnostics.ts
29718
+ var memoryHintContent = new WeakMap;
29719
+ function withMemoryHintContent(result, fullContent) {
29720
+ memoryHintContent.set(result, fullContent);
29721
+ return result;
29722
+ }
29723
+ function createAutoSearchDiagnostics(threshold) {
29724
+ return {
29725
+ threshold,
29726
+ lanes: {},
29727
+ pool: "unknown",
29728
+ poolRows: null,
29729
+ peek: "not-run",
29730
+ workerStarted: false
29731
+ };
29732
+ }
29733
+ function autoSearchRelevance(query, result) {
29734
+ const content = result.source === "memory" ? memoryHintContent.get(result) ?? result.content : result.content;
29735
+ if (cleanSearchText(content).length === 0)
29736
+ return 0;
29737
+ return result.source === "message" || "matchType" in result ? hintKeywordRelevance(query, content) : result.score;
29738
+ }
29739
+ function recordAutoSearchLane(diagnostics, lane, query, results) {
29740
+ if (!diagnostics)
29741
+ return;
29742
+ const scores = results.map((result) => result.relevanceScore ?? autoSearchRelevance(query, result));
29743
+ diagnostics.lanes[lane] = {
29744
+ top: scores.length ? Math.max(...scores) : null,
29745
+ nearMisses: scores.filter((score) => score >= 0.5 && score < diagnostics.threshold).length
29746
+ };
29747
+ }
29748
+
29346
29749
  // ../plugin/src/features/magic-context/literal-probes.ts
29347
29750
  var MAX_PROBES = 5;
29348
29751
  var MIN_PROBE_LENGTH = 3;
@@ -29969,7 +30372,7 @@ function mergeMemoryResults(args) {
29969
30372
  suppressedVisibleIds.push(id);
29970
30373
  continue;
29971
30374
  }
29972
- results.push({
30375
+ results.push(withMemoryHintContent({
29973
30376
  source: "memory",
29974
30377
  content: previewText(memory.content),
29975
30378
  score,
@@ -29977,7 +30380,7 @@ function mergeMemoryResults(args) {
29977
30380
  category: memory.category,
29978
30381
  matchType: "fts",
29979
30382
  sourceName: args.sourceNameByMemoryId?.get(memory.id)
29980
- });
30383
+ }, memory.content));
29981
30384
  }
29982
30385
  return {
29983
30386
  results: results.sort((left, right) => {
@@ -30574,8 +30977,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
30574
30977
  options.diagnostics.suppressedLiveMessageMatches = outcome.suppressedLiveMessageMatches;
30575
30978
  }
30576
30979
  messageResults = outcome.results.filter((result) => result.messageId !== options.excludeMessageId);
30980
+ recordAutoSearchLane(options.autoSearchDiagnostics, "messages", trimmedQuery, messageResults);
30981
+ options.onAutoSearchDiagnostics?.();
30577
30982
  }
30578
30983
  const workspace = resolveSearchWorkspaceContext(db, projectPath);
30984
+ if (options.autoSearchDiagnostics)
30985
+ options.autoSearchDiagnostics.pool = "off";
30579
30986
  const [memoryOutcome, primerResults, noteResults] = await Promise.all([
30580
30987
  runMemory ? searchMemories({
30581
30988
  db,
@@ -30610,6 +31017,10 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
30610
31017
  if (options.diagnostics) {
30611
31018
  options.diagnostics.suppressedVisibleMemoryIds = memoryOutcome.suppressedVisibleIds;
30612
31019
  }
31020
+ recordAutoSearchLane(options.autoSearchDiagnostics, "memory", trimmedQuery, memoryOutcome.results);
31021
+ recordAutoSearchLane(options.autoSearchDiagnostics, "primers", trimmedQuery, primerResults);
31022
+ recordAutoSearchLane(options.autoSearchDiagnostics, "notes", trimmedQuery, noteResults);
31023
+ options.onAutoSearchDiagnostics?.();
30613
31024
  const results = [...memoryOutcome.results, ...primerResults, ...messageResults, ...noteResults].sort(compareUnifiedResults).slice(0, limit);
30614
31025
  const countRetrievals = options.countRetrievals ?? true;
30615
31026
  if (countRetrievals) {
@@ -30631,7 +31042,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
30631
31042
  if (options.autoHint) {
30632
31043
  return results.map((result) => ({
30633
31044
  ...result,
30634
- relevanceScore: cleanSearchText(result.content).length === 0 ? 0 : keywordRelevance(trimmedQuery, result.content)
31045
+ relevanceScore: autoSearchRelevance(trimmedQuery, result)
30635
31046
  }));
30636
31047
  }
30637
31048
  return results;
@@ -30877,6 +31288,32 @@ function selectAutoSearchResults(results, threshold) {
30877
31288
  return results.filter((result) => relevance(result) >= threshold).sort((left, right) => relevance(right) - relevance(left));
30878
31289
  }
30879
31290
 
31291
+ // ../plugin/src/hooks/magic-context/auto-search-observation.ts
31292
+ var disabledTurns = new WeakMap;
31293
+ function logDisabledAutoSearch(db, sessionId, messageId, threshold, startedAt) {
31294
+ const sessions = disabledTurns.get(db) ?? new Map;
31295
+ disabledTurns.set(db, sessions);
31296
+ if (sessions.get(sessionId) === messageId)
31297
+ return;
31298
+ sessions.set(sessionId, messageId);
31299
+ observeAutoSearch(sessionId, threshold, startedAt).finish("disabled");
31300
+ }
31301
+ function observeAutoSearch(sessionId, threshold, startedAt) {
31302
+ const diagnostics = createAutoSearchDiagnostics(threshold);
31303
+ let logged = false;
31304
+ return {
31305
+ diagnostics,
31306
+ finish(outcome, reason) {
31307
+ if (logged)
31308
+ return;
31309
+ logged = true;
31310
+ const lanes = Object.entries(diagnostics.lanes).map(([lane, value]) => `${lane}:${value.top?.toFixed(3) ?? "none"}`).join(",");
31311
+ const nearMisses = Object.values(diagnostics.lanes).reduce((sum, lane) => sum + lane.nearMisses, 0);
31312
+ sessionLog(sessionId, `auto-search: decision outcome=${outcome}${reason ? ` reason=${reason}` : ""} lanes=${lanes || "none"} nearMisses=${nearMisses} threshold=${threshold} pool=${diagnostics.pool} rows=${diagnostics.poolRows ?? "unknown"} workerStarted=${diagnostics.workerStarted ?? "unknown"} peek=${diagnostics.peek} elapsedMs=${Math.round(performance.now() - startedAt)}`);
31313
+ }
31314
+ };
31315
+ }
31316
+
30880
31317
  // src/auto-search-pi.ts
30881
31318
  var DEFAULT_SCORE_THRESHOLD = 0.55;
30882
31319
  var DEFAULT_MIN_PROMPT_CHARS = 20;
@@ -31013,8 +31450,6 @@ async function runAutoSearchHintForPi(args) {
31013
31450
  const timestamp = message.timestamp;
31014
31451
  return `test-entry-${index}:${typeof timestamp === "number" ? timestamp : "no-ts"}`;
31015
31452
  }) : args.entryIds;
31016
- if (!options.enabled)
31017
- return messages;
31018
31453
  const strictResolutionFailed = entryIds === null;
31019
31454
  const effectiveEntryIds = strictResolutionFailed ? messages.map((message) => {
31020
31455
  const id = message.id;
@@ -31024,6 +31459,11 @@ async function runAutoSearchHintForPi(args) {
31024
31459
  if (found === null)
31025
31460
  return messages;
31026
31461
  const { message: userMsg, messageId: userMsgId } = found;
31462
+ if (!options.enabled) {
31463
+ if (found.index === messages.length - 1)
31464
+ logDisabledAutoSearch(db, sessionId, userMsgId, options.scoreThreshold ?? DEFAULT_SCORE_THRESHOLD, startedAt);
31465
+ return messages;
31466
+ }
31027
31467
  const replayServedDecision = () => {
31028
31468
  const decision = getAutoSearchHintDecisions(db, sessionId).find((entry) => entry.messageId === userMsgId);
31029
31469
  if (decision?.decision === "hint") {
@@ -31052,21 +31492,24 @@ async function runAutoSearchHintForPi(args) {
31052
31492
  return messages;
31053
31493
  if (!isUsableProjectIdentity(options.projectPath))
31054
31494
  return messages;
31495
+ const observation = observeAutoSearch(sessionId, options.scoreThreshold ?? DEFAULT_SCORE_THRESHOLD, startedAt);
31055
31496
  const writeNoHintAndReconcile = (reason) => {
31056
31497
  const outcome = appendAutoSearchHintDecision(db, sessionId, {
31057
31498
  messageId: userMsgId,
31058
31499
  decision: "no-hint",
31059
31500
  reason
31060
31501
  });
31061
- if (!outcome.ok)
31502
+ if (!outcome.ok) {
31503
+ observation.finish("error", "cas-exhausted");
31062
31504
  return;
31505
+ }
31063
31506
  if (outcome.kind === "already-present" && outcome.decision.decision === "hint") {
31064
31507
  appendHintToUserMessage(userMsg, outcome.decision.text);
31065
31508
  }
31509
+ observation.finish(outcome.decision.decision === "hint" ? "attached" : reason === "below-threshold" ? "below-threshold" : reason === "error" ? "error" : "no-candidates", reason);
31066
31510
  };
31067
31511
  const rawPartsText = collectUserPromptParts(userMsg);
31068
31512
  if (hasStackedAugmentation(rawPartsText)) {
31069
- sessionLog(sessionId, "auto-search: skipping — user message already carries augmentation/hint");
31070
31513
  writeNoHintAndReconcile("stacked");
31071
31514
  return messages;
31072
31515
  }
@@ -31079,6 +31522,7 @@ async function runAutoSearchHintForPi(args) {
31079
31522
  let results;
31080
31523
  try {
31081
31524
  const searchOptions = {
31525
+ autoSearchDiagnostics: observation.diagnostics,
31082
31526
  limit: 10,
31083
31527
  autoHint: true,
31084
31528
  maxMessageOrdinal: Math.max(0, getLastCompartmentEndMessage(db, sessionId)),
@@ -31094,13 +31538,13 @@ async function runAutoSearchHintForPi(args) {
31094
31538
  countRetrievals: false
31095
31539
  });
31096
31540
  }, startedAt);
31097
- } catch (error) {
31098
- log(`[auto-search] unified search failed for session ${sessionId} (will retry next pass): ${error instanceof Error ? error.message : String(error)}`);
31541
+ } catch {
31542
+ observation.finish("error");
31099
31543
  return messages;
31100
31544
  }
31101
31545
  if (results === null) {
31102
31546
  persistAutoSearchSkip(db, sessionId, userMsgId, "timeout");
31103
- sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn (frozen)`);
31547
+ observation.finish("timeout");
31104
31548
  return messages;
31105
31549
  }
31106
31550
  if (results.length === 0) {
@@ -31110,7 +31554,6 @@ async function runAutoSearchHintForPi(args) {
31110
31554
  const scoreThreshold = options.scoreThreshold ?? DEFAULT_SCORE_THRESHOLD;
31111
31555
  results = selectAutoSearchResults(results, scoreThreshold);
31112
31556
  if (results.length === 0) {
31113
- sessionLog(sessionId, `auto-search: no fragment meets relevance threshold ${scoreThreshold}`);
31114
31557
  writeNoHintAndReconcile("below-threshold");
31115
31558
  return messages;
31116
31559
  }
@@ -31130,12 +31573,14 @@ ${hintText}`;
31130
31573
  decision: "hint",
31131
31574
  text: payload
31132
31575
  });
31133
- if (!outcome.ok)
31576
+ if (!outcome.ok) {
31577
+ observation.finish("error", "cas-exhausted");
31134
31578
  return messages;
31579
+ }
31135
31580
  if (outcome.decision.decision === "hint") {
31136
31581
  appendHintToUserMessage(userMsg, outcome.decision.text);
31137
31582
  }
31138
- sessionLog(sessionId, `auto-search: attached hint to ${userMsgId} (${results.length} fragments, top score ${results[0].score.toFixed(3)})`);
31583
+ observation.finish(outcome.decision.decision === "hint" ? "attached" : "no-candidates");
31139
31584
  return messages;
31140
31585
  }, replayServedDecision));
31141
31586
  }
@@ -35713,6 +36158,14 @@ async function runPiHistorian(deps) {
35713
36158
  return;
35714
36159
  }
35715
36160
  const protectedTailStart = boundarySnapshot.protectedTailStart;
36161
+ if (!forceDrainQuota && shouldWaitForNewHistorianHistory(boundarySnapshot, {
36162
+ emergency: getOverflowState(db, sessionId).needsEmergencyRecovery
36163
+ })) {
36164
+ telemetry.status = "noop";
36165
+ telemetry.failureReason = "waiting for new history after no forward progress";
36166
+ sessionLog(sessionId, "historian no-op: waiting for new history after no forward progress");
36167
+ return;
36168
+ }
35716
36169
  const eligibleEndOrdinal = Math.min(boundarySnapshot.eligibleEndOrdinal, protectedTailStart);
35717
36170
  if (protectedTailStart <= offset || eligibleEndOrdinal <= offset) {
35718
36171
  telemetry.status = "noop";
@@ -35900,6 +36353,12 @@ ${chunkText}`,
35900
36353
  };
35901
36354
  retainDrainReservationForRetryThrottle = true;
35902
36355
  const historianEditorSystemPrompt = withContentLanguageDirective(HISTORIAN_EDITOR_SYSTEM_PROMPT, deps.language, { preserveUserQuotes: true });
36356
+ if (!forceDrainQuota) {
36357
+ shouldWaitForNewHistorianHistory(boundarySnapshot, {
36358
+ emergency: getOverflowState(db, sessionId).needsEmergencyRecovery,
36359
+ claim: true
36360
+ });
36361
+ }
35903
36362
  const firstResult = await runHistorianSubagentWithTransientRetries({
35904
36363
  runner,
35905
36364
  sessionId,
@@ -36058,6 +36517,7 @@ ${chunkText}`,
36058
36517
  const failCount = incrementHistorianFailure(db, sessionId, errorMsg);
36059
36518
  await notify(buildHistorianFailureNotice(failCount, errorMsg));
36060
36519
  }
36520
+ recordHistorianNoProgress(boundarySnapshot, inEmergency);
36061
36521
  rollbackDrainReservation();
36062
36522
  return;
36063
36523
  }
@@ -36151,6 +36611,7 @@ ${chunkText}`,
36151
36611
  }
36152
36612
  db.exec("COMMIT");
36153
36613
  published = true;
36614
+ clearHistorianHistoryWait(sessionId);
36154
36615
  logSlowWriteTransaction("pi_historian_publish", transactionStartedAt);
36155
36616
  } finally {
36156
36617
  if (!published) {
@@ -36873,7 +37334,8 @@ function tagAttribution(input) {
36873
37334
  if (!cached) {
36874
37335
  removed.length = 0;
36875
37336
  added.length = 0;
36876
- added.push(...input.tags);
37337
+ for (const tag of input.tags)
37338
+ added.push(tag);
36877
37339
  cached = {
36878
37340
  tags: input.tags,
36879
37341
  messageTags: new Map,
@@ -37726,7 +38188,11 @@ function mergeChangedRows(rows, dirty, replacements) {
37726
38188
  }
37727
38189
  merged.push(tag);
37728
38190
  }
37729
- merged.push(...replacements.slice(index));
38191
+ for (;index < replacements.length; index += 1) {
38192
+ const row = replacements[index];
38193
+ if (row)
38194
+ merged.push(row);
38195
+ }
37730
38196
  return merged;
37731
38197
  }
37732
38198
  var entryReaders = new WeakMap;
@@ -39853,6 +40319,26 @@ async function executeContextRecompWithResult(deps, options = {}) {
39853
40319
  }
39854
40320
 
39855
40321
  // ../plugin/src/hooks/magic-context/history-boundary-repair.ts
40322
+ function readStoreBoundaries(db, sessionId) {
40323
+ const columns = getTableColumnNames(db, "compartments");
40324
+ const numbers = ["id", "sequence", "start_message", "end_message", "created_at"];
40325
+ const strings = ["session_id", "start_message_id", "end_message_id", "title", "content"];
40326
+ const predicates = [...numbers, ...strings].every((column) => columns.has(column)) ? [
40327
+ ...numbers.map((column) => `typeof(${column}) IN ('integer', 'real')`),
40328
+ ...strings.map((column) => `typeof(${column}) = 'text'`)
40329
+ ] : ["0"];
40330
+ for (const column of ["p1", "p2", "p3", "p4", "episode_type"]) {
40331
+ if (columns.has(column))
40332
+ predicates.push(`typeof(${column}) IN ('text', 'null')`);
40333
+ }
40334
+ for (const column of ["importance", "legacy"]) {
40335
+ if (columns.has(column))
40336
+ predicates.push(`typeof(${column}) IN ('integer', 'real', 'null')`);
40337
+ }
40338
+ return db.prepare(`SELECT sequence, ${columns.has("end_message_id") ? "end_message_id" : "NULL"} AS endMessageId
40339
+ FROM compartments WHERE session_id = ? AND ${predicates.join(" AND ")}
40340
+ ORDER BY sequence ASC`).all(sessionId);
40341
+ }
39856
40342
  function readBaseline(db, sessionId) {
39857
40343
  const row = db.prepare("SELECT cached_m0_bytes IS NOT NULL AS hasM0, cached_m0_last_baseline_end_message_id AS boundary FROM session_meta WHERE session_id = ?").get(sessionId);
39858
40344
  return {
@@ -39918,7 +40404,7 @@ function dropCompartmentsAfter(db, sessionId, anchor, holderId, activeBranch = f
39918
40404
  }
39919
40405
  function repairMissingHistoryBoundary(args) {
39920
40406
  const { db, sessionId, isInHostStore, scope = "store" } = args;
39921
- const compartments = scope === "active-branch" ? db.prepare("SELECT sequence, COALESCE(end_message_id, '') AS endMessageId FROM compartments WHERE session_id = ? ORDER BY sequence").all(sessionId) : getCompartments(db, sessionId);
40407
+ const compartments = scope === "active-branch" ? db.prepare("SELECT sequence, COALESCE(end_message_id, '') AS endMessageId FROM compartments WHERE session_id = ? ORDER BY sequence").all(sessionId) : readStoreBoundaries(db, sessionId);
39922
40408
  let latestIndex = compartments.length - 1;
39923
40409
  while (latestIndex >= 0 && !compartments[latestIndex]?.endMessageId)
39924
40410
  latestIndex -= 1;
@@ -39962,7 +40448,7 @@ function repairMissingHistoryBoundary(args) {
39962
40448
  let dropped;
39963
40449
  let anchorForDrop = anchor;
39964
40450
  try {
39965
- if (scope === "active-branch" && anchor) {
40451
+ if (anchor) {
39966
40452
  const row = db.prepare("SELECT end_message AS endMessage FROM compartments WHERE session_id = ? AND sequence = ?").get(sessionId, anchor.sequence);
39967
40453
  if (!row)
39968
40454
  return { kind: "unresolved", reason: "busy", missingEndMessageId: missing };
@@ -41533,6 +42019,79 @@ function canRemoveNativeToolCall(message, toolCallId) {
41533
42019
  var NATIVE_TOOL_REMOVAL_MARKER = '{"__magic_context_remove_tool_arc__":true}';
41534
42020
 
41535
42021
  // src/native-replay-state-pi.ts
42022
+ var removalBatches = new WeakMap;
42023
+
42024
+ class PiToolRemovalBatchError extends Error {
42025
+ constructor(cause) {
42026
+ super("Native tool removal batch could not be persisted", { cause });
42027
+ }
42028
+ }
42029
+ function isTransactionOpen(db) {
42030
+ const connection = db;
42031
+ return connection.inTransaction === true || connection.isTransaction === true;
42032
+ }
42033
+ function withPiToolRemovalBatch(args, edit) {
42034
+ let sessions = removalBatches.get(args.db);
42035
+ const enclosing = sessions?.get(args.sessionId);
42036
+ if (enclosing)
42037
+ return edit(enclosing.flush);
42038
+ if (!sessions) {
42039
+ sessions = new Map;
42040
+ removalBatches.set(args.db, sessions);
42041
+ }
42042
+ let publishing = false;
42043
+ const batch = {
42044
+ saved: args.saved,
42045
+ planned: new Map(args.saved),
42046
+ requested: new Map,
42047
+ pending: new Map,
42048
+ flushed: false,
42049
+ flush() {
42050
+ if (batch.flushed)
42051
+ return;
42052
+ batch.persistPending();
42053
+ batch.flushed = true;
42054
+ },
42055
+ persistPending() {
42056
+ if (publishing || batch.pending.size === 0)
42057
+ return;
42058
+ if (!isTransactionOpen(args.db))
42059
+ throw new Error("Native removal publication requires a caller-owned transaction");
42060
+ try {
42061
+ publishing = true;
42062
+ saveNativeToolInputs(args.db, args.sessionId, batch.pending);
42063
+ batch.pending.clear();
42064
+ } catch (cause) {
42065
+ throw new PiToolRemovalBatchError(cause);
42066
+ } finally {
42067
+ publishing = false;
42068
+ }
42069
+ }
42070
+ };
42071
+ sessions.set(args.sessionId, batch);
42072
+ try {
42073
+ const result = withNativeReplayWriteBarrier(args.db, args.sessionId, batch.persistPending, () => edit(batch.flush));
42074
+ if (batch.requested.size > 0 && !batch.flushed)
42075
+ throw new Error("Native removal markers were not flushed in the caller's transaction");
42076
+ const saved = args.saved;
42077
+ if (saved && batch.requested.size > 0) {
42078
+ const prior = new Map([...batch.requested.keys()].map((id) => [id, saved.get(id)]));
42079
+ args.rollbackObservations?.push(() => {
42080
+ for (const [id, value] of prior) {
42081
+ if (value === undefined)
42082
+ saved.delete(id);
42083
+ else
42084
+ saved.set(id, value);
42085
+ }
42086
+ });
42087
+ for (const [id, marker] of batch.requested)
42088
+ saved.set(id, marker);
42089
+ }
42090
+ return result;
42091
+ } finally {
42092
+ sessions.delete(args.sessionId);
42093
+ }
42094
+ }
41536
42095
  function applyNativeToolInputReplayPi(args, saved) {
41537
42096
  if (saved.size === 0 && (!args.canApply || args.changes.size === 0))
41538
42097
  return 0;
@@ -41644,6 +42203,12 @@ function applyNativeReasoningReplayPi(args, saved) {
41644
42203
  function preparePiToolRemovalMeasurements(args) {
41645
42204
  if (!args.saved || !args.canApply)
41646
42205
  return;
42206
+ const batch = removalBatches.get(args.db)?.get(args.sessionId);
42207
+ if (batch?.saved === args.saved) {
42208
+ for (const callId of args.callIds)
42209
+ authorizePiToolRemoval({ ...args, callId });
42210
+ return;
42211
+ }
41647
42212
  const requested = new Map;
41648
42213
  for (const callId of args.callIds) {
41649
42214
  if (args.saved.get(callId) !== NATIVE_TOOL_REMOVAL_MARKER)
@@ -41659,9 +42224,54 @@ function preparePiToolRemovalMeasurements(args) {
41659
42224
  args.saved.set(callId, marker);
41660
42225
  } catch {}
41661
42226
  }
42227
+ function preparePiToolRemovalChecks(args) {
42228
+ if (!args.db || !isTransactionOpen(args.db) || args.rows.length === 0)
42229
+ return;
42230
+ const occurrences = new Map;
42231
+ for (const raw of args.messages) {
42232
+ if (!raw || typeof raw !== "object")
42233
+ continue;
42234
+ const message = raw;
42235
+ if (message.role !== "assistant" || !Array.isArray(message.content))
42236
+ continue;
42237
+ for (const rawPart of message.content) {
42238
+ if (!rawPart || typeof rawPart !== "object")
42239
+ continue;
42240
+ const part = rawPart;
42241
+ if (part.type === "toolCall" && typeof part.id === "string")
42242
+ occurrences.set(part.id, (occurrences.get(part.id) ?? 0) + 1);
42243
+ }
42244
+ }
42245
+ const requested = [];
42246
+ for (const row of args.rows) {
42247
+ if (row.callId === null || occurrences.get(row.callId) !== 1)
42248
+ continue;
42249
+ const target = args.targets.get(row.tagNumber);
42250
+ if (target?.requiresToolArcSkeleton || target?.canDrop?.() !== true)
42251
+ continue;
42252
+ if (!target.mutationParts?.some(({ part }) => part !== null && typeof part === "object" && ("kind" in part) && part.kind === "tool_use" && ("id" in part) && part.id === row.callId))
42253
+ continue;
42254
+ requested.push(row.callId);
42255
+ }
42256
+ if (requested.length)
42257
+ args.prepare(requested);
42258
+ }
41662
42259
  function authorizePiToolRemoval(args) {
41663
42260
  if (!args.saved)
41664
42261
  return false;
42262
+ const batch = removalBatches.get(args.db)?.get(args.sessionId);
42263
+ if (batch?.saved === args.saved) {
42264
+ if (batch.planned.get(args.callId) === NATIVE_TOOL_REMOVAL_MARKER)
42265
+ return true;
42266
+ if (!args.canApply)
42267
+ return false;
42268
+ if (batch.flushed)
42269
+ throw new Error("Tool removal requested after lane publication");
42270
+ batch.planned.set(args.callId, NATIVE_TOOL_REMOVAL_MARKER);
42271
+ batch.requested.set(args.callId, NATIVE_TOOL_REMOVAL_MARKER);
42272
+ batch.pending.set(args.callId, NATIVE_TOOL_REMOVAL_MARKER);
42273
+ return true;
42274
+ }
41665
42275
  if (args.saved.get(args.callId) === NATIVE_TOOL_REMOVAL_MARKER)
41666
42276
  return true;
41667
42277
  if (!args.canApply)
@@ -41986,16 +42596,6 @@ function registerPiGuardedContext(pi, handler, options = {}) {
41986
42596
  refused: false,
41987
42597
  ended: false
41988
42598
  });
41989
- const lifecycle = new Map;
41990
- receiptMaps.add(lifecycle);
41991
- const lifecycleCounts = (key) => {
41992
- let counts = lifecycle.get(key);
41993
- if (!counts) {
41994
- counts = { starts: 0, ends: 0 };
41995
- lifecycle.set(key, counts);
41996
- }
41997
- return counts;
41998
- };
41999
42599
  const fenceEnabled = resolvePiHarnessKind() === "omp";
42000
42600
  const unsendableNonces = new Map;
42001
42601
  receiptMaps.add(unsendableNonces);
@@ -48029,7 +48629,7 @@ function createPiAssistantPart(working, messageIndex, partIndex, markDirty) {
48029
48629
  setToolOutput() {
48030
48630
  throw new Error("setToolOutput on assistant part");
48031
48631
  },
48032
- getToolMetadata() {
48632
+ getToolMetadata(options) {
48033
48633
  const current = working[messageIndex].content;
48034
48634
  const p = current[partIndex];
48035
48635
  if (p?.type !== "toolCall") {
@@ -48040,7 +48640,7 @@ function createPiAssistantPart(working, messageIndex, partIndex, markDirty) {
48040
48640
  try {
48041
48641
  const serialized = JSON.stringify(p.arguments);
48042
48642
  inputByteSize = serialized.length;
48043
- inputTokenCount = serialized ? estimateTokens(serialized) : 0;
48643
+ inputTokenCount = serialized && !options?.skipInputTokenCount ? estimateTokens(serialized) : 0;
48044
48644
  } catch {
48045
48645
  inputByteSize = 0;
48046
48646
  inputTokenCount = 0;
@@ -48473,6 +49073,7 @@ function observePipelineResultForTests(sessionId, result) {
48473
49073
  }
48474
49074
  var variantChangeLogObserverForTests;
48475
49075
  var beforePipelineForTests;
49076
+ var thinkingFitInputObserverForTests;
48476
49077
  function logPiLkgRecovery(sessionId, message) {
48477
49078
  lkgRecoveryLogObserverForTests?.(message);
48478
49079
  sessionLog(sessionId, message);
@@ -48603,6 +49204,7 @@ function buildPiTextIdentityPlan(db, sessionId, tagger, transcript, reuseCandida
48603
49204
  sourceCache.set(tagId, source);
48604
49205
  }
48605
49206
  const driftedMessageIds = new Set;
49207
+ const reusableTextMessageIds = new Set;
48606
49208
  const legacyReminderTagNumbers = new Set;
48607
49209
  for (const [messageId, currentSources] of currentSourcesByMessageId) {
48608
49210
  const legacyRows = legacyRowsByMessageId.get(messageId) ?? [];
@@ -48630,6 +49232,8 @@ function buildPiTextIdentityPlan(db, sessionId, tagger, transcript, reuseCandida
48630
49232
  });
48631
49233
  if (!vectorMatches)
48632
49234
  driftedMessageIds.add(messageId);
49235
+ else if (!messageId.startsWith("pi-msg-") && legacyRows.every((row) => sourceCache.has(row.tagId) && !legacyReminderTagNumbers.has(row.tagId)))
49236
+ reusableTextMessageIds.add(messageId);
48633
49237
  }
48634
49238
  const reusableMessageIds = new Set;
48635
49239
  for (const messageId of reuseCandidates) {
@@ -48640,7 +49244,8 @@ function buildPiTextIdentityPlan(db, sessionId, tagger, transcript, reuseCandida
48640
49244
  driftedMessageIds,
48641
49245
  legacyReminderTagNumbers,
48642
49246
  reusableMessageIds,
48643
- sourceCache
49247
+ sourceCache,
49248
+ reusableTextMessageIds
48644
49249
  };
48645
49250
  }
48646
49251
  var piBranchProjectionBySession = new Map;
@@ -50665,15 +51270,7 @@ function registerPiContextHandler(pi, baseOptions) {
50665
51270
  contextLimit: rawFallbackLimit,
50666
51271
  forcePercentage: forceMaterializationPercentage
50667
51272
  });
50668
- let inputForThinkingFit = (getHarness() !== "pi" || nativeFitPossible) && isPrefixBoundThinkingModel(ctx.model?.provider, ctx.model?.id) ? structuredClone(event.messages) : null;
50669
- let inputForFitReclaim = nativeFitPossible ? inputForThinkingFit ?? structuredClone(event.messages) : undefined;
50670
- if (fitInputStart !== undefined)
50671
- recordPiTransformTiming({
50672
- sessionId,
50673
- stage: "fitInputPreparation",
50674
- elapsedMs: performance.now() - fitInputStart,
50675
- extra: inputForFitReclaim ? "snapshot" : "no-snapshot"
50676
- });
51273
+ const fitPredictionMs = fitInputStart === undefined ? undefined : performance.now() - fitInputStart;
50677
51274
  const pipelineArgs = {
50678
51275
  recoveryPrefix: recovery.prefix,
50679
51276
  recoveryRebuildReason: recovery.rebuildReason,
@@ -50765,7 +51362,25 @@ function registerPiContextHandler(pi, baseOptions) {
50765
51362
  throw new PiLkgAdmissionReplay(replay.messages, "held tag-identity rebuild: replaying the served prefix", "held_identity_repair");
50766
51363
  }
50767
51364
  };
51365
+ const fitCopyStart = fitPredictionMs === undefined ? undefined : performance.now();
51366
+ const thinkingFitHeld = hasActiveAnthropicThinkingTurn(pipelineArgs.messages, pipelineArgs.resolvedProviderID, pipelineArgs.resolvedModelID) && pipelineArgs.reasoningClearing?.prefixBound === true;
51367
+ let inputForThinkingFit = !pipelineArgs.compactionOff && (getHarness() !== "pi" || nativeFitPossible) && isPrefixBoundThinkingModel(ctx.model?.provider, ctx.model?.id) && thinkingFitHeld ? structuredClone(event.messages) : null;
51368
+ let inputForFitReclaim = nativeFitPossible ? inputForThinkingFit ?? structuredClone(event.messages) : undefined;
51369
+ if (fitPredictionMs !== undefined && fitCopyStart !== undefined)
51370
+ recordPiTransformTiming({
51371
+ sessionId,
51372
+ stage: "fitInputPreparation",
51373
+ elapsedMs: fitPredictionMs + (performance.now() - fitCopyStart),
51374
+ extra: inputForFitReclaim ? "snapshot" : "no-snapshot"
51375
+ });
50768
51376
  let result = await budget.waitMandatory(runPipeline(pipelineArgs));
51377
+ thinkingFitInputObserverForTests?.({
51378
+ sessionId,
51379
+ held: thinkingFitHeld,
51380
+ strictThinkingKept: result.strictThinkingKept,
51381
+ thinkingCopy: inputForThinkingFit,
51382
+ fitReclaimCopy: inputForFitReclaim
51383
+ });
50769
51384
  observePipelineResultForTests(sessionId, result);
50770
51385
  logTransformTiming(sessionId, "runPipeline", tRunPipeline);
50771
51386
  if (result.identityRebuilt || result.lkgFrozenReleaseReason)
@@ -52350,6 +52965,21 @@ async function runPipeline(args) {
52350
52965
  try {
52351
52966
  nativeRemovalInputs = getNativeReplayState(args.db, args.sessionId).toolInputs;
52352
52967
  } catch {}
52968
+ const removalBatch = (edit) => withPiToolRemovalBatch({ db: args.db, sessionId: args.sessionId, saved: nativeRemovalInputs }, edit);
52969
+ const applyToolRemovalGroup = (edit) => {
52970
+ const savedRemovalInputs = nativeRemovalInputs;
52971
+ nativeRemovalInputs = savedRemovalInputs && new Map(savedRemovalInputs);
52972
+ try {
52973
+ return args.db.transaction(() => removalBatch((flush) => {
52974
+ const result = edit();
52975
+ flush();
52976
+ return result;
52977
+ })).immediate();
52978
+ } catch (cause) {
52979
+ nativeRemovalInputs = savedRemovalInputs;
52980
+ throw cause instanceof PiToolRemovalBatchError ? cause : new PiToolRemovalBatchError(cause);
52981
+ }
52982
+ };
52353
52983
  const tTranscriptBuild = performance.now();
52354
52984
  const transcript = createPiTranscript(args.messages, args.sessionId, args.messages.map(stableIdResolver), {
52355
52985
  preserveReasoningToolArcs: args.reasoningClearing?.preserveReasoningToolArcs,
@@ -52624,6 +53254,7 @@ async function runPipeline(args) {
52624
53254
  skipPrefixInjection: !ctxReduceCallable,
52625
53255
  entryFingerprintByMessageId,
52626
53256
  reuseMessageIds: textIdentityPlan.reusableMessageIds,
53257
+ reuseTextMessageIds: textIdentityPlan.reusableTextMessageIds,
52627
53258
  textIdentityDriftMessageIds: textIdentityPlan.driftedMessageIds,
52628
53259
  textIdentitySourceCache: textIdentityPlan.sourceCache,
52629
53260
  textTokenCache: tagTextTokenCache,
@@ -52705,17 +53336,34 @@ async function runPipeline(args) {
52705
53336
  });
52706
53337
  }
52707
53338
  if (foldBustsServedPrefixThisPass) {
53339
+ const savedRemovalInputs = nativeRemovalInputs;
52708
53340
  try {
53341
+ nativeRemovalInputs = savedRemovalInputs && new Map(savedRemovalInputs);
52709
53342
  let converted = 0;
52710
- args.db.transaction(() => {
53343
+ args.db.transaction(() => removalBatch((flush) => {
52711
53344
  converted = convertLegacyToolSkeletons(args.db, args.sessionId, targets, {
52712
- keepFullDrop: (callId) => nativeRemovalInputs === undefined || callId !== null && nativeRemovalInputs.get(callId) === NATIVE_TOOL_REMOVAL_MARKER
53345
+ prepareRemovals: (rows) => preparePiToolRemovalChecks({
53346
+ db: args.db,
53347
+ rows,
53348
+ targets,
53349
+ messages: args.messages,
53350
+ prepare: transcript.prepareToolRemovalMeasurements
53351
+ }),
53352
+ keepFullDrop: (callId) => nativeRemovalInputs === undefined || callId !== null && authorizePiToolRemoval({
53353
+ db: args.db,
53354
+ sessionId: args.sessionId,
53355
+ callId,
53356
+ saved: nativeRemovalInputs,
53357
+ canApply: false
53358
+ }) === true
52713
53359
  }).size;
52714
- }).immediate();
53360
+ flush();
53361
+ })).immediate();
52715
53362
  if (converted > 0) {
52716
53363
  sessionLog(args.sessionId, `pi HARD fold converted ${converted} legacy dropped-tool skeleton(s) to real-or-absent`);
52717
53364
  }
52718
53365
  } catch (error) {
53366
+ nativeRemovalInputs = savedRemovalInputs;
52719
53367
  sessionLog(args.sessionId, `pi legacy dropped-tool skeleton conversion failed (kept legacy bytes): ${error instanceof Error ? error.message : String(error)}`);
52720
53368
  }
52721
53369
  }
@@ -52749,13 +53397,13 @@ async function runPipeline(args) {
52749
53397
  pendingDecisionLogObserverForTests?.(pendingDecisionLog);
52750
53398
  try {
52751
53399
  const tApplyPending = performance.now();
52752
- pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, newTargets, new Set([
53400
+ pendingOpsDidMutate = applyToolRemovalGroup(() => applyPendingOperations(args.sessionId, args.db, newTargets, new Set([
52753
53401
  ...args.contextUsage.percentage >= 95 ? [] : protectedTagNumbersForPass,
52754
53402
  ...protectedToolTags,
52755
53403
  ...identityAdoption.protectedTagNumbers
52756
53404
  ]), pendingOperationTags, pendingOps, [], new Set, (reduction) => droppedTokenReductions.push(reduction), undefined, (eligible) => {
52757
53405
  eligiblePendingDropTagIds = eligible;
52758
- });
53406
+ }));
52759
53407
  if (pendingOpsDidMutate) {
52760
53408
  droppedCount += pendingOps.length;
52761
53409
  }
@@ -52785,7 +53433,26 @@ async function runPipeline(args) {
52785
53433
  const flushedDroppedTags = getDroppedTagsByNumbers(args.db, args.sessionId, targetTagNumbers);
52786
53434
  logTransformTiming(args.sessionId, "getDroppedTagsByNumbers", tGetTags, `targets=${targetTagNumbers.length} fetched=${flushedDroppedTags.length}`);
52787
53435
  const tFlushed = performance.now();
52788
- runPersistedReplayStage("flushed-status-failure", undefined, () => applyFlushedStatuses(args.sessionId, args.db, targets, flushedDroppedTags));
53436
+ runPersistedReplayStage("flushed-status-failure", undefined, () => {
53437
+ const render = () => applyFlushedStatuses(args.sessionId, args.db, targets, flushedDroppedTags);
53438
+ const savedRemovalInputs = nativeRemovalInputs;
53439
+ const needsRemovalPreparation = savedRemovalInputs !== undefined && flushedDroppedTags.some((tag) => tag.type === "tool" && tag.dropMode === "full" && savedRemovalInputs.get(tag.messageId) !== NATIVE_TOOL_REMOVAL_MARKER);
53440
+ if (!isCacheBustingPass || !needsRemovalPreparation)
53441
+ return render();
53442
+ try {
53443
+ nativeRemovalInputs = savedRemovalInputs && new Map(savedRemovalInputs);
53444
+ return args.db.transaction(() => removalBatch((flush) => {
53445
+ prepareFlushedToolRemovals(targets, flushedDroppedTags);
53446
+ flush();
53447
+ return render();
53448
+ })).immediate();
53449
+ } catch (error) {
53450
+ nativeRemovalInputs = savedRemovalInputs;
53451
+ if (error instanceof PiToolRemovalBatchError)
53452
+ return render();
53453
+ throw error;
53454
+ }
53455
+ });
52789
53456
  logTransformTiming(args.sessionId, "applyFlushedStatuses", tFlushed);
52790
53457
  logTransformTiming(args.sessionId, "batchFinalize:flushed", tFlushed);
52791
53458
  const messageIdToMaxTag = buildMessageIdToMaxTag(targets);
@@ -52847,6 +53514,7 @@ async function runPipeline(args) {
52847
53514
  pendingDecisionLogObserverForTests?.(heuristicsDecisionLog);
52848
53515
  }
52849
53516
  if (shouldRunHeuristics && args.heuristics) {
53517
+ const heuristics = args.heuristics;
52850
53518
  try {
52851
53519
  const tHeuristic = performance.now();
52852
53520
  const independentMutationBeforeHeuristics = pendingOpsDidMutate || systemPrefixChangedThisPass || foldBustsServedPrefixThisPass || identityRebuild || firstRenderBust || heldIdleExpiryRebuild;
@@ -52854,7 +53522,7 @@ async function runPipeline(args) {
52854
53522
  clearEmergencyDropSample(args.db, args.sessionId);
52855
53523
  }
52856
53524
  routineCleanupApplied = (emergencyDropEligible ? args.contextUsage.percentage >= 95 || getEmergencyInputSample(args.db, args.sessionId) === 0 : args.sessionMeta.isSubagent || !routinePressureAlreadyApplied) || hasPendingMaterializeSignal || rideSignals.explicitFlush || deferredMaterialize || independentMutationBeforeHeuristics;
52857
- heuristicsResult = applyPiHeuristicCleanup(args.sessionId, args.db, newTargets, args.messages, {
53525
+ heuristicsResult = applyToolRemovalGroup(() => applyPiHeuristicCleanup(args.sessionId, args.db, newTargets, args.messages, {
52858
53526
  protectedTools: args.protectedTools,
52859
53527
  prepareToolRemovalMeasurements: transcript.prepareToolRemovalMeasurements,
52860
53528
  protectedToolTags,
@@ -52869,10 +53537,10 @@ async function runPipeline(args) {
52869
53537
  usagePercentage: args.contextUsage.percentage,
52870
53538
  passAlreadyPriced: independentMutationBeforeHeuristics
52871
53539
  } : undefined,
52872
- caveman: args.isSubagent ? undefined : args.heuristics.caveman
52873
- }, activeTags, stableIdResolver);
53540
+ caveman: args.isSubagent ? undefined : heuristics.caveman
53541
+ }, activeTags, stableIdResolver));
52874
53542
  if (!routineCleanupApplied && heuristicsResult.emergencyDroppedTools > 0) {
52875
- const ridingCleanup = applyPiHeuristicCleanup(args.sessionId, args.db, newTargets, args.messages, {
53543
+ const ridingCleanup = applyToolRemovalGroup(() => applyPiHeuristicCleanup(args.sessionId, args.db, newTargets, args.messages, {
52876
53544
  protectedTools: args.protectedTools,
52877
53545
  protectedToolTags,
52878
53546
  protectedTags: args.protectedTags,
@@ -52880,8 +53548,8 @@ async function runPipeline(args) {
52880
53548
  routine: true,
52881
53549
  staleReduceStripEnabled: true,
52882
53550
  onStaleReduceEdit: recordStaleReduceEdit,
52883
- caveman: args.isSubagent ? undefined : args.heuristics.caveman
52884
- }, getActiveTagsBySession(args.db, args.sessionId), stableIdResolver);
53551
+ caveman: args.isSubagent ? undefined : heuristics.caveman
53552
+ }, getActiveTagsBySession(args.db, args.sessionId), stableIdResolver));
52885
53553
  heuristicsResult = {
52886
53554
  droppedTools: heuristicsResult.droppedTools + ridingCleanup.droppedTools,
52887
53555
  deduplicatedTools: heuristicsResult.deduplicatedTools + ridingCleanup.deduplicatedTools,
@@ -52920,6 +53588,8 @@ async function runPipeline(args) {
52920
53588
  }
52921
53589
  logTransformTiming(args.sessionId, "applyHeuristicCleanup", tHeuristic, `droppedTools=${heuristicsResult.droppedTools} deduplicatedTools=${heuristicsResult.deduplicatedTools} droppedInjections=${heuristicsResult.droppedInjections} staleReduce=${heuristicsResult.droppedStaleReduceCalls} compressedTextTags=${heuristicsResult.compressedTextTags} mutatedTextTags=${heuristicsResult.mutatedTextTags}`);
52922
53590
  } catch (err) {
53591
+ if (err instanceof PiToolRemovalBatchError)
53592
+ throw err;
52923
53593
  sessionLog(args.sessionId, `heuristic cleanup failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
52924
53594
  }
52925
53595
  }
@@ -53056,7 +53726,7 @@ async function runPipeline(args) {
53056
53726
  }
53057
53727
  autoReclaimTargetCount = syntheticPendingOps.length;
53058
53728
  if (syntheticPendingOps.length > 0) {
53059
- autoReclaimDidMutate = applyPendingOperations(args.sessionId, args.db, newTargets, protectedTagNumbersForPass, undefined, [], syntheticPendingOps, editMarkerTagIds, (reduction) => droppedTokenReductions.push(reduction));
53729
+ autoReclaimDidMutate = applyToolRemovalGroup(() => applyPendingOperations(args.sessionId, args.db, newTargets, protectedTagNumbersForPass, undefined, [], syntheticPendingOps, editMarkerTagIds, (reduction) => droppedTokenReductions.push(reduction)));
53060
53730
  if (autoReclaimDidMutate) {
53061
53731
  droppedCount += syntheticPendingOps.length;
53062
53732
  autoReclaimDidMutateThisPass = true;
@@ -58898,18 +59568,18 @@ var FULL_PARAMETER_DESCRIPTIONS = {
58898
59568
  message: "Message ordinal from a <session-history> heading or a mmc_search hit, not a tag number. Returns that one message in full. Use alone."
58899
59569
  },
58900
59570
  mmc_note: {
58901
- action: "write | read | update | dismiss. Defaults to write when content is given, else read.",
59571
+ action: "write | read | update | dismiss.",
58902
59572
  content: "Note text for write/update: first line is the title (under 80 chars), then the detail.",
58903
59573
  filter: "Read filter: active (default), all, dismissed.",
58904
59574
  limit: "Rows per read (default 25).",
58905
59575
  offset: "Skip this many newest rows (default 0).",
58906
- note_ids: "Note ids: one for update, 1–50 for dismiss or read (read returns full bodies). Ignored by write."
59576
+ note_ids: "Note ids: one for update, 1–50 for dismiss, or 1–50 to read those notes in full. Write and a plain read take an empty list."
58907
59577
  },
58908
59578
  mmc_memory: {
58909
59579
  action: "write | update | archive | merge | get",
58910
- content: "The memory text — one standalone fact (write, update, merge).",
58911
- category: "Kind of fact (required for write; on update/merge optional, omitted keeps the current category).",
58912
- ids: "Memory ids from <project-memory>: one for update, one or more for archive, two or more for merge, 1–20 for get.",
59580
+ content: "The memory text — one standalone fact (write, update, merge). Archive and get take an empty string.",
59581
+ category: "Kind of fact: the new fact's kind for write; on update/merge, the memory's current category to keep it. Archive and get ignore it.",
59582
+ ids: "Memory ids from <project-memory>: one for update, one or more for archive, two or more for merge, 1–20 for get. Write takes an empty list.",
58913
59583
  reason: "Why it is being archived (optional)."
58914
59584
  },
58915
59585
  mmc_search: {
@@ -58932,18 +59602,18 @@ var LIGHT_PARAMETER_DESCRIPTIONS = {
58932
59602
  message: "Message ordinal from a <session-history> heading or a mmc_search hit, not a tag number. Returns that one message in full. Use alone."
58933
59603
  },
58934
59604
  mmc_note: {
58935
- action: "write | read | update | dismiss (default: write with content, else read).",
59605
+ action: "write | read | update | dismiss.",
58936
59606
  content: "Note text: first line title (<80 chars), then detail.",
58937
59607
  filter: "Read filter: active (default), all, dismissed.",
58938
59608
  limit: "Rows per read (default 25).",
58939
59609
  offset: "Skip newest rows (default 0).",
58940
- note_ids: "One id for update, 1–50 for dismiss or read (full bodies). Ignored by write."
59610
+ note_ids: "One id for update, 1–50 for dismiss, or 1–50 to read those notes in full. Write and a plain read take an empty list."
58941
59611
  },
58942
59612
  mmc_memory: {
58943
59613
  action: "write | update | archive | merge | get",
58944
- content: "One standalone fact (write, update, merge).",
58945
- category: "Kind of fact (required for write; optional on update/merge).",
58946
- ids: "Ids from <project-memory>: one for update, 1+ for archive, 2+ for merge, 1–20 for get.",
59614
+ content: "One standalone fact (write, update, merge). Archive and get: empty string.",
59615
+ category: "Kind of fact (write; on update/merge pass the current category to keep it). Archive and get ignore it.",
59616
+ ids: "Ids from <project-memory>: one for update, 1+ for archive, 2+ for merge, 1–20 for get. Write: empty list.",
58947
59617
  reason: "Why it is archived (optional)."
58948
59618
  },
58949
59619
  mmc_search: {
@@ -59058,6 +59728,17 @@ function resolveCtxExpandMode(args, domain) {
59058
59728
 
59059
59729
  // ../plugin/src/tools/ctx-expand/render.ts
59060
59730
  import { createHash as createHash25 } from "node:crypto";
59731
+ function recoveryParts(sessionId, message, attachments) {
59732
+ return attachments ? [...message.parts, ...attachments(sessionId, message.id)] : message.parts;
59733
+ }
59734
+ function attachmentNotice(part) {
59735
+ const field = (value) => typeof value === "string" ? value.trim().replace(/[\r\n]+/g, " ") : "";
59736
+ const source = isRecord7(part.source) ? part.source : {};
59737
+ const location = [part.url, part.path, source.uri, source.url, source.path].map(field).find((value) => value && !/^(data|blob):/i.test(value));
59738
+ const name = field(part.filename) || field(part.name);
59739
+ const mime = field(part.mime) || field(part.mimeType) || field(part.mediaType);
59740
+ return `[file]${name ? ` ${name}` : ""}${mime ? ` (${mime})` : ""}` + `${location ? ` — stored location: ${location}` : ""}` + "; content not recovered. If needed, ask the user to attach it again.";
59741
+ }
59061
59742
  function isRecord7(value) {
59062
59743
  return value !== null && typeof value === "object" && !Array.isArray(value);
59063
59744
  }
@@ -59164,8 +59845,8 @@ function renderPartPreview(part, expandTools) {
59164
59845
  if (reasoning !== null)
59165
59846
  return ` • [reasoning] ${truncate2(reasoning, 120)}`;
59166
59847
  const type = typeof part.type === "string" ? part.type : "part";
59167
- if (type === "file")
59168
- return " • [file]";
59848
+ if (type === "file" || type === "image")
59849
+ return ` • ${attachmentNotice(part)}`;
59169
59850
  if (type === "step-start" || type === "step-finish")
59170
59851
  return null;
59171
59852
  return ` • [${type}]`;
@@ -59195,13 +59876,11 @@ ${tool.output}`);
59195
59876
  `);
59196
59877
  }
59197
59878
  const type = typeof part.type === "string" ? part.type : "part";
59198
- if (type === "file") {
59199
- const name = typeof part.filename === "string" && part.filename || typeof part.url === "string" && part.url || "";
59200
- return ` [file]${name ? ` ${name}` : ""}`;
59201
- }
59879
+ if (type === "file" || type === "image")
59880
+ return ` ${attachmentNotice(part)}`;
59202
59881
  return null;
59203
59882
  }
59204
- function renderMessageByOrdinal(sessionId, ordinal) {
59883
+ function renderMessageByOrdinal(sessionId, ordinal, attachments) {
59205
59884
  const found = [];
59206
59885
  visitRawSessionMessages2(sessionId, ordinal, ordinal, (m) => {
59207
59886
  if (m.ordinal === ordinal)
@@ -59212,7 +59891,7 @@ function renderMessageByOrdinal(sessionId, ordinal) {
59212
59891
  if (!msg) {
59213
59892
  return `No message at ordinal ${ordinal} is currently in this session's stored history — ` + `it may have been removed by a session prune/revert, or the ordinal may be wrong. ` + `Re-run the tool if you still need the data.`;
59214
59893
  }
59215
- const rendered = msg.parts.map(renderPartFull).filter((l) => l !== null);
59894
+ const rendered = recoveryParts(sessionId, msg, attachments).map(renderPartFull).filter((l) => l !== null);
59216
59895
  const lines = [`[${msg.ordinal}] ${roleLabel(msg.role)} — full recovery:`, ""];
59217
59896
  if (rendered.length === 0) {
59218
59897
  lines.push(" (no recoverable content — message had only structural/reasoning parts)");
@@ -59222,14 +59901,14 @@ function renderMessageByOrdinal(sessionId, ordinal) {
59222
59901
  return lines.join(`
59223
59902
  `);
59224
59903
  }
59225
- function renderVerboseRange(sessionId, start, end, tokenBudget, expandTools) {
59904
+ function renderVerboseRange(sessionId, start, end, tokenBudget, expandTools, attachments) {
59226
59905
  const out = [];
59227
59906
  let usedTokens = 0;
59228
59907
  let lastOrdinal = start - 1;
59229
59908
  let truncated = false;
59230
59909
  visitRawSessionMessages2(sessionId, start, end, (msg) => {
59231
59910
  const header = `[${msg.ordinal}] ${verboseRoleLabel(msg)}`;
59232
- const partLines = msg.parts.map((part) => renderPartPreview(part, expandTools)).filter((l) => l !== null);
59911
+ const partLines = recoveryParts(sessionId, msg, attachments).map((part) => renderPartPreview(part, expandTools)).filter((l) => l !== null);
59233
59912
  const block = partLines.length > 0 ? `${header}
59234
59913
  ${partLines.join(`
59235
59914
  `)}` : header;
@@ -59247,7 +59926,7 @@ ${partLines.join(`
59247
59926
 
59248
59927
  `), lastOrdinal, truncated };
59249
59928
  }
59250
- function renderItemByTag(db, sessionId, number, textIndexDomain = "part") {
59929
+ function renderItemByTag(db, sessionId, number, textIndexDomain = "part", attachments) {
59251
59930
  const tag = getTagById(db, sessionId, number);
59252
59931
  if (!tag)
59253
59932
  return `no tag ${number} in this session; if ${number} came from a <session-history> heading or a ctx_search hit, it is an ordinal: use message=${number}`;
@@ -59272,17 +59951,19 @@ function renderItemByTag(db, sessionId, number, textIndexDomain = "part") {
59272
59951
  return rendered.length ? rendered.join(`
59273
59952
  `) : `Tag ${number}'s original tool call is no longer in stored history.`;
59274
59953
  }
59275
- const scoped = /^(.*):p(\d+)$/.exec(tag.messageId);
59954
+ const scoped = /^(.*):(?:p|file)(\d+)$/.exec(tag.messageId);
59276
59955
  const derived = /^(.*):mc-text-v1:([a-f0-9]+):([a-f0-9]+):o(\d+)$/.exec(tag.messageId);
59277
59956
  const owner = scoped?.[1] ?? derived?.[1] ?? tag.messageId;
59278
59957
  const message = readRawSessionMessageById2(sessionId, owner);
59279
59958
  if (!message)
59280
59959
  return `Tag ${number}'s original text is no longer in stored history.`;
59281
59960
  const index = scoped ? Number(scoped[2]) : 0;
59282
- const piText = message.parts.filter((part) => isRecord7(part) && part.type === "text");
59283
- const part = message.parts[index];
59961
+ const attached = attachments?.(sessionId, message.id) ?? [];
59962
+ const parts = [...message.parts, ...attached];
59963
+ const piText = parts.filter((part) => isRecord7(part) && part.type === "text");
59964
+ const part = tag.type === "file" ? attached.find((part) => isRecord7(part) && part.partIndex === index) ?? message.parts[index] : parts[index];
59284
59965
  const matching = derived ? piText.filter((part) => isRecord7(part) && typeof part.text === "string" && createHash25("sha256").update(part.text).digest("hex") === derived[3]) : [];
59285
- const selected = derived ? matching[Number(derived[4])] : textIndexDomain === "text" ? piText[index] : part;
59966
+ const selected = derived ? matching[Number(derived[4])] : textIndexDomain === "text" && tag.type !== "file" ? piText[index] : part;
59286
59967
  if (!selected)
59287
59968
  return `Tag ${number}'s original text part is no longer in stored history.`;
59288
59969
  const renderedPart = renderPartFull(selected);
@@ -59725,6 +60406,34 @@ var integerKeyPattern = new RegExp("^(?:0|[1-9][0-9]*)$");
59725
60406
 
59726
60407
  // ../../node_modules/.bun/typebox@1.3.27/node_modules/typebox/build/type/engine/indexed/from_object.mjs
59727
60408
  var NumericKeyPattern = new RegExp(IntegerKey);
60409
+ // src/tools/expand-attachments.ts
60410
+ function piRecoveryAttachments(entries) {
60411
+ return (_sessionId, messageId) => {
60412
+ const entry = entries.find((entry) => entry !== null && typeof entry === "object" && entry.id === messageId);
60413
+ if (entry?.message?.role !== "user" || !Array.isArray(entry.message.content))
60414
+ return [];
60415
+ return entry.message.content.flatMap((part, partIndex) => {
60416
+ if (!part || part.type !== "image" && part.type !== "file")
60417
+ return [];
60418
+ return [
60419
+ {
60420
+ type: part.type,
60421
+ filename: part.filename ?? part.name,
60422
+ mime: part.mimeType ?? part.mime,
60423
+ url: [
60424
+ part.url,
60425
+ part.source?.uri,
60426
+ part.source?.url,
60427
+ part.providerFile?.uri
60428
+ ].find((value) => typeof value === "string" && value.trim() && !/^(data|blob):/i.test(value.trim())),
60429
+ path: part.path ?? part.source?.path,
60430
+ partIndex
60431
+ }
60432
+ ];
60433
+ });
60434
+ };
60435
+ }
60436
+
59728
60437
  // src/tools/ctx-expand.ts
59729
60438
  var ParamsSchema = _Object_({
59730
60439
  tag: Optional(Union([Number2(), String2()], {
@@ -59774,12 +60483,13 @@ function createCtxExpandTool(deps) {
59774
60483
  readMessages: () => readPiSessionMessages(ctx, piRawOrdinalOffsetSource(deps.db, sessionId))
59775
60484
  });
59776
60485
  try {
60486
+ const attachments = piRecoveryAttachments(ctx.sessionManager.getBranch());
59777
60487
  const mode = resolveCtxExpandMode(params, "positive");
59778
60488
  if (mode.kind === "error") {
59779
60489
  return err(mode.message);
59780
60490
  }
59781
60491
  if (mode.kind === "tag") {
59782
- return ok(renderItemByTag(deps.db, sessionId, mode.tag, "text"));
60492
+ return ok(renderItemByTag(deps.db, sessionId, mode.tag, "text", attachments));
59783
60493
  }
59784
60494
  const alignment = resolvePiOrdinalAlignmentForContext(deps.db, sessionId, ctx);
59785
60495
  if (isPiOrdinalAlignmentUnanchored(alignment)) {
@@ -59797,7 +60507,7 @@ function createCtxExpandTool(deps) {
59797
60507
  if (mode.message <= lostThrough) {
59798
60508
  return ok(`No message at ordinal ${mode.message}: it is no longer in this session's history, which now starts at message ${firstAvailable}.`);
59799
60509
  }
59800
- return ok(renderMessageByOrdinal(sessionId, mode.message));
60510
+ return ok(renderMessageByOrdinal(sessionId, mode.message, attachments));
59801
60511
  }
59802
60512
  const { end, verbose } = mode;
59803
60513
  const requestedStart = mode.start;
@@ -59817,7 +60527,7 @@ function createCtxExpandTool(deps) {
59817
60527
 
59818
60528
  ` : "";
59819
60529
  if (verbose) {
59820
- const v = renderVerboseRange(sessionId, start, effectiveEnd, CTX_EXPAND_TOKEN_BUDGET, deps.expandTools);
60530
+ const v = renderVerboseRange(sessionId, start, effectiveEnd, CTX_EXPAND_TOKEN_BUDGET, deps.expandTools, attachments);
59821
60531
  if (!v.text) {
59822
60532
  return ok(`No messages found in range ${start}-${effectiveEnd}. The range may be outside this session's history.`);
59823
60533
  }
@@ -59888,18 +60598,18 @@ function runImmediateTransaction2(db, fn) {
59888
60598
  var PRIMARY_ACTIONS = ["write", "update", "archive", "merge", "get"];
59889
60599
  var GET_MAX_IDS = 20;
59890
60600
  var ParamsShape = {
59891
- action: Optional(Union(PRIMARY_ACTIONS.map((a) => Literal(a)), {
60601
+ action: Union(PRIMARY_ACTIONS.map((a) => Literal(a)), {
59892
60602
  description: "write | update | archive | merge | get"
59893
- })),
59894
- content: Optional(String2({
60603
+ }),
60604
+ content: String2({
59895
60605
  description: "The memory text — one standalone fact (write, update, merge)."
59896
- })),
59897
- category: Optional(Union(V2_MEMORY_CATEGORIES.map((c) => Literal(c)), {
59898
- description: "Kind of fact (required for write; on update/merge optional, omitted keeps the current category)."
59899
- })),
59900
- ids: Optional(_Array_(Number2(), {
60606
+ }),
60607
+ category: Union(V2_MEMORY_CATEGORIES.map((c) => Literal(c)), {
60608
+ description: "Kind of fact (required for write; on update/merge, pass the memory's current category to keep it)."
60609
+ }),
60610
+ ids: _Array_(Number2(), {
59901
60611
  description: "Memory ids from <project-memory>: one for update, one or more for archive, two or more for merge, 1–20 for get."
59902
- })),
60612
+ }),
59903
60613
  reason: Optional(String2({
59904
60614
  description: "Why it is being archived (optional)."
59905
60615
  }))
@@ -59907,6 +60617,20 @@ var ParamsShape = {
59907
60617
  var PrimaryParamsSchema = _Object_(ParamsShape, {
59908
60618
  additionalProperties: true
59909
60619
  });
60620
+ function rejectInvalidMemoryArguments(args) {
60621
+ if (args === null || typeof args !== "object")
60622
+ return;
60623
+ const record = args;
60624
+ if (record.content !== undefined && typeof record.content !== "string") {
60625
+ throw new Error("Error: 'content' must be a string.");
60626
+ }
60627
+ if (record.reason !== undefined && typeof record.reason !== "string") {
60628
+ throw new Error("Error: 'reason' must be a string.");
60629
+ }
60630
+ if (record.ids !== undefined && (!Array.isArray(record.ids) || record.ids.some((id) => typeof id !== "number" || !Number.isSafeInteger(id) || id < 1))) {
60631
+ throw new Error("Error: 'ids' must be an array of positive memory IDs.");
60632
+ }
60633
+ }
59910
60634
  function ok2(text) {
59911
60635
  return { content: [{ type: "text", text }], details: undefined };
59912
60636
  }
@@ -60005,6 +60729,10 @@ function createCtxMemoryTool(deps) {
60005
60729
  label: "Magic Context: Memory",
60006
60730
  description: CTX_MEMORY_DESCRIPTION,
60007
60731
  parameters: PrimaryParamsSchema,
60732
+ prepareArguments(args) {
60733
+ rejectInvalidMemoryArguments(args);
60734
+ return args;
60735
+ },
60008
60736
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
60009
60737
  params = unwrapImitatedReducedArgs(params, ["action"], {
60010
60738
  action: { type: "enum", values: PRIMARY_ACTIONS },
@@ -60337,7 +61065,7 @@ function renderGlance(notes, options) {
60337
61065
  const remaining = ordered.length - options.offset - page.length;
60338
61066
  const footer = remaining > 0 ? `
60339
61067
 
60340
- Showing ${page.length} of ${ordered.length} — ${remaining} older: mmc_note(action="read", offset=${options.offset + page.length})` : "";
61068
+ Showing ${page.length} of ${ordered.length} — ${remaining} older: mmc_note(action="read", content="", note_ids=[], offset=${options.offset + page.length})` : "";
60341
61069
  return `## Notes
60342
61070
 
60343
61071
  ${rows}${footer}`;
@@ -60367,22 +61095,21 @@ function formatWriteReply(noteId, tray, nowMs) {
60367
61095
  // src/tools/ctx-note.ts
60368
61096
  var FILTER_VALUES = ["active", "dismissed", "all"];
60369
61097
  var ParamsSchema2 = _Object_({
60370
- action: Optional(Union([
61098
+ action: Union([
60371
61099
  Literal("write"),
60372
61100
  Literal("read"),
60373
61101
  Literal("dismiss"),
60374
61102
  Literal("update")
60375
61103
  ], {
60376
- description: "write | read | update | dismiss. Defaults to write when content is given, else read."
60377
- })),
60378
- content: Optional(String2({
61104
+ description: "write | read | update | dismiss."
61105
+ }),
61106
+ content: String2({
60379
61107
  description: "Note text for write/update: first line is the title (under 80 chars), then the detail."
60380
- })),
60381
- note_ids: Optional(_Array_(Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }), {
60382
- minItems: 1,
61108
+ }),
61109
+ note_ids: _Array_(Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }), {
60383
61110
  maxItems: 50,
60384
- description: "Note ids: one for update, 1–50 for dismiss or read (read returns full bodies). Ignored by write."
60385
- })),
61111
+ description: "Note ids: one for update, 1–50 for dismiss, or 1–50 to read those notes in full. Write and a plain read take an empty list."
61112
+ }),
60386
61113
  filter: Optional(Union(FILTER_VALUES.map((value) => Literal(value)), {
60387
61114
  description: "Read filter: active (default), all, dismissed."
60388
61115
  })),
@@ -60393,6 +61120,17 @@ var ParamsSchema2 = _Object_({
60393
61120
  description: "Skip this many newest rows (default 0)."
60394
61121
  }))
60395
61122
  }, { additionalProperties: true });
61123
+ function rejectInvalidNoteArguments(args) {
61124
+ if (args === null || typeof args !== "object")
61125
+ return;
61126
+ const record = args;
61127
+ if (record.content !== undefined && typeof record.content !== "string") {
61128
+ throw new Error("Error: 'content' must be a string.");
61129
+ }
61130
+ if (record.note_ids !== undefined && (!Array.isArray(record.note_ids) || record.note_ids.some((id) => typeof id !== "number" || !Number.isSafeInteger(id) || id < 1))) {
61131
+ throw new Error("Error: 'note_ids' must be an array of positive note IDs.");
61132
+ }
61133
+ }
60396
61134
  function ok3(text) {
60397
61135
  return { content: [{ type: "text", text }], details: undefined };
60398
61136
  }
@@ -60413,7 +61151,7 @@ function captureAnchorOrdinal(db, sessionId) {
60413
61151
  }
60414
61152
  var DISMISS_FOOTER = `
60415
61153
 
60416
- To dismiss a stale note: mmc_note(action="dismiss", note_ids=[N])`;
61154
+ To dismiss a stale note: mmc_note(action="dismiss", note_ids=[N], content="")`;
60417
61155
  function formatDismissResults(results) {
60418
61156
  const dismissedCount = results.filter((result) => result.outcome === "dismissed").length;
60419
61157
  return `Dismissed ${dismissedCount} of ${results.length} notes.
@@ -60449,6 +61187,10 @@ function createCtxNoteTool(deps) {
60449
61187
  label: "Magic Context: Notes",
60450
61188
  description: CTX_NOTE_DESCRIPTION,
60451
61189
  parameters: ParamsSchema2,
61190
+ prepareArguments(args) {
61191
+ rejectInvalidNoteArguments(args);
61192
+ return args;
61193
+ },
60452
61194
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
60453
61195
  params = unwrapImitatedReducedArgs(params, ["action", "content"], {
60454
61196
  action: {
@@ -60463,7 +61205,8 @@ function createCtxNoteTool(deps) {
60463
61205
  });
60464
61206
  const sessionId = ctx.sessionManager.getSessionId();
60465
61207
  const action = params.action ?? (params.content?.trim() ? "write" : "read");
60466
- const noteIds = action === "dismiss" || action === "update" || action === "read" && params.note_ids !== undefined ? parseNoteIds(action, params.note_ids) : undefined;
61208
+ const targetedReadIds = action === "read" && Array.isArray(params.note_ids) && params.note_ids.length === 0 ? undefined : params.note_ids;
61209
+ const noteIds = action === "dismiss" || action === "update" || action === "read" && targetedReadIds !== undefined ? parseNoteIds(action, targetedReadIds) : undefined;
60467
61210
  if (typeof noteIds === "string")
60468
61211
  return err3(noteIds);
60469
61212
  if (action === "write") {
@@ -62272,7 +63015,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
62272
63015
  harness: PI_HARNESS_KIND
62273
63016
  });
62274
63017
  const emptyHistorian = empty.find((chain) => chain.owner === "historian");
62275
- const historianLine = historian ? `registered historian trigger (model=${historian.model}${historian.fallbackModels?.length ? `, fallbacks=${historian.fallbackModels.join(",")}` : ""}, executeThreshold=${formatExecuteThresholdForLog(historian.executeThresholdPercentage)})` : emptyHistorian ? `registered historian trigger: DISABLED (no configured model is registered in Pi: ${formatEmptyPiModelChain(emptyHistorian)})` : undefined;
63018
+ const historianLine = historian ? `registered historian trigger (model=${historian.model}${historian.fallbackModels?.length ? `, fallbacks=${historian.fallbackModels.map(formatPiModelForLog).join(",")}` : ""}, executeThreshold=${formatExecuteThresholdForLog(historian.executeThresholdPercentage)})` : emptyHistorian ? `registered historian trigger: DISABLED (no configured model is registered in Pi: ${formatEmptyPiModelChain(emptyHistorian)})` : undefined;
62276
63019
  if (historianLine && !loggedPiModelChainLines.has(historianLine)) {
62277
63020
  loggedPiModelChainLines.add(historianLine);
62278
63021
  info(historianLine);
@@ -62820,6 +63563,11 @@ function formatExecuteThresholdForLog(value) {
62820
63563
  const base = `${value.default}%`;
62821
63564
  return overrides.length > 0 ? `${base} (${overrides.join(", ")})` : base;
62822
63565
  }
63566
+ function formatPiModelForLog(entry) {
63567
+ if (typeof entry === "string")
63568
+ return entry;
63569
+ return `${entry.model}${entry.qualifier ? ` (thinking_level=${entry.qualifier})` : ""}`;
63570
+ }
62823
63571
  export {
62824
63572
  __test,
62825
63573
  canonicalPiModelKey,