@cortexkit/pi-magic-context 0.49.6 → 0.49.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/auto-search-worker.js +10 -7
  2. package/dist/{compaction-marker-candidates-9w5g66at.js → compaction-marker-candidates-tqcq5n00.js} +2 -2
  3. package/dist/compaction-marker-candidates-worker.js +2 -2
  4. package/dist/{embedding-backfill-809pgwvb.js → embedding-backfill-vygvr2x4.js} +6 -6
  5. package/dist/{index-fx0r5485.js → index-0d8zqccx.js} +3 -3
  6. package/dist/{index-9mn34fnx.js → index-0eg59y8d.js} +2 -2
  7. package/dist/{index-5z1h0qsa.js → index-0ww9cgfd.js} +4 -4
  8. package/dist/{index-wf7mwdnf.js → index-34zacc0h.js} +1 -1
  9. package/dist/{index-57mvvbbh.js → index-6q5mx0cz.js} +8 -7
  10. package/dist/{index-fcacf11g.js → index-6vjj0555.js} +1 -1
  11. package/dist/{index-5rc9kvrk.js → index-7mm27xdc.js} +1 -1
  12. package/dist/{index-8vbh4tzw.js → index-7ptxvfc5.js} +34 -13
  13. package/dist/{index-hfnjfk4d.js → index-8jpqq2yk.js} +64 -64
  14. package/dist/{index-cd6bjrj4.js → index-8ye22z7k.js} +1 -1
  15. package/dist/{index-p4matwj0.js → index-a7fj661j.js} +1 -1
  16. package/dist/{index-kxxdyjfs.js → index-dj784nyr.js} +1 -1
  17. package/dist/{index-swab4319.js → index-dj8hpkz7.js} +1 -1
  18. package/dist/{index-j7pa3t4s.js → index-fhv6ydkq.js} +98 -19
  19. package/dist/{index-kamhcj15.js → index-gqsfhksw.js} +1 -1
  20. package/dist/{index-0p9gemc6.js → index-mrsarsbh.js} +160 -25
  21. package/dist/index.js +979 -321
  22. package/dist/{message-index-m6snq0fh.js → message-index-8rxj3cxh.js} +7 -7
  23. package/dist/{message-index-bounded-8a94k72f.js → message-index-bounded-e5bv8gzm.js} +7 -7
  24. package/dist/{message-time-backfill-xqwg3hg1.js → message-time-backfill-hmekd2jj.js} +1 -1
  25. package/dist/migration-worker.js +5 -5
  26. package/dist/{project-embedding-registry-3x6nmszv.js → project-embedding-registry-z5m4eh3d.js} +5 -5
  27. package/dist/raw-ordinal-warmup-worker.js +17 -15
  28. package/dist/{read-session-chunk-49e1ayzs.js → read-session-chunk-sp5adk01.js} +6 -4
  29. package/dist/{read-session-raw-545mgtjz.js → read-session-raw-s14c7nf3.js} +2 -2
  30. package/dist/{search-q76dp7w9.js → search-hgfaqytc.js} +13 -13
  31. package/dist/{sqlite-wp7wddw3.js → sqlite-f8kh2n10.js} +1 -1
  32. package/dist/{storage-memory-7417pbm9.js → storage-memory-yf7r98vz.js} +2 -2
  33. package/dist/{storage-tags-cvjsrbqh.js → storage-tags-0xk95neb.js} +2 -2
  34. package/dist/subagent-entry.js +16 -16
  35. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -168,6 +168,7 @@ import {
168
168
  readPiSessionMessagePage,
169
169
  findRestartModelSeedFromBranch,
170
170
  iterateEntriesToRawMessageRange,
171
+ iterateAsyncEntriesToRawMessageRange,
171
172
  convertEntriesToRawMessages,
172
173
  convertPiAssistantEntryById,
173
174
  convertEntriesToRawMessagePage,
@@ -249,7 +250,7 @@ import {
249
250
  registerTodoStateLifecycle,
250
251
  syncCtxMemoryToolEnabled,
251
252
  registerMagicContextTools
252
- } from "./index-0p9gemc6.js";
253
+ } from "./index-mrsarsbh.js";
253
254
  import {
254
255
  canonicalModelIdentity,
255
256
  piModelRefToCanonical,
@@ -289,7 +290,7 @@ import {
289
290
  openCurrentDatabase,
290
291
  openDatabaseAsync,
291
292
  closeDatabase
292
- } from "./index-57mvvbbh.js";
293
+ } from "./index-6q5mx0cz.js";
293
294
  import {
294
295
  setHomeProjectPermission,
295
296
  ProjectIdentityError,
@@ -300,7 +301,7 @@ import {
300
301
  resolveProjectIdentityForSession,
301
302
  beginBootQuietPeriod,
302
303
  scheduleAfterBootQuiet
303
- } from "./index-kamhcj15.js";
304
+ } from "./index-gqsfhksw.js";
304
305
  import {
305
306
  detectThinkingBindingMismatch,
306
307
  detectLatestTurnThinkingMismatch,
@@ -398,6 +399,7 @@ import {
398
399
  getPendingPiCompactionMarkerState,
399
400
  setPendingPiCompactionMarkerState,
400
401
  clearPendingPiCompactionMarkerStateIf,
402
+ logPiCompactionMarkerClear,
401
403
  getSessionsWithPendingPiMarker,
402
404
  setSessionWorkMetrics,
403
405
  getSessionWorkMetrics,
@@ -445,6 +447,7 @@ import {
445
447
  readRawSessionMessages2,
446
448
  getRawSessionMessageOrdinalCount2,
447
449
  visitRawSessionMessages2,
450
+ visitRawSessionMessagesAsync2,
448
451
  readRawSessionMessageRange2,
449
452
  prepareRawSessionOrdinals2,
450
453
  primeTailRawMessageCache2,
@@ -455,7 +458,7 @@ import {
455
458
  getRawSessionTagKeysThrough2,
456
459
  getLegacyProtectedTailStartOrdinal2,
457
460
  readSessionChunk2
458
- } from "./index-j7pa3t4s.js";
461
+ } from "./index-fhv6ydkq.js";
459
462
  import {
460
463
  UNKNOWN_FIT_RATIO,
461
464
  hasMeasuredDecisionCalibration,
@@ -538,7 +541,7 @@ import {
538
541
  getToolTagNumberByOwner2,
539
542
  getNullOwnerToolTag2,
540
543
  adoptNullOwnerToolTag2
541
- } from "./index-fcacf11g.js";
544
+ } from "./index-6vjj0555.js";
542
545
  import {
543
546
  textMentionsRecentCommit,
544
547
  hasMeaningfulUserText2,
@@ -562,7 +565,7 @@ import {
562
565
  isTransientSqliteError2,
563
566
  withAsyncPrivilegedWriter2,
564
567
  indexCoordinationInput2
565
- } from "./index-8vbh4tzw.js";
568
+ } from "./index-7ptxvfc5.js";
566
569
  import {
567
570
  stableStringify,
568
571
  closeQuietly,
@@ -587,7 +590,7 @@ import"./index-399p688g.js";
587
590
  import {
588
591
  runSessionWarmupWorker,
589
592
  RAW_SUMMARY_TEXT_MAX_CHARS2
590
- } from "./index-5rc9kvrk.js";
593
+ } from "./index-7mm27xdc.js";
591
594
  import {
592
595
  piHarnessKindFromExecutable
593
596
  } from "./index-0j0zqb4t.js";
@@ -613,13 +616,13 @@ import {
613
616
  indexSingleMessage2,
614
617
  indexMessagesAfterOrdinal2,
615
618
  sweepOrphanedOpenCodeMessageIndexes2
616
- } from "./index-5z1h0qsa.js";
619
+ } from "./index-0ww9cgfd.js";
617
620
  import {
618
621
  formatSynapseLaneDescriptor
619
622
  } from "./index-21v1kfs7.js";
620
623
  import {
621
624
  sameProjectDirectory
622
- } from "./index-wf7mwdnf.js";
625
+ } from "./index-34zacc0h.js";
623
626
  import {
624
627
  buildCanonicalChunkTextFromFts,
625
628
  buildCompartmentSummaryFallbackText,
@@ -629,7 +632,7 @@ import {
629
632
  replaceCompartmentChunkEmbeddings,
630
633
  deleteChunkEmbedBackoffForSession,
631
634
  chunkWindowSourceKey
632
- } from "./index-cd6bjrj4.js";
635
+ } from "./index-8ye22z7k.js";
633
636
  import {
634
637
  recordSessionProjectIdentity,
635
638
  GIT_SWEEP_LEASE_RENEWAL_MS,
@@ -654,7 +657,7 @@ import {
654
657
  embedSessionCompartmentChunks2,
655
658
  getEmbeddingCoverageStatus2,
656
659
  getAutoEmbeddingSessionCoverage2
657
- } from "./index-fx0r5485.js";
660
+ } from "./index-0d8zqccx.js";
658
661
  import {
659
662
  V2_MEMORY_CATEGORIES
660
663
  } from "./index-135e8hvg.js";
@@ -666,7 +669,7 @@ import {
666
669
  expandWorkspaceIdentitySetWithAliases,
667
670
  sourceNameForMemory,
668
671
  computeWorkspaceEpochFingerprint
669
- } from "./index-9mn34fnx.js";
672
+ } from "./index-0eg59y8d.js";
670
673
  import {
671
674
  configureContextDatabasePragmas2
672
675
  } from "./index-fapenpch.js";
@@ -716,7 +719,7 @@ import {
716
719
  normalizeVerificationFiles,
717
720
  indexCommitsForProject,
718
721
  embedUnembeddedCommits
719
- } from "./index-hfnjfk4d.js";
722
+ } from "./index-8jpqq2yk.js";
720
723
  import {
721
724
  setEmbeddingSessionBusy2,
722
725
  isEmbeddingHostBusy2
@@ -740,7 +743,7 @@ import {
740
743
  setMemoryClassification2,
741
744
  archiveMemory2,
742
745
  getMemoryCountsByStatus2
743
- } from "./index-kxxdyjfs.js";
746
+ } from "./index-dj784nyr.js";
744
747
  import {
745
748
  invalidateMemory2
746
749
  } from "./index-yhzhdp3m.js";
@@ -750,8 +753,8 @@ import {
750
753
  import"./index-gc2749rx.js";
751
754
  import {
752
755
  cosineSimilarity
753
- } from "./index-p4matwj0.js";
754
- import"./index-swab4319.js";
756
+ } from "./index-a7fj661j.js";
757
+ import"./index-dj8hpkz7.js";
755
758
  import {
756
759
  buildHiddenAgentRegistrations,
757
760
  logTemperatureDecision,
@@ -2367,7 +2370,7 @@ function observeNoteNudgeServe(args) {
2367
2370
  sessions.set(args.sessionId, seen);
2368
2371
  return {
2369
2372
  isFirstServe: warm && args.isLiveTail && firstServe,
2370
- isCacheBustingPass: args.anchorMessageId !== null && args.isCacheBustingPass
2373
+ prefixRewriteCoversAnchor: args.anchorMessageId !== null && args.prefixRewriteCoversAnchor
2371
2374
  };
2372
2375
  }
2373
2376
  var NOTE_NUDGE_COOLDOWN_MS = 15 * 60 * 1000;
@@ -2406,7 +2409,7 @@ function peekNoteNudgeText(db, sessionId, currentUserMessageId, projectIdentity,
2406
2409
  const state = getPersistedNoteNudge(db, sessionId);
2407
2410
  if (!state.triggerPending)
2408
2411
  return null;
2409
- if (eligibility && !eligibility.isFirstServe && !eligibility.isCacheBustingPass) {
2412
+ if (eligibility && !eligibility.isFirstServe && !eligibility.prefixRewriteCoversAnchor) {
2410
2413
  sessionLog2(sessionId, "note-nudge: deferring — user already served or first serve unproven");
2411
2414
  return null;
2412
2415
  }
@@ -3421,7 +3424,7 @@ function startHostStallProfilerSwitch() {
3421
3424
  // package.json
3422
3425
  var package_default = {
3423
3426
  name: "@cortexkit/pi-magic-context",
3424
- version: "0.49.6",
3427
+ version: "0.49.7",
3425
3428
  type: "module",
3426
3429
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
3427
3430
  main: "dist/index.js",
@@ -10320,6 +10323,25 @@ async function runHiddenSingleShotPrompt(args) {
10320
10323
  });
10321
10324
  promptSettled = true;
10322
10325
  return { validated: run.validated, completion: run.output, childSessionId: handle.id };
10326
+ } catch (error) {
10327
+ if (args.onFailureOutput) {
10328
+ let timer;
10329
+ try {
10330
+ const completion = await Promise.race([
10331
+ args.executor.collect(handle, 200),
10332
+ new Promise((resolve) => {
10333
+ timer = setTimeout(() => resolve(null), 5000);
10334
+ })
10335
+ ]);
10336
+ if (completion)
10337
+ args.onFailureOutput(completion);
10338
+ } catch (readError) {
10339
+ log2(`${args.callContext}: failed to collect interrupted output: ${String(readError)}`);
10340
+ } finally {
10341
+ clearTimeout(timer);
10342
+ }
10343
+ }
10344
+ throw error;
10323
10345
  } finally {
10324
10346
  await args.executor.close(handle, {
10325
10347
  promptSettled,
@@ -13906,31 +13928,33 @@ function snapshotMaintainDocsFiles(docsDir) {
13906
13928
  import { createHash as createHash8 } from "node:crypto";
13907
13929
 
13908
13930
  // ../plugin/src/features/magic-context/dreamer/map-memories-prompt.ts
13931
+ import { execFile as execFile2 } from "node:child_process";
13909
13932
  import { existsSync as existsSync8, statSync as statSync2 } from "node:fs";
13910
13933
  import path10 from "node:path";
13934
+ import { promisify as promisify2 } from "node:util";
13911
13935
  var MAP_MEMORIES_SYSTEM_PROMPT = `You are a memory mapper for the magic-context system. You map project memories to the repository files that back them.
13912
13936
 
13913
13937
  A memory's BACKING FILES are the file(s) whose code the memory makes a claim about — the files you would open to check whether the memory is accurate. You do NOT judge accuracy, rewrite, or remove anything. You only LOCATE backing files.
13914
13938
 
13915
- Tools (read-only): read, grep, glob. Request line ranges rather than full files; use offset/limit for longer files. Each memory may come with "Likely files" already named in it and confirmed to exist — confirm those FIRST (cheap) instead of searching. Use search/grep to FIND code only when no likely files are given. Do not guess — confirm a file exists and genuinely backs the memory before listing it. Keep reads minimal: you do not need to read a whole file to confirm it backs a one-line claim.
13939
+ Tools (read-only): read, grep, glob. Use at most 3 tool calls per memory, including searches and reads. Never read the same file twice in a batch; reuse what you already learned. Never repeat an identical search or use broad searches such as "fn " or ".*". Request one useful line range, not repeated slices. Each memory may come with "Likely files" from paths it names or a literal identifier search, confirmed to exist — confirm those FIRST (cheap) instead of searching. Use search/grep to FIND code only when no likely files are given. Do not guess — confirm a file exists and genuinely backs the memory before listing it. You are locating code, not exhaustively verifying the claim. If the call budget runs out before you can decide, leave that id unmapped and move on; uncertainty is not evidence of file-independence.
13916
13940
 
13917
13941
  For each memory decide ONE of:
13918
- - Backing files found → the COMPLETE set of repo-relative paths whose code the memory is about.
13942
+ - Backing files found → the specific repo-relative paths whose code the memory is about; do not chase every caller or related implementation.
13919
13943
  - File-independent → the memory describes EXTERNAL behavior (a provider / API / platform / protocol limit, e.g. "Anthropic returns 400 on empty content"), or a pure process / workflow / philosophy rule, with NO specific local file that backs it. A BEHAVIORAL claim (when to act, how to work, who decides, or tool-usage discipline) is file-independent even when it cites file paths or commands as examples: a named file is not the file backing the rule.
13920
13944
 
13921
- Output ONE XML manifest at the very end and NOTHING else — no narration, no per-memory commentary, no reasoning:
13945
+ Output ONE XML manifest and NOTHING else — no narration, no per-memory commentary, no reasoning. Open <mappings> before investigating. Emit each <memory> element as soon as you decide its mapping, before investigating the next memory, so an interruption cannot lose completed work. Close </mappings> when finished:
13922
13946
  <mappings>
13923
13947
  <memory id="N" files="path/a.ts,path/b.ts"/>
13924
13948
  <memory id="M" independent="true"/>
13925
13949
  </mappings>
13926
13950
 
13927
13951
  Rules:
13928
- - Every input memory id MUST appear exactly once.
13952
+ - Every decided input memory id MUST appear exactly once. Omit uncertain ids rather than inventing backing files or declaring them independent.
13929
13953
  - files: repo-relative, comma-separated, no spaces inside a path. Only files that actually exist and genuinely back the memory.
13930
13954
  - A BACKING FILE is CODE that implements or handles the claim — not a file that merely mentions it. A path named inside a process directive is an action target or example, not evidence that the file backs the directive. A markdown doc (.md), a PARITY/notes file, or a test that only DESCRIBES an external fact is NOT a backing file. If the only place a memory's fact appears is prose/docs/a test (no code implements or handles it), mark it independent="true".
13931
13955
  - Many CONSTRAINTS are HYBRID: "external system does X, and OUR code handles it here." Map those to the HANDLING code (you can verify the handling, even though you can't verify the external behavior). Only mark independent when there is NO local code that implements or handles the fact.
13932
13956
  - Prefer the most specific file(s); do not pad with tangential files. Most memories map to one file; some to a few.
13933
- - When you genuinely cannot find any local backing and it is not clearly external, still emit the memory with independent="true" (do not drop it).`;
13957
+ - Mark independent="true" only for the file-independent cases above; an investigation that runs out of calls stays unmapped.`;
13934
13958
  var MAX_SEED_PATHS_PER_MEMORY = 3;
13935
13959
  var PATH_PATTERN = "`?((?:[\\w.-]+\\/)+[\\w.-]+\\.(?:ts|tsx|js|jsx|mjs|cjs|rs|go|py|json|jsonc|sql|toml|sh))`?";
13936
13960
  function extractMemoryCandidatePaths(content, repoDir) {
@@ -13952,10 +13976,65 @@ function extractMemoryCandidatePaths(content, repoDir) {
13952
13976
  }
13953
13977
  return [...found];
13954
13978
  }
13979
+ var execFileAsync2 = promisify2(execFile2);
13980
+ async function seedIdentifierCandidates(memories, repoDir) {
13981
+ const identifiers = new Map(memories.filter((memory) => memory.candidates.length === 0).map((memory) => [
13982
+ memory.id,
13983
+ [
13984
+ ...new Set(memory.content.match(/\b(?:[A-Z][A-Za-z0-9_]{3,}|[a-z][a-z0-9]*[A-Z][A-Za-z0-9_]*|[a-z][a-z0-9]*_[A-Za-z0-9_]{2,})\b/g) ?? [])
13985
+ ].slice(0, 3)
13986
+ ]));
13987
+ const terms = [...new Set([...identifiers.values()].flat())];
13988
+ if (terms.length === 0)
13989
+ return memories;
13990
+ let stdout;
13991
+ try {
13992
+ const result = await execFileAsync2("git", [
13993
+ "-c",
13994
+ "core.quotePath=false",
13995
+ "grep",
13996
+ "--no-color",
13997
+ "-n",
13998
+ "-I",
13999
+ "-F",
14000
+ ...terms.flatMap((term) => ["-e", term]),
14001
+ "--",
14002
+ ...["ts", "tsx", "js", "jsx", "mjs", "cjs", "rs", "go", "py", "sql", "sh"].map((extension) => `*.${extension}`)
14003
+ ], {
14004
+ cwd: repoDir,
14005
+ encoding: "utf8",
14006
+ timeout: 2000,
14007
+ maxBuffer: 256 * 1024,
14008
+ windowsHide: true,
14009
+ killSignal: "SIGKILL"
14010
+ });
14011
+ stdout = result.stdout;
14012
+ } catch {
14013
+ return memories;
14014
+ }
14015
+ const matches = stdout.split(`
14016
+ `).flatMap((line) => {
14017
+ const match = line.match(/^([^:]+):\d+:(.*)$/);
14018
+ return match ? [{ file: match[1], text: match[2] }] : [];
14019
+ });
14020
+ return memories.map((memory) => {
14021
+ const names = identifiers.get(memory.id);
14022
+ if (!names?.length)
14023
+ return memory;
14024
+ const files = new Map;
14025
+ for (const match of matches) {
14026
+ const score = names.filter((name) => match.text.includes(name)).length;
14027
+ if (score)
14028
+ files.set(match.file, Math.max(files.get(match.file) ?? 0, score));
14029
+ }
14030
+ const candidates = [...files].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_SEED_PATHS_PER_MEMORY).map(([file]) => file);
14031
+ return candidates.length ? { ...memory, candidates } : memory;
14032
+ });
14033
+ }
13955
14034
  function buildMapMemoriesPrompt(projectPath, memories) {
13956
14035
  const list = memories.map((m) => {
13957
14036
  const seed = m.candidates.length ? `
13958
- Likely files (named in the memory, confirmed to exist): ${m.candidates.join(", ")}` : "";
14037
+ Likely files (candidate paths, confirmed to exist): ${m.candidates.join(", ")}` : "";
13959
14038
  return `[${m.id}] ${m.category}
13960
14039
  ${m.content}${seed}`;
13961
14040
  }).join(`
@@ -13965,13 +14044,13 @@ ${m.content}${seed}`;
13965
14044
 
13966
14045
  Project: ${projectPath}
13967
14046
 
13968
- For each memory below, find the repo file(s) it makes a claim about, or mark it file-independent. Behavioral process/workflow directives stay file-independent even when they name files. When "Likely files" are listed, those paths are named in the memory and confirmed to exist — START there only for code claims: confirm each actually backs the claim (a quick read/outline), drop any that don't, add others only if genuinely needed. Search from scratch only when no likely files are given. Then output ONE <mappings> manifest covering every id.
14047
+ For each memory below, find the repo file(s) it makes a claim about, or mark it file-independent. Behavioral process/workflow directives stay file-independent even when they name files. When "Likely files" are listed, those paths either occur in the memory or match its identifiers in a bounded literal source search — START there only for code claims: confirm each actually backs the claim (a quick read/outline), drop any that don't, add others only if genuinely needed. Search from scratch only when no likely files are given. Use at most 3 tool calls per memory, reuse prior reads, and emit each decided mapping immediately in ONE <mappings> manifest. Leave uncertain ids unmapped and move on.
13969
14048
 
13970
14049
  <memories>
13971
14050
  ${list}
13972
14051
  </memories>`;
13973
14052
  }
13974
- var MEMORY_ELEMENT_PATTERN = "<memory\\b([^>]*)(?:\\/>|>([\\s\\S]*?)<\\/memory>)";
14053
+ var MEMORY_ELEMENT_PATTERN = "<memory\\b([^<>]*)(?:\\/>|>([\\s\\S]*?)<\\/memory>)";
13975
14054
  var NESTED_FILE_PATTERN = "<file\\b([^>]*)\\/?>";
13976
14055
  function extractNestedFilePaths(inner) {
13977
14056
  const files = [];
@@ -14029,12 +14108,78 @@ function validateMapMemoriesManifest(text, expectedIds) {
14029
14108
  assertNoDuplicateManifestIds(parsed.filter((entry) => expectedIds.has(entry.id)).map((entry) => entry.id), "mappings");
14030
14109
  return parsed;
14031
14110
  }
14111
+ function parsePartialMapMemoriesManifest(text, expectedIds) {
14112
+ const start = text.indexOf("<mappings>");
14113
+ if (start < 0)
14114
+ return [];
14115
+ const entries = new Map;
14116
+ const conflicts = new Set;
14117
+ for (const match of text.slice(start).matchAll(new RegExp(MEMORY_ELEMENT_PATTERN, "gi"))) {
14118
+ try {
14119
+ const entry = parseMapMemoriesManifest(`<mappings>${match[0]}</mappings>`)[0];
14120
+ if (!entry || !expectedIds.has(entry.id) || conflicts.has(entry.id))
14121
+ continue;
14122
+ const previous = entries.get(entry.id);
14123
+ if (previous && JSON.stringify(previous) !== JSON.stringify(entry)) {
14124
+ entries.delete(entry.id);
14125
+ conflicts.add(entry.id);
14126
+ } else {
14127
+ entries.set(entry.id, entry);
14128
+ }
14129
+ } catch {}
14130
+ }
14131
+ return [...entries.values()];
14132
+ }
14032
14133
 
14033
14134
  // ../plugin/src/features/magic-context/dreamer/map-memories.ts
14034
- var MAP_BATCH_SIZE = 20;
14135
+ var MAP_BATCH_SIZE = 5;
14035
14136
  var MAP_BATCH_FLOOR_MS = 240000;
14036
14137
  var CONSECUTIVE_TIMEOUT_LIMIT2 = 2;
14037
14138
  var MAX_INDEPENDENT_REQUEUE_PER_RUN = MAP_BATCH_SIZE;
14139
+ function retryKey(project) {
14140
+ return `map-memories:retry:${project}`;
14141
+ }
14142
+ function readRetries(db, project) {
14143
+ try {
14144
+ const parsed = JSON.parse(getDreamState(db, retryKey(project)) ?? "{}");
14145
+ if (!isRecord(parsed))
14146
+ return {};
14147
+ const retries = {};
14148
+ for (const [id, value] of Object.entries(parsed)) {
14149
+ if (isRecord(value) && typeof value.content === "string" && typeof value.failures === "number" && typeof value.deferUntil === "number") {
14150
+ retries[id] = {
14151
+ content: value.content,
14152
+ failures: value.failures,
14153
+ deferUntil: value.deferUntil
14154
+ };
14155
+ }
14156
+ }
14157
+ return retries;
14158
+ } catch {
14159
+ return {};
14160
+ }
14161
+ }
14162
+ function contentKey(memory) {
14163
+ return createHash8("sha256").update(`${memory.category}
14164
+ ${memory.content}`).digest("hex");
14165
+ }
14166
+ function isDeferred(memory, retries) {
14167
+ const retry = retries[memory.id];
14168
+ return !!retry && retry.content === contentKey(memory) && retry.deferUntil > Date.now();
14169
+ }
14170
+ function assistantText(messages) {
14171
+ return messages.filter((message) => isRecord(message) && (message.role === "assistant" || isRecord(message.info) && message.info.role === "assistant")).map((message) => extractLatestAssistantText([message]) ?? (isRecord(message) && Array.isArray(message.content) ? message.content.filter(isRecord).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
14172
+ `) : "")).join(`
14173
+ `);
14174
+ }
14175
+ function currentManifest(text) {
14176
+ const start = text.lastIndexOf("<mappings>");
14177
+ return start < 0 ? text : text.slice(start);
14178
+ }
14179
+ function logBatchUsage(messages, committed, startedAt) {
14180
+ const steps = messages.filter((message) => isRecord(message) && (message.role === "assistant" || isRecord(message.info) && message.info.role === "assistant")).length;
14181
+ log2(`[dreamer] map-memories batch: steps=${steps} committed=${committed} steps_per_memory=${committed ? (steps / committed).toFixed(2) : "n/a"} elapsed_ms=${Date.now() - startedAt}`);
14182
+ }
14038
14183
  function computeMapBatchSliceMs(remainingMs, batchesRemaining) {
14039
14184
  return Math.min(remainingMs, Math.max(MAP_BATCH_FLOOR_MS, Math.floor(remainingMs / batchesRemaining)));
14040
14185
  }
@@ -14056,7 +14201,7 @@ function toMapInput(memory, repoDir) {
14056
14201
  candidates: extractMemoryCandidatePaths(memory.content, repoDir)
14057
14202
  };
14058
14203
  }
14059
- function selectMapMemoryInputs(db, projectIdentity, repoDir) {
14204
+ function selectMapMemoryInputs(db, projectIdentity, repoDir, includeDeferred = false) {
14060
14205
  const active = getMemoriesByProject2(db, projectIdentity);
14061
14206
  const activeIds = active.map((m) => m.id);
14062
14207
  const unmapped = new Set(getUnmappedMemoryIds(db, activeIds));
@@ -14075,7 +14220,8 @@ function selectMapMemoryInputs(db, projectIdentity, repoDir) {
14075
14220
  if (requeue.length >= MAX_INDEPENDENT_REQUEUE_PER_RUN)
14076
14221
  break;
14077
14222
  }
14078
- return [...unmappedInputs, ...requeue];
14223
+ const retries = readRetries(db, projectIdentity);
14224
+ return [...unmappedInputs, ...requeue].filter((memory) => includeDeferred || !isDeferred(memory, retries));
14079
14225
  }
14080
14226
  async function mapMemories(args) {
14081
14227
  const result = {
@@ -14085,14 +14231,21 @@ async function mapMemories(args) {
14085
14231
  remaining: 0,
14086
14232
  complete: true
14087
14233
  };
14088
- const inputs = selectMapMemoryInputs(args.db, args.projectIdentity, args.sessionDirectory);
14234
+ const allInputs = selectMapMemoryInputs(args.db, args.projectIdentity, args.sessionDirectory, true);
14235
+ const retries = readRetries(args.db, args.projectIdentity);
14236
+ const activeIds = new Set(allInputs.map((memory) => String(memory.id)));
14237
+ for (const id of Object.keys(retries))
14238
+ if (!activeIds.has(id))
14239
+ delete retries[id];
14240
+ const inputs = allInputs.filter((memory) => !isDeferred(memory, retries));
14241
+ result.remaining = allInputs.length;
14242
+ result.complete = result.remaining === 0;
14089
14243
  if (inputs.length === 0)
14090
14244
  return result;
14091
14245
  const batches = [];
14092
14246
  for (let i = 0;i < inputs.length; i += MAP_BATCH_SIZE) {
14093
14247
  batches.push({ inputs: inputs.slice(i, i + MAP_BATCH_SIZE), isOmissionRetry: false });
14094
14248
  }
14095
- result.remaining = inputs.length;
14096
14249
  const abortController = new AbortController;
14097
14250
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
14098
14251
  try {
@@ -14115,6 +14268,24 @@ async function mapMemories(args) {
14115
14268
  break;
14116
14269
  const outcome = await mapOneBatch(args, batch.inputs, sliceMs, abortController.signal);
14117
14270
  const committed = outcome.mapped + outcome.independent;
14271
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
14272
+ const committedIds = new Set(outcome.committedIds);
14273
+ for (const memory of batch.inputs) {
14274
+ if (committedIds.has(memory.id)) {
14275
+ delete retries[memory.id];
14276
+ continue;
14277
+ }
14278
+ const content = contentKey(memory);
14279
+ const prior = retries[memory.id];
14280
+ const failures = (prior?.content === content ? prior.failures : 0) + 1;
14281
+ retries[memory.id] = {
14282
+ content,
14283
+ failures,
14284
+ deferUntil: failures < 2 ? 0 : Date.now() + Math.min(24, 2 ** (failures - 2)) * 3600000
14285
+ };
14286
+ }
14287
+ setDreamState(args.db, retryKey(args.projectIdentity), JSON.stringify(retries));
14288
+ });
14118
14289
  result.mapped += outcome.mapped;
14119
14290
  result.independent += outcome.independent;
14120
14291
  result.remaining -= committed;
@@ -14124,8 +14295,8 @@ async function mapMemories(args) {
14124
14295
  }
14125
14296
  if (outcome.requeue?.length) {
14126
14297
  if (!batch.isOmissionRetry) {
14127
- batches.splice(i + 1, 0, {
14128
- inputs: outcome.requeue,
14298
+ batches.push({
14299
+ inputs: outcome.requeue.filter((memory) => !isDeferred(memory, retries)),
14129
14300
  isOmissionRetry: true
14130
14301
  });
14131
14302
  log2(`[dreamer] map-memories: committed ${committed}/${batch.inputs.length} mapping(s); requeueing ${outcome.requeue.length} omitted id(s) in a targeted retry`);
@@ -14133,12 +14304,12 @@ async function mapMemories(args) {
14133
14304
  log2(`[dreamer] map-memories: targeted retry still omitted ${outcome.requeue.length} id(s); leaving them unmapped for the next run`);
14134
14305
  }
14135
14306
  }
14136
- if (outcome.failure?.class === "timeout") {
14307
+ if (outcome.failure?.class === "timeout" && committed === 0) {
14137
14308
  consecutiveTimeouts += 1;
14138
14309
  timeoutStreakElapsedMs.push(outcome.failure.elapsedMs);
14139
14310
  if (consecutiveTimeouts >= CONSECUTIVE_TIMEOUT_LIMIT2) {
14140
14311
  result.stopReason = "timeout-circuit-breaker";
14141
- log2(`[dreamer] map-memories starvation: circuit breaker tripped after ${consecutiveTimeouts} consecutive batch timeouts (model too slow for its time slice); per-batch elapsed [${timeoutStreakElapsedMs.join("ms, ")}ms] vs ${sliceMs}ms slice; stopping with ${result.remaining} mapping(s) remaining`);
14312
+ log2(`[dreamer] map-memories starvation: circuit breaker tripped after ${consecutiveTimeouts} consecutive batch timeouts with no committed mappings (investigation made no durable progress); per-batch elapsed [${timeoutStreakElapsedMs.join("ms, ")}ms] vs ${sliceMs}ms slice; stopping with ${result.remaining} mapping(s) remaining`);
14142
14313
  break;
14143
14314
  }
14144
14315
  } else {
@@ -14158,12 +14329,13 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
14158
14329
  let promptSettled = false;
14159
14330
  const startedAt = Date.now();
14160
14331
  let budgetFinalized = false;
14332
+ let failedCompletion;
14161
14333
  const onBudgetUpdate = (state) => {
14162
14334
  budgetFinalized ||= state.finalizeFired;
14163
14335
  args.onBudgetUpdate?.(state);
14164
14336
  };
14165
14337
  try {
14166
- const prompt = buildMapMemoriesPrompt(args.projectIdentity, batch);
14338
+ const prompt = buildMapMemoriesPrompt(args.projectIdentity, await seedIdentifierCandidates(batch, args.sessionDirectory));
14167
14339
  if (args.hiddenCompletionExecutor) {
14168
14340
  const run = await runHiddenSingleShotPrompt({
14169
14341
  executor: args.hiddenCompletionExecutor,
@@ -14179,14 +14351,18 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
14179
14351
  fallbackModels: args.fallbackModels,
14180
14352
  timeoutMs: sliceMs,
14181
14353
  signal,
14182
- parse: (text) => validateMapMemoriesManifest(text, new Set(batch.map((memory) => memory.id)))
14354
+ onFailureOutput: (completion) => {
14355
+ failedCompletion = completion;
14356
+ },
14357
+ parse: (text, completion) => validateMapMemoriesManifest(currentManifest(assistantText(completion.messages ?? []) || text), new Set(batch.map((memory) => memory.id)))
14183
14358
  });
14184
14359
  recordInvocation2(args, startedAt, {
14185
14360
  status: "completed",
14186
14361
  messages: run.completion.messages ?? []
14187
14362
  });
14188
- const outcome = await applyParsedBatchMappings(args, batch, run.validated, budgetFinalized);
14189
- const returnedIds = new Set(run.validated.map((entry) => entry.id));
14363
+ const outcome = await applyParsedBatchMappings(args, batch, run.validated, budgetFinalized, true);
14364
+ logBatchUsage(run.completion.messages ?? [], outcome.mapped + outcome.independent, startedAt);
14365
+ const returnedIds = new Set(outcome.committedIds);
14190
14366
  return {
14191
14367
  ...outcome,
14192
14368
  requeue: budgetFinalized ? [] : batch.filter((memory) => !returnedIds.has(memory.id))
@@ -14229,7 +14405,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
14229
14405
  fetchOutput: async () => {
14230
14406
  const messagesResponse = await client.session.messages({
14231
14407
  path: { id: agentSessionId },
14232
- query: { directory: args.sessionDirectory, limit: 100 }
14408
+ query: { directory: args.sessionDirectory, limit: 200 }
14233
14409
  });
14234
14410
  return normalizeSDKResponse(messagesResponse, [], {
14235
14411
  preferResponseOnMissingData: true
@@ -14239,7 +14415,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
14239
14415
  if (hasLengthCappedOutput(messages)) {
14240
14416
  throw new Error("map-memories returned length-capped output");
14241
14417
  }
14242
- const text = extractLatestAssistantText(messages);
14418
+ const text = currentManifest(assistantText(messages));
14243
14419
  if (!text)
14244
14420
  throw new Error("map-memories returned no output");
14245
14421
  return validateMapMemoriesManifest(text, new Set(batch.map((memory) => memory.id)));
@@ -14247,8 +14423,9 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
14247
14423
  });
14248
14424
  promptSettled = true;
14249
14425
  recordInvocation2(args, startedAt, { status: "completed", messages: run.output });
14250
- const outcome = await applyParsedBatchMappings(args, batch, run.validated, budgetFinalized);
14251
- const returnedIds = new Set(run.validated.filter((entry) => batch.some((memory) => memory.id === entry.id)).map((entry) => entry.id));
14426
+ const outcome = await applyParsedBatchMappings(args, batch, run.validated, budgetFinalized, true);
14427
+ logBatchUsage(run.output, outcome.mapped + outcome.independent, startedAt);
14428
+ const returnedIds = new Set(outcome.committedIds);
14252
14429
  return {
14253
14430
  ...outcome,
14254
14431
  requeue: budgetFinalized ? [] : batch.filter((memory) => !returnedIds.has(memory.id))
@@ -14256,18 +14433,23 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
14256
14433
  } catch (error) {
14257
14434
  const desc = describeError(error);
14258
14435
  log2(`[dreamer] map-memories batch failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
14436
+ const messages = failedCompletion?.messages ?? await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory) ?? [];
14259
14437
  recordInvocation2(args, startedAt, {
14260
14438
  status: failedInvocationStatus(error),
14261
14439
  error,
14262
- messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
14440
+ messages
14263
14441
  });
14264
- if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded)
14442
+ if (error instanceof DreamerModuleFailureError)
14265
14443
  throw error;
14266
14444
  if (signal.aborted)
14267
14445
  throw error;
14446
+ const recovered = parsePartialMapMemoriesManifest(assistantText(messages) || failedCompletion?.text || "", new Set(batch.map((memory) => memory.id)));
14447
+ const outcome = await applyParsedBatchMappings(args, batch, recovered, budgetFinalized, true);
14448
+ logBatchUsage(messages, outcome.mapped + outcome.independent, startedAt);
14449
+ if (error instanceof DreamTokenBudgetExceeded)
14450
+ throw error;
14268
14451
  return {
14269
- mapped: 0,
14270
- independent: 0,
14452
+ ...outcome,
14271
14453
  failure: {
14272
14454
  class: isTimeoutClassError2(error) ? "timeout" : "other",
14273
14455
  elapsedMs: Date.now() - startedAt
@@ -14286,7 +14468,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
14286
14468
  });
14287
14469
  }
14288
14470
  }
14289
- async function applyParsedBatchMappings(args, batch, parsed, budgetFinalized = false) {
14471
+ async function applyParsedBatchMappings(args, batch, parsed, budgetFinalized = false, allowPartial = false) {
14290
14472
  const batchIds = new Set(batch.map((memory) => memory.id));
14291
14473
  const valid = parsed.filter((entry) => batchIds.has(entry.id));
14292
14474
  const unknown = parsed.filter((entry) => !batchIds.has(entry.id));
@@ -14294,14 +14476,14 @@ async function applyParsedBatchMappings(args, batch, parsed, budgetFinalized = f
14294
14476
  log2(`[dreamer] map-memories warning: dropping ${unknown.length} unknown mapping entr${unknown.length === 1 ? "y" : "ies"} outside the current batch (${unknown.map((entry) => entry.id).join(", ")})`);
14295
14477
  }
14296
14478
  assertNoDuplicateManifestIds(valid.map((entry) => entry.id), "mappings");
14297
- if (!budgetFinalized && valid.length * 2 < batch.length) {
14479
+ if (!allowPartial && !budgetFinalized && valid.length * 2 < batch.length) {
14298
14480
  throw new Error(`mappings manifest covers ${valid.length}/${batch.length} batch ids after filtering unknown entries; rejecting mostly-wrong manifest`);
14299
14481
  }
14300
14482
  if (budgetFinalized && valid.length < batch.length) {
14301
14483
  log2(`[dreamer] map-memories: accepted partial manifest after token budget: ${valid.length}/${batch.length}`);
14302
14484
  }
14303
14485
  if (valid.length === 0)
14304
- return { mapped: 0, independent: 0 };
14486
+ return { mapped: 0, independent: 0, committedIds: [] };
14305
14487
  const planned = [];
14306
14488
  const batchById = new Map(batch.map((memory) => [memory.id, memory]));
14307
14489
  for (const p of valid) {
@@ -14350,9 +14532,10 @@ async function applyParsedBatchMappings(args, batch, parsed, budgetFinalized = f
14350
14532
  });
14351
14533
  }
14352
14534
  if (planned.length === 0)
14353
- return { mapped: 0, independent: 0 };
14535
+ return { mapped: 0, independent: 0, committedIds: [] };
14354
14536
  let mapped = 0;
14355
14537
  let independent = 0;
14538
+ const committedIds = [];
14356
14539
  if (args.moduleRoute) {
14357
14540
  const identities = getModuleMemoryIdentities(args.db, args.projectIdentity, planned.map((item) => item.id));
14358
14541
  const rows = planned.map((item) => {
@@ -14390,19 +14573,22 @@ async function applyParsedBatchMappings(args, batch, parsed, budgetFinalized = f
14390
14573
  const accepted = new Set(result.accepted.filter((id) => typeof id === "number"));
14391
14574
  for (const item of planned) {
14392
14575
  const identity = identities.get(item.id);
14393
- if (identity && accepted.has(identity.moduleId))
14576
+ if (identity && accepted.has(identity.moduleId)) {
14394
14577
  item.independent ? independent += 1 : mapped += 1;
14578
+ committedIds.push(item.id);
14579
+ }
14395
14580
  }
14396
- return { mapped, independent };
14581
+ return { mapped, independent, committedIds };
14397
14582
  }
14398
14583
  const now = Date.now();
14399
14584
  runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
14400
14585
  for (const item of planned) {
14401
14586
  recordMemoryMapping(args.db, item.id, item.files, now, item.mappingOrigin);
14402
14587
  item.independent ? independent += 1 : mapped += 1;
14588
+ committedIds.push(item.id);
14403
14589
  }
14404
14590
  });
14405
- return { mapped, independent };
14591
+ return { mapped, independent, committedIds };
14406
14592
  }
14407
14593
  function recordInvocation2(args, startedAt, params) {
14408
14594
  if (!args.parentSessionId)
@@ -14670,32 +14856,41 @@ async function promotePrimers(args) {
14670
14856
  // ../plugin/src/features/magic-context/dreamer/primer-seed.ts
14671
14857
  var PRIMER_SEED_CAP_TOKENS = 4000;
14672
14858
  function renderUserAndToolOrientation(sessionId, startOrdinal, endOrdinal, capTokens) {
14859
+ const renderer = orientationRenderer(capTokens);
14860
+ visitRawSessionMessages2(sessionId, startOrdinal, endOrdinal, renderer.visit, {
14861
+ summary: true
14862
+ });
14863
+ return renderer.result();
14864
+ }
14865
+ function orientationRenderer(capTokens) {
14673
14866
  const lines = [];
14674
14867
  let tokens = 0;
14675
14868
  let sawMessage = false;
14676
- visitRawSessionMessages2(sessionId, startOrdinal, endOrdinal, (msg) => {
14677
- sawMessage = true;
14678
- const out = [];
14679
- if (msg.role === "user" && hasMeaningfulUserText2(msg.parts)) {
14680
- const text = extractTexts2(msg.parts).map((t) => cleanUserText2(t)).map(normalizeText).filter((t) => t.length > 0).join(" / ");
14681
- if (text)
14682
- out.push(`U: ${text}`);
14683
- }
14684
- for (const tc of extractToolCallSummaries(msg.parts))
14685
- out.push(tc);
14686
- for (const line of out) {
14687
- const lineTokens = estimateTokens(line);
14688
- if (tokens + lineTokens > capTokens && lines.length > 0) {
14689
- lines.push("… (orientation truncated; investigate the current source directly)");
14690
- return false;
14869
+ return {
14870
+ visit: (msg) => {
14871
+ sawMessage = true;
14872
+ const out = [];
14873
+ if (msg.role === "user" && hasMeaningfulUserText2(msg.parts)) {
14874
+ const text = extractTexts2(msg.parts).map((t) => cleanUserText2(t)).map(normalizeText).filter((t) => t.length > 0).join(" / ");
14875
+ if (text)
14876
+ out.push(`U: ${text}`);
14877
+ }
14878
+ for (const tc of extractToolCallSummaries(msg.parts))
14879
+ out.push(tc);
14880
+ for (const line of out) {
14881
+ const lineTokens = estimateTokens(line);
14882
+ if (tokens + lineTokens > capTokens && lines.length > 0) {
14883
+ lines.push("… (orientation truncated; investigate the current source directly)");
14884
+ return false;
14885
+ }
14886
+ lines.push(line);
14887
+ tokens += lineTokens;
14691
14888
  }
14692
- lines.push(line);
14693
- tokens += lineTokens;
14694
- }
14695
- return true;
14696
- }, { summary: true });
14697
- return sawMessage ? lines.join(`
14698
- `) : null;
14889
+ return true;
14890
+ },
14891
+ result: () => sawMessage ? lines.join(`
14892
+ `) : null
14893
+ };
14699
14894
  }
14700
14895
  function selectPrimerOriginCandidate(db, primer) {
14701
14896
  return getPrimerCandidatesByIds(db, primer.sourceCandidateIds).filter((candidate) => candidate.projectPath === primer.projectPath).sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
@@ -14729,16 +14924,37 @@ function closedBookOriginP1(db, sessionId, originStartMessage) {
14729
14924
  return { orientation, sessionId };
14730
14925
  }
14731
14926
  function buildPrimerSeed(db, primer) {
14927
+ return buildPrimerSeedUsing(db, selectPrimerOriginCandidate(db, primer), renderUserAndToolOrientation);
14928
+ }
14929
+ async function buildPrimerSeedAsync(db, primer) {
14732
14930
  const mostRecent = selectPrimerOriginCandidate(db, primer);
14931
+ let orientation = null;
14932
+ if (mostRecent && typeof mostRecent.sourceCompartmentStart === "number" && typeof mostRecent.sourceCompartmentEnd === "number") {
14933
+ try {
14934
+ const renderer = orientationRenderer(PRIMER_SEED_CAP_TOKENS);
14935
+ await visitRawSessionMessagesAsync2(mostRecent.sessionId, mostRecent.sourceCompartmentStart, mostRecent.sourceCompartmentEnd, renderer.visit, { summary: true });
14936
+ orientation = renderer.result();
14937
+ } catch {
14938
+ orientation = null;
14939
+ }
14940
+ }
14941
+ return buildPrimerSeedUsing(db, mostRecent, () => orientation);
14942
+ }
14943
+ function buildPrimerSeedUsing(db, mostRecent, render) {
14733
14944
  if (!mostRecent || typeof mostRecent.sourceCompartmentStart !== "number" || typeof mostRecent.sourceCompartmentEnd !== "number") {
14734
- return { kind: "closed-book", orientation: "", prePost: "", sessionId: null };
14945
+ return {
14946
+ kind: "closed-book",
14947
+ orientation: "",
14948
+ prePost: "",
14949
+ sessionId: null
14950
+ };
14735
14951
  }
14736
14952
  const sessionId = mostRecent.sessionId;
14737
14953
  const start = mostRecent.sourceCompartmentStart;
14738
14954
  const end = mostRecent.sourceCompartmentEnd;
14739
14955
  let orientation = null;
14740
14956
  try {
14741
- orientation = renderUserAndToolOrientation(sessionId, start, end, PRIMER_SEED_CAP_TOKENS);
14957
+ orientation = render(sessionId, start, end, PRIMER_SEED_CAP_TOKENS);
14742
14958
  } catch {
14743
14959
  orientation = null;
14744
14960
  }
@@ -14869,14 +15085,14 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
14869
15085
  provider = null;
14870
15086
  }
14871
15087
  }
14872
- const seed = withRawSessionMessageCache2(() => {
15088
+ const seed = await (async () => {
14873
15089
  const unregister = provider && originSessionId ? setRawMessageProvider2(originSessionId, provider) : null;
14874
15090
  try {
14875
- return buildPrimerSeed(args.db, primer);
15091
+ return provider?.iterateMessageRangeAsync ? await buildPrimerSeedAsync(args.db, primer) : withRawSessionMessageCache2(() => buildPrimerSeed(args.db, primer));
14876
15092
  } finally {
14877
15093
  unregister?.();
14878
15094
  }
14879
- });
15095
+ })();
14880
15096
  let agentSessionId = null;
14881
15097
  let promptSettled = false;
14882
15098
  const startedAt = Date.now();
@@ -14892,7 +15108,10 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
14892
15108
  prompt,
14893
15109
  title: "magic-context-dream-refresh-primers",
14894
15110
  callContext: "dreamer:refresh-primers",
14895
- metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
15111
+ metadata: {
15112
+ tokenBudget: args.tokenBudget,
15113
+ onBudgetUpdate: args.onBudgetUpdate
15114
+ },
14896
15115
  model: args.model,
14897
15116
  fallbackModels: args.fallbackModels,
14898
15117
  language: args.language,
@@ -14958,7 +15177,10 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
14958
15177
  validateOutput: (messages) => parseAnswer(messages, primer.answer)
14959
15178
  });
14960
15179
  promptSettled = true;
14961
- recordInvocation3(args, startedAt, { status: "completed", messages: run.output });
15180
+ recordInvocation3(args, startedAt, {
15181
+ status: "completed",
15182
+ messages: run.output
15183
+ });
14962
15184
  const answer = run.validated.trim();
14963
15185
  if (!answer)
14964
15186
  return false;
@@ -14973,7 +15195,10 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
14973
15195
  } catch (error) {
14974
15196
  const desc = describeError(error);
14975
15197
  log2(`[dreamer] refresh-primers failed (primer #${primer.id}): ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
14976
- recordInvocation3(args, startedAt, { status: failedInvocationStatus(error), error });
15198
+ recordInvocation3(args, startedAt, {
15199
+ status: failedInvocationStatus(error),
15200
+ error
15201
+ });
14977
15202
  throw error;
14978
15203
  } finally {
14979
15204
  if (agentSessionId && args.client)
@@ -15587,9 +15812,9 @@ function insertDreamRun(db, run) {
15587
15812
  import { createHash as createHash9 } from "node:crypto";
15588
15813
 
15589
15814
  // ../plugin/src/features/magic-context/dreamer/verify-diff.ts
15590
- import { execFile as execFile2 } from "node:child_process";
15591
- import { promisify as promisify2 } from "node:util";
15592
- var exec = promisify2(execFile2);
15815
+ import { execFile as execFile3 } from "node:child_process";
15816
+ import { promisify as promisify3 } from "node:util";
15817
+ var exec = promisify3(execFile3);
15593
15818
  var VERIFY_DIFF_BUDGET_TOKENS = 12000;
15594
15819
  var MAX_DIFF_BYTES = 4 * 1024 * 1024;
15595
15820
  async function git2(root, args) {
@@ -19906,11 +20131,19 @@ class PiSubagentRunner {
19906
20131
  });
19907
20132
  return new Promise((resolve) => {
19908
20133
  let accountingMessages = [];
20134
+ let mappingMessages = [];
20135
+ const isMemoryMapper = options.agent.replace(/^magic-context-/, "") === "dreamer-memory-mapper";
19909
20136
  let settled = false;
19910
20137
  const settle = (result) => {
19911
20138
  if (settled)
19912
20139
  return;
19913
20140
  settled = true;
20141
+ if (isMemoryMapper) {
20142
+ result = {
20143
+ ...result,
20144
+ meta: { ...result.meta, mappingMessages: [...mappingMessages] }
20145
+ };
20146
+ }
19914
20147
  if (accountingMessages.length > 0 && /historian|dreamer|classify/i.test(options.agent)) {
19915
20148
  const last = [...accountingMessages].reverse().find((message) => message !== null && typeof message === "object" && message.role === "assistant");
19916
20149
  const responseChars = result.ok ? result.assistantText.length : last?.content?.filter((part) => typeof part === "object" && part !== null && part.type === "text" && typeof part.text === "string").reduce((sum, part) => sum + part.text.length, 0) ?? 0;
@@ -20103,6 +20336,12 @@ class PiSubagentRunner {
20103
20336
  if (typeof event !== "object" || event === null)
20104
20337
  return;
20105
20338
  const e = event;
20339
+ if (isMemoryMapper) {
20340
+ if (e.type === "message_end" && e.message)
20341
+ mappingMessages.push(e.message);
20342
+ if (e.type === "agent_end" && Array.isArray(e.messages))
20343
+ mappingMessages = e.messages;
20344
+ }
20106
20345
  if (rpcBudget && e.type === "response" && event.success === false) {
20107
20346
  const reply = event;
20108
20347
  if (reply.command === "prompt" || reply.command === "steer" && !sawAgentEnd) {
@@ -20722,7 +20961,9 @@ import { join as join13 } from "node:path";
20722
20961
 
20723
20962
  // src/dreamer/bounded-session-reader.ts
20724
20963
  import { closeSync, opendirSync, openSync, readSync } from "node:fs";
20964
+ import { open as open4, opendir } from "node:fs/promises";
20725
20965
  import { join as join11 } from "node:path";
20966
+ import { setImmediate as yieldToHost } from "node:timers/promises";
20726
20967
  var MAX_ENTRY_BYTES = 1024 * 1024;
20727
20968
  var OVERSIZED_HEAD_BYTES = 64 * 1024;
20728
20969
  var OVERSIZED_TAIL_BYTES = 4 * 1024;
@@ -20793,86 +21034,107 @@ function lastMatch(text, pattern) {
20793
21034
  last = match[1];
20794
21035
  return last;
20795
21036
  }
21037
+
21038
+ class LineDecoder {
21039
+ pending = Buffer.alloc(0);
21040
+ oversized = null;
21041
+ *append(buffer) {
21042
+ const bytes = buffer.length;
21043
+ let start = 0;
21044
+ for (let i = 0;i < bytes; i++) {
21045
+ if (buffer[i] !== 10)
21046
+ continue;
21047
+ const piece = buffer.subarray(start, i);
21048
+ if (this.oversized) {
21049
+ yield this.oversized.append(piece).stub();
21050
+ this.oversized = null;
21051
+ } else if (this.pending.length + piece.length > MAX_ENTRY_BYTES) {
21052
+ yield new OversizedLine().append(this.pending).append(piece).stub();
21053
+ } else {
21054
+ yield Buffer.concat([this.pending, piece]).toString("utf8");
21055
+ }
21056
+ this.pending = Buffer.alloc(0);
21057
+ start = i + 1;
21058
+ }
21059
+ const rest = buffer.subarray(start, bytes);
21060
+ if (this.oversized) {
21061
+ this.oversized.append(rest);
21062
+ } else if (this.pending.length + rest.length > MAX_ENTRY_BYTES) {
21063
+ this.oversized = new OversizedLine().append(this.pending).append(rest);
21064
+ this.pending = Buffer.alloc(0);
21065
+ } else {
21066
+ this.pending = Buffer.concat([this.pending, rest]);
21067
+ }
21068
+ }
21069
+ *finish() {
21070
+ if (this.oversized)
21071
+ yield this.oversized.stub();
21072
+ else if (this.pending.length)
21073
+ yield this.pending.toString("utf8");
21074
+ }
21075
+ }
20796
21076
  function* lines(path) {
20797
21077
  const fd = openSync(path, "r");
20798
21078
  const buffer = Buffer.alloc(64 * 1024);
20799
- let pending = Buffer.alloc(0);
20800
- let oversized = null;
21079
+ const decoder = new LineDecoder;
20801
21080
  try {
20802
21081
  while (true) {
20803
21082
  const bytes = readSync(fd, buffer, 0, buffer.length, null);
20804
21083
  if (bytes === 0)
20805
21084
  break;
20806
- let start = 0;
20807
- for (let i = 0;i < bytes; i++) {
20808
- if (buffer[i] !== 10)
20809
- continue;
20810
- const piece = buffer.subarray(start, i);
20811
- if (oversized) {
20812
- yield oversized.append(piece).stub();
20813
- oversized = null;
20814
- } else if (pending.length + piece.length > MAX_ENTRY_BYTES) {
20815
- yield new OversizedLine().append(pending).append(piece).stub();
20816
- } else {
20817
- yield Buffer.concat([pending, piece]).toString("utf8");
20818
- }
20819
- pending = Buffer.alloc(0);
20820
- start = i + 1;
20821
- }
20822
- const rest = buffer.subarray(start, bytes);
20823
- if (oversized) {
20824
- oversized.append(rest);
20825
- } else if (pending.length + rest.length > MAX_ENTRY_BYTES) {
20826
- oversized = new OversizedLine().append(pending).append(rest);
20827
- pending = Buffer.alloc(0);
20828
- } else {
20829
- pending = Buffer.concat([pending, rest]);
20830
- }
21085
+ yield* decoder.append(buffer.subarray(0, bytes));
20831
21086
  }
20832
- if (oversized)
20833
- yield oversized.stub();
20834
- else if (pending.length)
20835
- yield pending.toString("utf8");
21087
+ yield* decoder.finish();
20836
21088
  } finally {
20837
21089
  closeSync(fd);
20838
21090
  }
20839
21091
  }
21092
+ async function* asyncLines(path) {
21093
+ const file = await open4(path, "r");
21094
+ const buffer = Buffer.alloc(64 * 1024);
21095
+ const decoder = new LineDecoder;
21096
+ try {
21097
+ while (true) {
21098
+ const { bytesRead } = await file.read(buffer, 0, buffer.length, null);
21099
+ if (bytesRead === 0)
21100
+ break;
21101
+ yield* decoder.append(buffer.subarray(0, bytesRead));
21102
+ await yieldToHost();
21103
+ }
21104
+ yield* decoder.finish();
21105
+ } finally {
21106
+ await file.close();
21107
+ }
21108
+ }
20840
21109
  function record(value) {
20841
21110
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
20842
21111
  }
20843
- function* sessionFiles(directory, nested) {
21112
+ async function* asyncSessionEntries(path) {
21113
+ for await (const line of asyncLines(path)) {
21114
+ try {
21115
+ yield JSON.parse(line);
21116
+ } catch {}
21117
+ }
21118
+ }
21119
+ async function* asyncSessionFiles(directory, nested) {
20844
21120
  let dir;
20845
21121
  try {
20846
- dir = opendirSync(directory);
21122
+ dir = await opendir(directory);
20847
21123
  } catch {
20848
21124
  return;
20849
21125
  }
20850
- try {
20851
- while (true) {
20852
- const entry = dir.readSync();
20853
- if (!entry)
20854
- break;
20855
- const path = join11(directory, entry.name);
20856
- if (entry.isFile() && entry.name.endsWith(".jsonl"))
20857
- yield path;
20858
- else if (nested && entry.isDirectory())
20859
- yield* sessionFiles(path, false);
20860
- }
20861
- } finally {
20862
- dir.closeSync();
20863
- }
20864
- }
20865
- function findSession(directory, nested, sessionId) {
20866
- for (const header of sessionHeaders(directory, nested)) {
20867
- if (header.id === sessionId)
20868
- return header.path;
21126
+ for await (const entry of dir) {
21127
+ const path = join11(directory, entry.name);
21128
+ if (entry.isFile() && entry.name.endsWith(".jsonl"))
21129
+ yield path;
21130
+ else if (nested && entry.isDirectory())
21131
+ yield* asyncSessionFiles(path, false);
20869
21132
  }
20870
- return null;
20871
21133
  }
20872
- function* sessionHeaders(directory, nested) {
20873
- for (const path of sessionFiles(directory, nested)) {
21134
+ async function* asyncSessionHeaders(directory, nested) {
21135
+ for await (const path of asyncSessionFiles(directory, nested)) {
20874
21136
  try {
20875
- for (const line of lines(path)) {
21137
+ for await (const line of asyncLines(path)) {
20876
21138
  const header = record(JSON.parse(line));
20877
21139
  if (header.type === "session")
20878
21140
  yield { ...header, path };
@@ -20881,13 +21143,6 @@ function* sessionHeaders(directory, nested) {
20881
21143
  } catch {}
20882
21144
  }
20883
21145
  }
20884
- function* sessionEntries(path) {
20885
- for (const line of lines(path)) {
20886
- try {
20887
- yield JSON.parse(line);
20888
- } catch {}
20889
- }
20890
- }
20891
21146
 
20892
21147
  // src/dreamer/pi-session-api.ts
20893
21148
  import { existsSync as existsSync11, readFileSync as readFileSync10, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
@@ -21154,15 +21409,9 @@ function projectEntry(value) {
21154
21409
  }
21155
21410
  };
21156
21411
  }
21157
- function* summaryEntries(path) {
21412
+ function summaryProjector() {
21158
21413
  let pendingResults = 0;
21159
- for (const line of lines(path)) {
21160
- let parsed;
21161
- try {
21162
- parsed = JSON.parse(line);
21163
- } catch {
21164
- continue;
21165
- }
21414
+ return (parsed) => {
21166
21415
  const entry = projectEntry(parsed);
21167
21416
  const e = record2(entry);
21168
21417
  if (e.type === "message") {
@@ -21173,7 +21422,30 @@ function* summaryEntries(path) {
21173
21422
  } else if (m.role === "user" || m.role === "assistant")
21174
21423
  pendingResults = 0;
21175
21424
  }
21176
- yield entry;
21425
+ return entry;
21426
+ };
21427
+ }
21428
+ function* summaryEntries(path) {
21429
+ const project = summaryProjector();
21430
+ for (const line of lines(path)) {
21431
+ let entry;
21432
+ try {
21433
+ entry = JSON.parse(line);
21434
+ } catch {}
21435
+ if (entry !== undefined)
21436
+ yield project(entry);
21437
+ }
21438
+ }
21439
+ async function* asyncSummaryEntries(path) {
21440
+ const project = summaryProjector();
21441
+ for await (const line of asyncLines(path)) {
21442
+ let entry;
21443
+ try {
21444
+ entry = JSON.parse(line);
21445
+ } catch {
21446
+ continue;
21447
+ }
21448
+ yield project(entry);
21177
21449
  }
21178
21450
  }
21179
21451
  function createPiPrimerRawProviderFactory(deps = {}) {
@@ -21186,13 +21458,23 @@ function createPiPrimerRawProviderFactory(deps = {}) {
21186
21458
  return null;
21187
21459
  directory = join13(mod.getAgentDir(), "sessions");
21188
21460
  }
21189
- const path = findSession(directory, !deps.sessionDir, sessionId);
21461
+ let path = null;
21462
+ for await (const header of asyncSessionHeaders(directory, !deps.sessionDir)) {
21463
+ if (header.id === sessionId) {
21464
+ path = header.path;
21465
+ break;
21466
+ }
21467
+ }
21190
21468
  if (!path)
21191
21469
  return null;
21470
+ const sessionPath = path;
21192
21471
  return {
21193
21472
  readMessages() {
21194
21473
  throw new Error("Pi primer history requires bounded pages");
21195
21474
  },
21475
+ iterateMessageRangeAsync(from, to) {
21476
+ return iterateAsyncEntriesToRawMessageRange(asyncSummaryEntries(sessionPath), from - 1, Number.MAX_SAFE_INTEGER, to);
21477
+ },
21196
21478
  iterateMessageRange(from, to) {
21197
21479
  return iterateEntriesToRawMessageRange(summaryEntries(path), from - 1, Number.MAX_SAFE_INTEGER, to);
21198
21480
  },
@@ -21207,6 +21489,7 @@ function createPiPrimerRawProviderFactory(deps = {}) {
21207
21489
  }
21208
21490
 
21209
21491
  // src/dreamer/retrospective-raw-provider-pi.ts
21492
+ import { stat as stat2 } from "node:fs/promises";
21210
21493
  import { join as join14 } from "node:path";
21211
21494
  class PiRetrospectiveRawProvider {
21212
21495
  deps;
@@ -21222,11 +21505,11 @@ class PiRetrospectiveRawProvider {
21222
21505
  return [];
21223
21506
  directory = join14(mod.getAgentDir(), "sessions");
21224
21507
  }
21225
- const sessions = this.deps.listSessions ? await this.deps.listSessions(directory) : directory ? sessionHeaders(directory, !this.deps.sessionDir) : [];
21508
+ const sessions = this.deps.listSessions ? await this.deps.listSessions(directory) : directory ? asyncSessionHeaders(directory, !this.deps.sessionDir) : [];
21226
21509
  const projectCwd = this.deps.projectCwd;
21227
21510
  const result = [];
21228
21511
  this.sessionPathById.clear();
21229
- for (const raw of sessions) {
21512
+ for await (const raw of sessions) {
21230
21513
  const info = raw;
21231
21514
  if (!info || typeof info !== "object")
21232
21515
  continue;
@@ -21248,7 +21531,7 @@ class PiRetrospectiveRawProvider {
21248
21531
  }
21249
21532
  async readUserMessagesSince(sessionId, sinceMs, capPerSession) {
21250
21533
  const limit = Math.max(1, Math.floor(capPerSession));
21251
- const messages = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, limit + 1, false);
21534
+ const messages = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, limit + 1, false, sinceMs);
21252
21535
  return {
21253
21536
  messages: messages.slice(0, limit),
21254
21537
  truncated: messages.length > limit
@@ -21257,7 +21540,7 @@ class PiRetrospectiveRawProvider {
21257
21540
  async readOldestMessageTimesSince(sessionIds, sinceMs) {
21258
21541
  const out = new Map;
21259
21542
  for (const sessionId of sessionIds) {
21260
- const [oldest] = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, 1, false);
21543
+ const [oldest] = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, 1, false, sinceMs);
21261
21544
  if (oldest)
21262
21545
  out.set(sessionId, oldest.ts);
21263
21546
  }
@@ -21266,15 +21549,19 @@ class PiRetrospectiveRawProvider {
21266
21549
  async readUserMessagesBefore(sessionId, beforeMs, count) {
21267
21550
  return this.selectUserEntries(sessionId, (ts) => ts <= beforeMs, Math.max(1, Math.floor(count)), true);
21268
21551
  }
21269
- async selectUserEntries(sessionId, eligible, limit, newest) {
21552
+ async selectUserEntries(sessionId, eligible, limit, newest, sinceMs) {
21270
21553
  const filePath = this.sessionPathById.get(sessionId);
21271
21554
  if (!filePath)
21272
21555
  return [];
21273
21556
  const kept = [];
21274
21557
  try {
21275
- const entries = this.deps.loadEntriesFromFile ? await this.deps.loadEntriesFromFile(filePath) : sessionEntries(filePath);
21558
+ if (sinceMs !== undefined && !this.deps.loadEntriesFromFile) {
21559
+ if ((await stat2(filePath)).mtimeMs < sinceMs)
21560
+ return [];
21561
+ }
21562
+ const entries = this.deps.loadEntriesFromFile ? await this.deps.loadEntriesFromFile(filePath) : asyncSessionEntries(filePath);
21276
21563
  let ordinal = 0;
21277
- for (const entry of entries) {
21564
+ for await (const entry of entries) {
21278
21565
  const row = normalizePiUserEntry(entry, sessionId, ++ordinal);
21279
21566
  if (!row || !eligible(row.ts))
21280
21567
  continue;
@@ -21622,6 +21909,10 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
21622
21909
  });
21623
21910
  }
21624
21911
  assertRegistrationOwnerActive();
21912
+ const mappingMessages = result.meta?.mappingMessages;
21913
+ if (budgetTask === "map-memories" && Array.isArray(mappingMessages)) {
21914
+ dreamSession.messages = mappingMessages.filter(isRecord).filter((message) => message.role === "assistant").map((message) => makeMessage("assistant", Array.isArray(message.content) ? message.content.filter(isRecord).flatMap((part) => part.type === "text" && typeof part.text === "string" ? [{ type: "text", text: part.text }] : part.type === "toolCall" ? syntheticToolParts(1) : []) : []));
21915
+ }
21625
21916
  if (!result.ok) {
21626
21917
  if (result.reason === "token_budget") {
21627
21918
  throw new DreamTokenBudgetExceeded(dreamSession.id, Number(result.meta?.tokenBudget?.spent ?? 0));
@@ -21635,13 +21926,14 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
21635
21926
  }
21636
21927
  throw error;
21637
21928
  }
21638
- dreamSession.messages = [
21639
- makeMessage("user", [{ type: "text", text: userMessage }]),
21640
- makeMessage("assistant", [
21641
- ...syntheticToolParts(result.toolCallCount ?? 0, result.completedToolCalls),
21642
- ...result.assistantText ? [{ type: "text", text: result.assistantText }] : []
21643
- ])
21644
- ];
21929
+ if (!(budgetTask === "map-memories" && Array.isArray(mappingMessages)))
21930
+ dreamSession.messages = [
21931
+ makeMessage("user", [{ type: "text", text: userMessage }]),
21932
+ makeMessage("assistant", [
21933
+ ...syntheticToolParts(result.toolCallCount ?? 0, result.completedToolCalls),
21934
+ ...result.assistantText ? [{ type: "text", text: result.assistantText }] : []
21935
+ ])
21936
+ ];
21645
21937
  onAdjunctsRefreshNeeded?.(opts.projectIdentity);
21646
21938
  } finally {
21647
21939
  inFlightDreams.delete(runPromise);
@@ -22389,7 +22681,7 @@ function reconcilePiCompactionMode(args) {
22389
22681
  return NO_TRANSITION;
22390
22682
  let clearedSomething = false;
22391
22683
  if (getPendingPiCompactionMarkerState(args.db, args.sessionId) !== null) {
22392
- setPendingPiCompactionMarkerState(args.db, args.sessionId, null);
22684
+ setPendingPiCompactionMarkerState(args.db, args.sessionId, null, "compaction-disabled");
22393
22685
  clearedSomething = true;
22394
22686
  }
22395
22687
  if (getOverflowState(args.db, args.sessionId).needsEmergencyRecovery) {
@@ -23286,21 +23578,40 @@ function buildReplacementContent(tagId) {
23286
23578
  return `[dropped §${tagId}§]`;
23287
23579
  }
23288
23580
  function applyNewToolDrop(target, options) {
23289
- if (!target)
23581
+ if (options.persist && (target?.prepareDrop?.() ?? target?.canDrop?.()) === false) {
23582
+ return { result: "incomplete", mode: "full" };
23583
+ }
23584
+ if (!target) {
23585
+ options.persist?.("full");
23290
23586
  return { result: "absent", mode: "full" };
23291
- if (options.inWindow && hasSmallToolInput(target) || options.keepSkeleton === true || target.cannotRemove?.() === true) {
23587
+ }
23588
+ if (options.inWindow && hasSmallToolInput(target) || options.keepSkeleton === true || target.cannotRemove?.() === true || target.wouldStrandConversationEnd?.() === true) {
23292
23589
  const strip = target.hasAttachments?.() === true;
23590
+ options.persist?.(strip ? "skeleton_stripped" : "skeleton_real");
23293
23591
  return {
23294
23592
  result: (strip ? target.skeletonStripped?.() : target.skeletonReal?.()) ?? "absent",
23295
23593
  mode: strip ? "skeleton_stripped" : "skeleton_real"
23296
23594
  };
23297
23595
  }
23596
+ options.persist?.("full");
23298
23597
  const result = target.drop?.() ?? "absent";
23299
23598
  if (result === "truncated" && target.hasAttachments?.() === true) {
23300
23599
  return { result: target.skeletonStripped?.() ?? "absent", mode: "skeleton_stripped" };
23301
23600
  }
23302
23601
  return { result, mode: result === "truncated" ? "skeleton_real" : "full" };
23303
23602
  }
23603
+ function applyPersistedToolDrop(db, sessionId, tagNumber, target, options, afterWrite) {
23604
+ return applyNewToolDrop(target, {
23605
+ ...options,
23606
+ persist: (mode) => {
23607
+ db.transaction(() => {
23608
+ updateTagDropMode2(db, sessionId, tagNumber, mode);
23609
+ updateTagStatus2(db, sessionId, tagNumber, "dropped");
23610
+ afterWrite?.();
23611
+ }).immediate();
23612
+ }
23613
+ });
23614
+ }
23304
23615
  function hasSmallToolInput(target) {
23305
23616
  const bytes = target?.inputStringBytes?.() ?? null;
23306
23617
  return bytes !== null && bytes <= SKELETON_REAL_INPUT_MAX_BYTES;
@@ -23375,11 +23686,11 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23375
23686
  diagnostics.reasons[reason] = (diagnostics.reasons[reason] ?? 0) + 1;
23376
23687
  };
23377
23688
  let admitted = false;
23378
- let startedAt = 0;
23379
23689
  try {
23380
23690
  db.transaction(() => {
23381
23691
  admitted = true;
23382
- startedAt = performance.now();
23692
+ }).immediate();
23693
+ {
23383
23694
  const pendingOps = preloadedPendingOps ?? getPendingOps(db, sessionId);
23384
23695
  const tags = preloadedTags ?? getTagsByNumbers2(db, sessionId, [
23385
23696
  ...new Set([
@@ -23431,10 +23742,31 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23431
23742
  continue;
23432
23743
  }
23433
23744
  }
23745
+ const persistDrop = (mode) => {
23746
+ let startedAt = 0;
23747
+ try {
23748
+ db.transaction(() => {
23749
+ startedAt = performance.now();
23750
+ if (mode)
23751
+ updateTagDropMode2(db, sessionId, pendingOp.tagId, mode);
23752
+ updateTagStatus2(db, sessionId, pendingOp.tagId, "dropped");
23753
+ if (!synthetic)
23754
+ removePendingOp(db, sessionId, pendingOp.tagId);
23755
+ }).immediate();
23756
+ } finally {
23757
+ if (startedAt)
23758
+ logSlowWriteTransaction("apply_pending_operation", startedAt);
23759
+ }
23760
+ };
23434
23761
  let shouldPersistDrop = false;
23435
23762
  if (isToolTag) {
23436
23763
  if (editMarkerTagIds.has(pendingOp.tagId)) {
23437
23764
  const strip = target?.hasAttachments?.() === true;
23765
+ if (target?.canDrop?.() === false || target?.prepareDrop?.() === false || !target) {
23766
+ reject("edit_marker_incomplete");
23767
+ continue;
23768
+ }
23769
+ persistDrop(strip ? "edit_marker_stripped" : "edit_marker");
23438
23770
  const markResult = (strip ? target?.editMarkerStripped?.() : target?.editMarker?.()) ?? "absent";
23439
23771
  if (markResult === "incomplete" || markResult === "absent") {
23440
23772
  reject(`edit_marker_${markResult}`);
@@ -23443,11 +23775,11 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23443
23775
  didMutateMessage = true;
23444
23776
  operationMutated = true;
23445
23777
  onTagReduced?.({ tagNumber: pendingOp.tagId, mode: "edit_marker" });
23446
- updateTagDropMode2(db, sessionId, pendingOp.tagId, strip ? "edit_marker_stripped" : "edit_marker");
23447
23778
  shouldPersistDrop = true;
23448
23779
  } else {
23449
23780
  const { result: dropResult, mode: appliedMode } = applyNewToolDrop(target, {
23450
- inWindow: skeletonWindow.has(pendingOp.tagId)
23781
+ inWindow: skeletonWindow.has(pendingOp.tagId),
23782
+ persist: persistDrop
23451
23783
  });
23452
23784
  if (dropResult === "incomplete" || synthetic && dropResult !== "removed" && dropResult !== "truncated") {
23453
23785
  reject(`drop_${dropResult}`);
@@ -23463,12 +23795,12 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23463
23795
  } else {
23464
23796
  reject(`drop_${dropResult}`);
23465
23797
  }
23466
- updateTagDropMode2(db, sessionId, pendingOp.tagId, appliedMode);
23467
23798
  shouldPersistDrop = true;
23468
23799
  }
23469
23800
  } else if (target) {
23470
23801
  const replacement = buildReplacementContent(pendingOp.tagId);
23471
23802
  const priorContent = target.getContent?.();
23803
+ persistDrop();
23472
23804
  const changed = target.setContent(replacement);
23473
23805
  if (changed) {
23474
23806
  didMutateMessage = true;
@@ -23485,30 +23817,25 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
23485
23817
  shouldPersistDrop = true;
23486
23818
  } else if (!synthetic) {
23487
23819
  reject("target_absent");
23820
+ persistDrop();
23488
23821
  shouldPersistDrop = true;
23489
23822
  }
23490
23823
  if (!shouldPersistDrop) {
23491
23824
  reject("not_persisted");
23492
23825
  continue;
23493
23826
  }
23494
- updateTagStatus2(db, sessionId, pendingOp.tagId, "dropped");
23495
- if (!synthetic)
23496
- removePendingOp(db, sessionId, pendingOp.tagId);
23497
23827
  if (operationMutated)
23498
23828
  diagnostics.mutated += 1;
23499
23829
  else
23500
23830
  diagnostics.persistedWithoutMutation += 1;
23501
23831
  }
23502
- }).immediate();
23832
+ }
23503
23833
  } catch (error) {
23504
23834
  if (!admitted && /database (?:table )?is locked|sqlite_(busy|locked)/i.test(String(error))) {
23505
23835
  sessionLog2(sessionId, "pending operations write admission busy; retaining prior bytes for retry");
23506
23836
  return false;
23507
23837
  }
23508
23838
  throw error;
23509
- } finally {
23510
- if (admitted)
23511
- logSlowWriteTransaction("apply_pending_operations", startedAt);
23512
23839
  }
23513
23840
  if (diagnostics) {
23514
23841
  onBatchComplete?.(diagnostics);
@@ -24880,6 +25207,24 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
24880
25207
  };
24881
25208
  }
24882
25209
 
25210
+ // ../plugin/src/hooks/magic-context/degraded-pass-refusal.ts
25211
+ class DegradedPassRefusalError extends Error {
25212
+ site;
25213
+ code = "DEGRADED_PASS_REFUSAL";
25214
+ recoverable = true;
25215
+ estimatedTokens;
25216
+ contextLimitTokens;
25217
+ constructor(site, options) {
25218
+ super(renderUserFacingFailure("transform_pass_degraded", "plain"), {
25219
+ cause: options?.cause
25220
+ });
25221
+ this.site = site;
25222
+ this.name = "DegradedPassRefusalError";
25223
+ this.estimatedTokens = options?.estimatedTokens;
25224
+ this.contextLimitTokens = options?.contextLimitTokens;
25225
+ }
25226
+ }
25227
+
24883
25228
  // ../plugin/src/hooks/magic-context/dropped-token-estimate.ts
24884
25229
  var TAG_QUERY_CHUNK_SIZE = 900;
24885
25230
  var ESTIMATED_CHARACTERS_PER_TOKEN = 3.5;
@@ -25500,8 +25845,110 @@ function advanceToolReclaimWatermarkToCurrentMax(db, sessionId) {
25500
25845
  return maxTagNumber;
25501
25846
  }
25502
25847
 
25848
+ // ../plugin/src/shared/recovery-reasoning-prefix.ts
25849
+ function recoveryReasoningPrefixes(messages, partsKey) {
25850
+ const prefixes = [];
25851
+ for (let index = 0;index < messages.length; index++) {
25852
+ const message = messages[index];
25853
+ const parts = message?.[partsKey];
25854
+ if (!Array.isArray(parts))
25855
+ continue;
25856
+ for (let p = 0;p < parts.length; p++) {
25857
+ const type = parts[p]?.type;
25858
+ if (type !== "thinking" && type !== "reasoning" && type !== "redactedThinking" && type !== "redacted_thinking")
25859
+ continue;
25860
+ prefixes.push(JSON.stringify([
25861
+ ...messages.slice(0, index),
25862
+ { ...message, [partsKey]: parts.slice(0, p + 1) }
25863
+ ]));
25864
+ }
25865
+ }
25866
+ return prefixes;
25867
+ }
25868
+
25503
25869
  // ../plugin/src/shared/tag-transcript.ts
25504
25870
  import { createHash as createHash11 } from "node:crypto";
25871
+ function buildPersistedDropTargets(transcript, tags, options = {}) {
25872
+ const targets = new Map;
25873
+ const tools = new Map(tags.filter((tag) => tag.type === "tool").map((tag) => [makeToolCompositeKey(tag.toolOwnerMessageId ?? "", tag.messageId), tag]));
25874
+ const textTags = new Map(tags.filter((tag) => tag.type !== "tool").map((tag) => [tag.messageId, tag]));
25875
+ const byNumber = new Map(tags.map((tag) => [tag.tagNumber, tag]));
25876
+ const aggregates = new Map;
25877
+ const pending = new Map;
25878
+ const resolved = new Map;
25879
+ const ambiguousCalls = new Set;
25880
+ const end = new ConversationEndGuard(transcript.messages);
25881
+ for (const message of transcript.messages) {
25882
+ let ordinal = 0;
25883
+ let resultRun;
25884
+ const parts = message.parts;
25885
+ const reasoning = message.requiresToolArcSkeleton ?? (message.info.role === "assistant" && parts.some((part) => part.kind === "thinking"));
25886
+ for (const part of parts) {
25887
+ if (part.kind !== "tool_result")
25888
+ resultRun = undefined;
25889
+ if (part.kind === "text") {
25890
+ const prefix = /^§(\d+)§\s/.exec(part.getText() ?? "");
25891
+ const tag = (prefix ? byNumber.get(Number(prefix[1])) : undefined) ?? textTags.get(`${message.info.id}:p${ordinal}`);
25892
+ ordinal++;
25893
+ if (tag && tag.type !== "tool")
25894
+ targets.set(tag.tagNumber, buildTextTarget(part, message, ""));
25895
+ } else if ((part.kind === "tool_use" || part.kind === "tool_result") && part.id) {
25896
+ const open = pending.get(part.id) ?? [];
25897
+ if (open.length > 1)
25898
+ ambiguousCalls.add(part.id);
25899
+ const key = (part.kind === "tool_result" ? resultRun?.callId === part.id ? resultRun.key : open.at(-1) ?? resolved.get(part.id) : undefined) ?? makeToolCompositeKey(message.info.id ?? "", part.id);
25900
+ if (part.kind === "tool_use")
25901
+ open.push(key);
25902
+ else {
25903
+ const position = open.lastIndexOf(key);
25904
+ if (position >= 0)
25905
+ open.splice(position, 1);
25906
+ resultRun = { callId: part.id, key };
25907
+ resolved.set(part.id, key);
25908
+ }
25909
+ pending.set(part.id, open);
25910
+ let aggregate = aggregates.get(key);
25911
+ if (!aggregate) {
25912
+ aggregate = {
25913
+ tag: tools.get(key),
25914
+ occurrences: [],
25915
+ reasoning: false
25916
+ };
25917
+ aggregates.set(key, aggregate);
25918
+ }
25919
+ aggregate.occurrences.push({ message, part, kind: part.kind });
25920
+ aggregate.reasoning ||= reasoning;
25921
+ }
25922
+ }
25923
+ }
25924
+ const arcsByCall = new Map;
25925
+ for (const { occurrences } of aggregates.values()) {
25926
+ const callId = occurrences[0]?.part.id ?? "";
25927
+ arcsByCall.set(callId, (arcsByCall.get(callId) ?? 0) + 1);
25928
+ }
25929
+ for (const aggregate of aggregates.values()) {
25930
+ const { occurrences, reasoning } = aggregate;
25931
+ const callId = occurrences[0]?.part.id ?? "";
25932
+ if (ambiguousCalls.has(callId))
25933
+ continue;
25934
+ const tag = aggregate.tag ?? (arcsByCall.get(callId) === 1 ? tools.get(makeToolCompositeKey("", callId)) : undefined);
25935
+ if (tag)
25936
+ targets.set(tag.tagNumber, buildAggregateTarget(tag.tagNumber, occurrences, reasoning, end));
25937
+ }
25938
+ if (!options.allowReasoningPrefixRewrite) {
25939
+ let lastThinking = -1;
25940
+ transcript.messages.forEach((message, index) => {
25941
+ if (message.parts.some((part) => part.kind === "thinking"))
25942
+ lastThinking = index;
25943
+ });
25944
+ const protectedIds = new Set(transcript.messages.slice(0, lastThinking + 1).map((message) => message.info.id));
25945
+ for (const [number, target] of targets) {
25946
+ if (target.mutationParts?.some(({ message }) => protectedIds.has(message.info.id)))
25947
+ targets.delete(number);
25948
+ }
25949
+ }
25950
+ return targets;
25951
+ }
25505
25952
  function textIdentityDigest(value) {
25506
25953
  return createHash11("sha256").update(value).digest("hex");
25507
25954
  }
@@ -26196,6 +26643,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
26196
26643
  }
26197
26644
  return any ? "truncated" : "absent";
26198
26645
  },
26646
+ prepareDrop: () => !occurrences.some((occ) => occ.part.canRemove?.() === "defer"),
26199
26647
  canDrop: () => complete() && !occurrences.some((occ) => occ.part.hasUserAnswer?.()),
26200
26648
  requiresToolArcSkeleton,
26201
26649
  readInput() {
@@ -28225,18 +28673,27 @@ function buildPiCompactionSummary(compartments) {
28225
28673
  return `Magic Context compacted ${titles.length} segments: ${shown}; …and ${titles.length - MAX_SUMMARY_TITLES} more`;
28226
28674
  }
28227
28675
  function findFirstKeptEntryId(entries, lastCompactedOrdinal, ordinalOffset = 0) {
28676
+ const boundary = resolvePiFirstKeptEntry(entries, lastCompactedOrdinal, ordinalOffset);
28677
+ return boundary.kind === "resolved" ? boundary.entryId : null;
28678
+ }
28679
+ function resolvePiFirstKeptEntry(entries, lastCompactedOrdinal, ordinalOffset = 0) {
28228
28680
  const target = lastCompactedOrdinal + 1;
28229
28681
  const afterOrdinal = Number.isNaN(lastCompactedOrdinal) ? 0 : Math.max(0, lastCompactedOrdinal);
28682
+ let syntheticKeptId;
28230
28683
  for (const message of iterateEntriesToRawMessageRange(entries, afterOrdinal, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, ordinalOffset)) {
28231
28684
  if (message.ordinal < target || isPiSystemEntry(message))
28232
28685
  continue;
28233
- if (message.id.startsWith(SYNTH_USER_ID_PREFIX))
28234
- return null;
28686
+ if (syntheticKeptId)
28687
+ return { kind: "unplaceable", rawId: syntheticKeptId };
28688
+ if (message.id.startsWith(SYNTH_USER_ID_PREFIX)) {
28689
+ syntheticKeptId = message.id;
28690
+ continue;
28691
+ }
28235
28692
  if (message.id.length === 0)
28236
28693
  continue;
28237
- return message.id;
28694
+ return { kind: "resolved", entryId: message.id };
28238
28695
  }
28239
- return null;
28696
+ return { kind: "waiting" };
28240
28697
  }
28241
28698
 
28242
28699
  // src/compaction-marker-manager-pi.ts
@@ -28257,6 +28714,7 @@ function logPiCompactionMarkerDrainGateOnce(sessionId, pending, gate, fields = {
28257
28714
  `endMessageId=${pending.endMessageId}`,
28258
28715
  `ordinal=${pending.ordinal}`,
28259
28716
  `firstKeptEntryId=${firstKeptEntryId ?? "<none>"}`,
28717
+ `boundaryKind=${pending.endMessageId.startsWith(SYNTH_USER_ID_PREFIX) ? "folded-tool-result" : "session-entry"}`,
28260
28718
  `renderedCoverageOrdinal=${fields.renderedCoverageOrdinal ?? "<none>"}`
28261
28719
  ];
28262
28720
  if (fields.renderedCoverageEndMessageId !== undefined)
@@ -28269,6 +28727,17 @@ function logPiCompactionMarkerDrainGateOnce(sessionId, pending, gate, fields = {
28269
28727
  parts.push(`matchCount=${fields.matchCount}`);
28270
28728
  if (fields.alignment !== undefined)
28271
28729
  parts.push(`alignment=${fields.alignment}`);
28730
+ if (fields.alignmentReason !== undefined)
28731
+ parts.push(`alignmentReason=${fields.alignmentReason}`);
28732
+ for (const key of [
28733
+ "historyConsumed",
28734
+ "materializationSatisfied",
28735
+ "drainSuppressed",
28736
+ "casLost"
28737
+ ]) {
28738
+ if (fields[key] !== undefined)
28739
+ parts.push(`${key}=${fields[key]}`);
28740
+ }
28272
28741
  if (fields.foldingOnlyTools !== undefined)
28273
28742
  parts.push(`foldingOnlyTools=${JSON.stringify(fields.foldingOnlyTools)}`);
28274
28743
  if (fields.persistedOnlyTools !== undefined)
@@ -28318,10 +28787,20 @@ function applyDeferredPiCompactionMarker(deps, sessionId, pending) {
28318
28787
  if (!firstKeptEntryId) {
28319
28788
  const alignment = resolvePiOrdinalAlignment(deps.db, sessionId, branchEntries);
28320
28789
  if (isPiOrdinalAlignmentUnanchored(alignment)) {
28321
- logPiCompactionMarkerDrainGateOnce(sessionId, pending, "ordinal-alignment-unavailable", { alignment: alignment.kind });
28790
+ if (alignment.kind === "unanchored" && alignment.reason === "anchor-missing" && hasMergedSyntheticBoundary(branchEntries, pending.endMessageId)) {
28791
+ return { kind: "stale-skip", reason: "synthetic-boundary-merged" };
28792
+ }
28793
+ logPiCompactionMarkerDrainGateOnce(sessionId, pending, "ordinal-alignment-unavailable", {
28794
+ alignment: alignment.kind,
28795
+ ...alignment.kind === "unanchored" ? { alignmentReason: alignment.reason } : {}
28796
+ });
28322
28797
  return { kind: "waiting-for-entry" };
28323
28798
  }
28324
- firstKeptEntryId = findFirstKeptEntryId(branchEntries, pending.ordinal, alignment.offset);
28799
+ const boundary = resolvePiFirstKeptEntry(branchEntries, pending.ordinal, alignment.offset);
28800
+ if (boundary.kind === "unplaceable") {
28801
+ return { kind: "stale-skip", reason: "synthetic-kept-entry" };
28802
+ }
28803
+ firstKeptEntryId = boundary.kind === "resolved" ? boundary.entryId : null;
28325
28804
  }
28326
28805
  if (!firstKeptEntryId) {
28327
28806
  logPiCompactionMarkerDrainGateOnce(sessionId, pending, "no-replay-safe-kept-entry", { firstKeptEntryId: null });
@@ -28376,6 +28855,27 @@ function applyDeferredPiCompactionMarker(deps, sessionId, pending) {
28376
28855
  return { kind: "retryable-failure", error };
28377
28856
  }
28378
28857
  }
28858
+ function hasMergedSyntheticBoundary(entries, endMessageId) {
28859
+ if (!endMessageId.startsWith(SYNTH_USER_ID_PREFIX))
28860
+ return false;
28861
+ const resultId = endMessageId.slice(SYNTH_USER_ID_PREFIX.length);
28862
+ let foundResult = false;
28863
+ for (const entry of entries) {
28864
+ const row = entry;
28865
+ if (row?.type !== "message")
28866
+ continue;
28867
+ const message = row.message;
28868
+ if (!foundResult) {
28869
+ foundResult = row.id === resultId && message?.role === "toolResult" && typeof message.toolCallId === "string" && message.toolCallId.length > 0;
28870
+ continue;
28871
+ }
28872
+ if (message?.role === "user")
28873
+ return true;
28874
+ if (message?.role === "assistant")
28875
+ return false;
28876
+ }
28877
+ return false;
28878
+ }
28379
28879
  function findLatestCompaction(branchEntries) {
28380
28880
  for (let i = branchEntries.length - 1;i >= 0; i--) {
28381
28881
  const entry = branchEntries[i];
@@ -29729,30 +30229,26 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
29729
30229
  const tPersist = performance.now();
29730
30230
  const toDrop = new Set(plan.tagNumbers);
29731
30231
  const newestEmergencyTags = recentTags;
29732
- db.transaction(() => {
29733
- for (const tag of tags) {
29734
- if (!toDrop.has(tag.tagNumber))
29735
- continue;
29736
- if (tag.status !== "active" || tag.type !== "tool")
29737
- continue;
29738
- const target = targets.get(tag.tagNumber);
29739
- const recent = (emergency.usagePercentage ?? 0) < 95 && newestEmergencyTags.has(tag.tagNumber);
29740
- const { result, mode } = applyNewToolDrop(target, {
29741
- inWindow: recent,
29742
- keepSkeleton: target?.requiresToolArcSkeleton === true
30232
+ for (const tag of tags) {
30233
+ if (!toDrop.has(tag.tagNumber))
30234
+ continue;
30235
+ if (tag.status !== "active" || tag.type !== "tool")
30236
+ continue;
30237
+ const target = targets.get(tag.tagNumber);
30238
+ const recent = (emergency.usagePercentage ?? 0) < 95 && newestEmergencyTags.has(tag.tagNumber);
30239
+ const { result, mode } = applyPersistedToolDrop(db, sessionId, tag.tagNumber, target, {
30240
+ inWindow: recent,
30241
+ keepSkeleton: target?.requiresToolArcSkeleton === true
30242
+ });
30243
+ if (result === "removed" || result === "truncated") {
30244
+ droppedTools++;
30245
+ emergencyDroppedTools++;
30246
+ droppedTokenReductions.push({
30247
+ tagNumber: tag.tagNumber,
30248
+ mode: mode === "full" ? "full" : "truncated"
29743
30249
  });
29744
- if (result === "removed" || result === "truncated") {
29745
- updateTagStatus2(db, sessionId, tag.tagNumber, "dropped");
29746
- updateTagDropMode2(db, sessionId, tag.tagNumber, mode);
29747
- droppedTools++;
29748
- emergencyDroppedTools++;
29749
- droppedTokenReductions.push({
29750
- tagNumber: tag.tagNumber,
29751
- mode: mode === "full" ? "full" : "truncated"
29752
- });
29753
- }
29754
30250
  }
29755
- }).immediate();
30251
+ }
29756
30252
  logStep("applyEmergencyDrops", tPersist, `dropped=${emergencyDroppedTools}`);
29757
30253
  sessionLog2(sessionId, `emergency tiered drop: ${plan.reason}`);
29758
30254
  } else {
@@ -29761,33 +30257,29 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
29761
30257
  }
29762
30258
  const staleReduce = routine && config.staleReduceStripEnabled ? collectStaleReduceCallIds(piMessages, buildMessageIdToMaxTagFromTargets(targets), buildCtxReduceTagNumbers(tags), toolAgeCutoff, resolveStableId) : { composite: new Set, bareCallIds: new Set };
29763
30259
  if (routine && config.staleReduceStripEnabled && (staleReduce.composite.size > 0 || staleReduce.bareCallIds.size > 0)) {
29764
- db.transaction(() => {
29765
- for (const tag of tags) {
29766
- if (tag.status !== "active")
29767
- continue;
29768
- if (tag.type !== "tool")
29769
- continue;
29770
- if (protectedTools.has(tag.tagNumber))
29771
- continue;
29772
- if (!tag.messageId)
29773
- continue;
29774
- const matched = tag.toolOwnerMessageId ? staleReduce.composite.has(`${tag.toolOwnerMessageId}\x00${tag.messageId}`) : staleReduce.bareCallIds.has(tag.messageId);
29775
- if (!matched)
29776
- continue;
29777
- const target = targets.get(tag.tagNumber);
29778
- if (target?.canDrop?.() === false)
29779
- continue;
29780
- const { result, mode } = applyNewToolDrop(target, { inWindow: false });
29781
- if (result === "incomplete")
29782
- continue;
29783
- updateTagDropMode2(db, sessionId, tag.tagNumber, mode);
29784
- updateTagStatus2(db, sessionId, tag.tagNumber, "dropped");
29785
- if (result === "removed" || result === "truncated") {
29786
- droppedStaleReduceCalls++;
29787
- config.onStaleReduceEdit?.(tag);
29788
- }
30260
+ for (const tag of tags) {
30261
+ if (tag.status !== "active")
30262
+ continue;
30263
+ if (tag.type !== "tool")
30264
+ continue;
30265
+ if (protectedTools.has(tag.tagNumber))
30266
+ continue;
30267
+ if (!tag.messageId)
30268
+ continue;
30269
+ const matched = tag.toolOwnerMessageId ? staleReduce.composite.has(`${tag.toolOwnerMessageId}\x00${tag.messageId}`) : staleReduce.bareCallIds.has(tag.messageId);
30270
+ if (!matched)
30271
+ continue;
30272
+ const target = targets.get(tag.tagNumber);
30273
+ if (target?.canDrop?.() === false)
30274
+ continue;
30275
+ const { result } = applyPersistedToolDrop(db, sessionId, tag.tagNumber, target, { inWindow: false });
30276
+ if (result === "incomplete")
30277
+ continue;
30278
+ if (result === "removed" || result === "truncated") {
30279
+ droppedStaleReduceCalls++;
30280
+ config.onStaleReduceEdit?.(tag);
29789
30281
  }
29790
- }).immediate();
30282
+ }
29791
30283
  }
29792
30284
  if (routine) {
29793
30285
  db.transaction(() => {
@@ -29856,37 +30348,33 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
29856
30348
  group.push(tag);
29857
30349
  fingerprintGroups.set(fingerprint, group);
29858
30350
  }
29859
- db.transaction(() => {
29860
- for (const [, group] of fingerprintGroups) {
29861
- if (group.length <= 1)
30351
+ for (const [, group] of fingerprintGroups) {
30352
+ if (group.length <= 1)
30353
+ continue;
30354
+ group.sort((a, b) => a.tagNumber - b.tagNumber);
30355
+ for (let i = 0;i < group.length - 1; i++) {
30356
+ const tag = group[i];
30357
+ if (tag.tagNumber > protectedCutoff)
29862
30358
  continue;
29863
- group.sort((a, b) => a.tagNumber - b.tagNumber);
29864
- for (let i = 0;i < group.length - 1; i++) {
29865
- const tag = group[i];
29866
- if (tag.tagNumber > protectedCutoff)
29867
- continue;
29868
- const target = targets.get(tag.tagNumber);
29869
- if (protectedTools.has(tag.tagNumber))
29870
- continue;
29871
- if (target?.canDrop?.() === false)
29872
- continue;
29873
- const { result, mode } = applyNewToolDrop(target, {
29874
- inWindow: false
30359
+ const target = targets.get(tag.tagNumber);
30360
+ if (protectedTools.has(tag.tagNumber))
30361
+ continue;
30362
+ if (target?.canDrop?.() === false)
30363
+ continue;
30364
+ const { result, mode } = applyPersistedToolDrop(db, sessionId, tag.tagNumber, target, {
30365
+ inWindow: false
30366
+ });
30367
+ if (result === "incomplete")
30368
+ continue;
30369
+ if (result === "removed" || result === "truncated") {
30370
+ deduplicatedTools++;
30371
+ droppedTokenReductions.push({
30372
+ tagNumber: tag.tagNumber,
30373
+ mode: mode === "full" ? "full" : "truncated"
29875
30374
  });
29876
- if (result === "incomplete")
29877
- continue;
29878
- updateTagDropMode2(db, sessionId, tag.tagNumber, mode);
29879
- updateTagStatus2(db, sessionId, tag.tagNumber, "dropped");
29880
- if (result === "removed" || result === "truncated") {
29881
- deduplicatedTools++;
29882
- droppedTokenReductions.push({
29883
- tagNumber: tag.tagNumber,
29884
- mode: mode === "full" ? "full" : "truncated"
29885
- });
29886
- }
29887
30375
  }
29888
30376
  }
29889
- }).immediate();
30377
+ }
29890
30378
  }
29891
30379
  if (droppedTools > 0 || deduplicatedTools > 0 || droppedInjections > 0 || droppedStaleReduceCalls > 0) {
29892
30380
  sessionLog2(sessionId, `heuristic cleanup: dropped ${droppedTools} tool tags, stale ctx_reduce=${droppedStaleReduceCalls}, deduplicated ${deduplicatedTools} tool calls, dropped ${droppedInjections} system injections`);
@@ -30933,6 +31421,7 @@ function forgetCachedPrefix(sessionId) {
30933
31421
  function dropCompartmentsAfter(db, sessionId, anchor, holderId, activeBranch = false, onRetire) {
30934
31422
  db.exec("BEGIN IMMEDIATE");
30935
31423
  let finished = false;
31424
+ let clearedMarker = null;
30936
31425
  try {
30937
31426
  if (!isCompartmentLeaseHeld(db, sessionId, holderId)) {
30938
31427
  db.exec("ROLLBACK");
@@ -30949,14 +31438,16 @@ function dropCompartmentsAfter(db, sessionId, anchor, holderId, activeBranch = f
30949
31438
  if (activeBranch) {
30950
31439
  db.prepare("UPDATE session_meta SET prior_boundary_ordinal = ? WHERE session_id = ?").run((anchor?.endMessage ?? 0) + 1, sessionId);
30951
31440
  const marker = getPendingPiCompactionMarkerState(db, sessionId);
30952
- if (marker)
30953
- clearPendingPiCompactionMarkerStateIf(db, sessionId, marker);
31441
+ if (marker && clearPendingPiCompactionMarkerStateIf(db, sessionId, marker, null))
31442
+ clearedMarker = marker;
30954
31443
  }
30955
31444
  deleteChunkEmbedBackoffForSession(db, sessionId);
30956
31445
  queueM0Mutation(db, { sessionId, mutationType: "recomp_boundary_change" });
30957
31446
  clearCachedM0M1(db, sessionId);
30958
31447
  db.exec("COMMIT");
30959
31448
  finished = true;
31449
+ if (clearedMarker)
31450
+ logPiCompactionMarkerClear(sessionId, clearedMarker, "active-branch-retired");
30960
31451
  return dropped.map((row) => row.sequence);
30961
31452
  } finally {
30962
31453
  if (!finished) {
@@ -32575,6 +33066,54 @@ function rewriteNativeToolInput(message, toolCallId, input) {
32575
33066
  items[match.index] = { ...match.item, [field]: nextValue };
32576
33067
  envelope.message.providerPayload = { ...envelope.payload, items };
32577
33068
  }
33069
+ function textIdentity(part) {
33070
+ if (!isRecord(part) || part.type !== "text" || typeof part.textSignature !== "string")
33071
+ return;
33072
+ try {
33073
+ const signature = JSON.parse(part.textSignature);
33074
+ if (isRecord(signature) && signature.v === 1 && typeof signature.id === "string" && signature.id.length > 0 && (signature.phase === undefined || signature.phase === "commentary" || signature.phase === "final_answer")) {
33075
+ return { id: signature.id, phase: signature.phase };
33076
+ }
33077
+ } catch {}
33078
+ return;
33079
+ }
33080
+ function rewriteNativeAssistantTexts(message, previous, sessionId) {
33081
+ const envelope = getNativeEnvelope(message);
33082
+ if (envelope?.message.role !== "assistant" || !isRecord(previous) || !Array.isArray(previous.content) || !Array.isArray(envelope.message.content))
33083
+ return;
33084
+ const content = envelope.message.content;
33085
+ for (let index = 0;index < content.length; index++) {
33086
+ const part = content[index];
33087
+ const before = previous.content[index];
33088
+ if (!isRecord(part) || part.type !== "text" || typeof part.text !== "string" || !isRecord(before) || before.type !== "text" || typeof before.text !== "string")
33089
+ continue;
33090
+ if (part.text.replace(/^§\d+§ /u, "") === before.text.replace(/^§\d+§ /u, ""))
33091
+ continue;
33092
+ const identity = textIdentity(part);
33093
+ const sameIdentity = (value) => {
33094
+ const other = textIdentity(value);
33095
+ return identity !== undefined && other?.id === identity.id && other.phase === identity.phase;
33096
+ };
33097
+ const matches = envelope.items.flatMap((item, itemIndex) => isRecord(item) && item.type === "message" && item.role === "assistant" && identity !== undefined && item.id === identity.id && item.phase === identity.phase ? [{ item, itemIndex }] : []);
33098
+ const match = matches[0];
33099
+ const outputs = match && Array.isArray(match.item.content) ? match.item.content.flatMap((output, outputIndex) => isRecord(output) && output.type === "output_text" && typeof output.text === "string" ? [{ output, outputIndex }] : []) : [];
33100
+ if (!identity || content.filter(sameIdentity).length !== 1 || previous.content.filter(sameIdentity).length !== 1 || matches.length !== 1 || outputs.length !== 1) {
33101
+ if (sessionId)
33102
+ sessionLog2(sessionId, `native assistant text replay skipped: missing or ambiguous id/phase match at part ${index} (items=${matches.length}, outputs=${outputs.length}); retaining native history for dispatch validation`);
33103
+ continue;
33104
+ }
33105
+ const { output, outputIndex } = outputs[0];
33106
+ if (output.text === part.text)
33107
+ continue;
33108
+ const nextContent = match.item.content.slice();
33109
+ nextContent[outputIndex] = { ...output, text: part.text };
33110
+ const items = envelope.items.slice();
33111
+ items[match.itemIndex] = { ...match.item, content: nextContent };
33112
+ envelope.items = items;
33113
+ envelope.payload = { ...envelope.payload, items };
33114
+ envelope.message.providerPayload = envelope.payload;
33115
+ }
33116
+ }
32578
33117
  function hasRedactedThinkingContent(message) {
32579
33118
  if (!Array.isArray(message.content))
32580
33119
  return false;
@@ -33670,7 +34209,7 @@ function object4(value) {
33670
34209
  function wellFormed(text) {
33671
34210
  return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "�");
33672
34211
  }
33673
- function textIdentity(text, phase) {
34212
+ function textIdentity2(text, phase) {
33674
34213
  return JSON.stringify([
33675
34214
  text,
33676
34215
  phase === "commentary" || phase === "final_answer" ? phase : null
@@ -33728,7 +34267,7 @@ function piResponsesAssistantTexts(value, model) {
33728
34267
  units += text.length;
33729
34268
  if (units > MAX_TEXT_UNITS)
33730
34269
  return;
33731
- texts.push(textIdentity(text, undefined));
34270
+ texts.push(textIdentity2(text, undefined));
33732
34271
  continue;
33733
34272
  }
33734
34273
  if (block?.type !== "text" || typeof block.text !== "string")
@@ -33736,7 +34275,7 @@ function piResponsesAssistantTexts(value, model) {
33736
34275
  units += block.text.length;
33737
34276
  if (units > MAX_TEXT_UNITS)
33738
34277
  return;
33739
- texts.push(textIdentity(wellFormed(block.text), sameModel ? signaturePhase(block) : undefined));
34278
+ texts.push(textIdentity2(wellFormed(block.text), sameModel ? signaturePhase(block) : undefined));
33740
34279
  blocks.push(block);
33741
34280
  }
33742
34281
  const payload = object4(message.providerPayload);
@@ -33761,7 +34300,7 @@ function piResponsesAssistantTexts(value, model) {
33761
34300
  }
33762
34301
  if (signature?.v !== 1 || typeof signature.id !== "string" || item.id !== signature.id || item.phase !== signature.phase || part.text !== block.text.replace(/^§\d+§ /u, ""))
33763
34302
  return texts;
33764
- replay.push(textIdentity(wellFormed(part.text), item.phase));
34303
+ replay.push(textIdentity2(wellFormed(part.text), item.phase));
33765
34304
  }
33766
34305
  }
33767
34306
  return replay.length === texts.length ? replay : texts;
@@ -33770,7 +34309,7 @@ function piResponsesWireTexts(value, phase) {
33770
34309
  if (phase !== undefined && phase !== null && phase !== "commentary" && phase !== "final_answer")
33771
34310
  return;
33772
34311
  if (typeof value === "string")
33773
- return [textIdentity(value, phase)];
34312
+ return [textIdentity2(value, phase)];
33774
34313
  if (!Array.isArray(value))
33775
34314
  return;
33776
34315
  const texts = [];
@@ -33782,7 +34321,7 @@ function piResponsesWireTexts(value, phase) {
33782
34321
  units += part.text.length;
33783
34322
  if (units > MAX_TEXT_UNITS)
33784
34323
  return;
33785
- texts.push(textIdentity(part.text, phase));
34324
+ texts.push(textIdentity2(part.text, phase));
33786
34325
  }
33787
34326
  return texts;
33788
34327
  }
@@ -35775,17 +36314,26 @@ function createPiLkgCoordinator(db, scheduleCapture = (capture) => setImmediate(
35775
36314
  return { rebuildReason: "lkg_recovery_input_rebuild" };
35776
36315
  return { prefix };
35777
36316
  };
35778
- const noteRecovery = (snapshot, messages) => {
35779
- const state = stateFor(snapshot.sessionId);
36317
+ const recoveryEntryIds = (snapshot) => {
35780
36318
  const anchor = snapshot.replayAnchorInputIndex;
35781
36319
  const slot = getSlot(snapshot.sessionId);
35782
36320
  if (snapshot.preparationFailure || anchor === null || !slot?.piOutputEntryIds)
35783
36321
  throw new PiLkgUncapturable("lkg_output_mapping_unavailable", "Cannot retain recovered output ownership");
35784
36322
  const surviving = new Set(snapshot.inputs.map((input) => input.id));
35785
- const outputEntryIds = [
36323
+ return [
35786
36324
  ...slot.piOutputEntryIds.filter((id) => id === null || surviving.has(id)),
35787
36325
  ...snapshot.inputs.slice(anchor + 1).map((input) => input.id)
35788
36326
  ];
36327
+ };
36328
+ const noteRecovery = (snapshot, messages, entryIds) => {
36329
+ const state = stateFor(snapshot.sessionId);
36330
+ const slot = getSlot(snapshot.sessionId);
36331
+ if (!slot)
36332
+ throw new PiLkgUncapturable("lkg_miss", "Cannot retain recovery without a saved request");
36333
+ const anchor = snapshot.replayAnchorInputIndex;
36334
+ if (anchor === null)
36335
+ throw new PiLkgUncapturable("lkg_miss", "Cannot retain recovery without an input anchor");
36336
+ const outputEntryIds = entryIds ?? recoveryEntryIds(snapshot);
35789
36337
  if (outputEntryIds.length !== messages.length)
35790
36338
  throw new PiLkgUncapturable("lkg_output_mapping_unavailable", "Recovered output ownership does not match the served array");
35791
36339
  state.recoveryPrefix = {
@@ -36202,6 +36750,7 @@ function createPiLkgCoordinator(db, scheduleCapture = (capture) => setImmediate(
36202
36750
  };
36203
36751
  return {
36204
36752
  noteRecovery,
36753
+ recoveryEntryIds,
36205
36754
  recoveryFor,
36206
36755
  beginPass(args) {
36207
36756
  return {
@@ -38557,31 +39106,61 @@ function isLargeImagePart(value) {
38557
39106
  const part = value;
38558
39107
  return part.type === "image" && typeof part.mimeType === "string" && part.mimeType.startsWith("image/") && typeof part.data === "string" && part.data.length > 200;
38559
39108
  }
38560
- function stripPiProcessedImages(args) {
38561
- const frozenIds = getProcessedImageStrippedIds(args.db, args.sessionId);
38562
- const newlyStrippedIds = [];
38563
- let hasAssistantResponse = false;
38564
- if (args.detect && args.watermark > 0) {
38565
- for (let i = args.messages.length - 1;i >= 0; i--) {
38566
- const raw = args.messages[i];
38567
- if (!raw || typeof raw !== "object")
38568
- continue;
38569
- const message = raw;
38570
- if (message.role === "assistant") {
38571
- hasAssistantResponse = true;
38572
- continue;
39109
+ function planPiProcessedImageIds(args, frozenIds) {
39110
+ if (!args.detect || args.watermark <= 0)
39111
+ return [];
39112
+ const ids = args.messages.map(args.stableId);
39113
+ const ownTags = ids.map((id) => id ? args.messageIdToMaxTag.get(id) ?? 0 : 0);
39114
+ const completeToolResults = new Set;
39115
+ const openCalls = new Map;
39116
+ for (let i = 0;i < args.messages.length; i++) {
39117
+ const message = args.messages[i];
39118
+ if (!message || typeof message !== "object")
39119
+ continue;
39120
+ if (message.role === "assistant" && Array.isArray(message.content)) {
39121
+ for (const part of message.content) {
39122
+ if (part?.type === "toolCall" && typeof part.id === "string") {
39123
+ openCalls.set(part.id, `${ids[i] ?? ""}\x00${part.id}`);
39124
+ }
38573
39125
  }
38574
- if (message.role !== "user" && message.role !== "toolResult" || !hasAssistantResponse || !Array.isArray(message.content) || !message.content.some(isLargeImagePart)) {
38575
- continue;
39126
+ } else if (message.role === "toolResult" && message.toolCallId) {
39127
+ const key = openCalls.get(message.toolCallId);
39128
+ if (key !== undefined && !args.protectedToolKeys?.has(key) && !args.protectedToolKeys?.has(message.toolCallId)) {
39129
+ completeToolResults.add(i);
38576
39130
  }
38577
- const id = args.stableId(raw, i);
38578
- if (!id || frozenIds.has(id))
38579
- continue;
38580
- const maxTag = args.messageIdToMaxTag.get(id) ?? 0;
38581
- if (maxTag <= args.watermark)
38582
- newlyStrippedIds.push(id);
39131
+ openCalls.delete(message.toolCallId);
39132
+ }
39133
+ }
39134
+ const selected = [];
39135
+ let hasAssistantResponse = false;
39136
+ let nextTag;
39137
+ for (let i = args.messages.length - 1;i >= 0; i--) {
39138
+ const ownTag = ownTags[i];
39139
+ const positionTag = ownTag > 0 ? ownTag : nextTag;
39140
+ if (ownTag > 0)
39141
+ nextTag = ownTag;
39142
+ const raw = args.messages[i];
39143
+ if (!raw || typeof raw !== "object")
39144
+ continue;
39145
+ const message = raw;
39146
+ if (message.role === "assistant") {
39147
+ hasAssistantResponse = true;
39148
+ continue;
38583
39149
  }
39150
+ if (message.role !== "user" && message.role !== "toolResult" || message.role === "toolResult" && !completeToolResults.has(i) || !hasAssistantResponse || !Array.isArray(message.content) || !message.content.some(isLargeImagePart))
39151
+ continue;
39152
+ const id = ids[i];
39153
+ if (!id || frozenIds.has(id) || positionTag === undefined || positionTag > args.watermark)
39154
+ continue;
39155
+ if ((args.usagePercentage ?? 0) < 95 && positionTag >= (args.protectedCutoff ?? Infinity))
39156
+ continue;
39157
+ selected.push(id);
38584
39158
  }
39159
+ return selected;
39160
+ }
39161
+ function stripPiProcessedImages(args) {
39162
+ const frozenIds = getProcessedImageStrippedIds(args.db, args.sessionId);
39163
+ const newlyStrippedIds = planPiProcessedImageIds(args, frozenIds);
38585
39164
  if (newlyStrippedIds.length > 0) {
38586
39165
  const persisted = (args.addIds ?? addProcessedImageStrippedIds)(args.db, args.sessionId, newlyStrippedIds);
38587
39166
  if (persisted) {
@@ -38833,6 +39412,9 @@ function createPiTranscript(source, sessionId, entryIds, options = {}) {
38833
39412
  if (committed)
38834
39413
  return;
38835
39414
  committed = true;
39415
+ for (const index of dirtyMessages) {
39416
+ rewriteNativeAssistantTexts(working[index], source[index], sessionId);
39417
+ }
38836
39418
  for (const [index, parts] of removals) {
38837
39419
  const message = working[index];
38838
39420
  if (message.role !== "assistant" && message.role !== "toolResult" || !Array.isArray(message.content))
@@ -39225,6 +39807,8 @@ function createPiAssistantPart(working, messageIndex, partIndex, markDirty) {
39225
39807
  ...existing,
39226
39808
  arguments: { dropped: sentinelText }
39227
39809
  };
39810
+ } else if (existing?.type === "text") {
39811
+ newContent[partIndex] = { ...existing, text: sentinelText };
39228
39812
  } else {
39229
39813
  newContent[partIndex] = { type: "text", text: sentinelText };
39230
39814
  }
@@ -41053,6 +41637,7 @@ function registerPiContextHandler(pi, baseOptions) {
41053
41637
  let sessionIdForError;
41054
41638
  let lkgPassSnapshot;
41055
41639
  let lkgCompactionOff = baseOptions.compactionOff === true;
41640
+ let lkgOptions = baseOptions;
41056
41641
  let lkgEmergencyRecoveryArmed = false;
41057
41642
  const captureServedEvidence = (...args) => {
41058
41643
  try {
@@ -41083,6 +41668,7 @@ function registerPiContextHandler(pi, baseOptions) {
41083
41668
  rawMessageCount = event.messages.length;
41084
41669
  const fullWireMessageCount = rawMessageCount;
41085
41670
  const options = baseOptions.resolveForProject?.(projectDirectory) ?? baseOptions;
41671
+ lkgOptions = options;
41086
41672
  const deliveryModelKey = resolvePiContextModelKey(ctx);
41087
41673
  if (!budget.sideTurn && deliveryModelKey) {
41088
41674
  const parentId = ctx.sessionManager.getLeafId?.();
@@ -42301,14 +42887,38 @@ function registerPiContextHandler(pi, baseOptions) {
42301
42887
  reason: lkgPassSnapshot.replayFailure ?? "lkg_recovery_unproven"
42302
42888
  } : lkgCoordinator.replay(lkgPassSnapshot, (id) => ctx.sessionManager.getEntry?.(id)?.parentId);
42303
42889
  if (replay.ok) {
42890
+ const reasoningPrefixes = recoveryReasoningPrefixes(replay.messages, "content");
42891
+ const entryIds = lkgCoordinator.recoveryEntryIds(lkgPassSnapshot);
42892
+ const recoverySessionId = sessionIdForError;
42893
+ const recoveryTranscript = createPiTranscript(replay.messages, sessionIdForError, entryIds.map((id) => id ?? undefined), {
42894
+ preserveReasoningToolArcs: ctx.model?.api !== "openai-codex-responses" && ctx.model?.api !== "openai-responses",
42895
+ authorizeToolRemoval: (callId) => authorizePiToolRemoval({
42896
+ db: lkgOptions.db,
42897
+ sessionId: recoverySessionId,
42898
+ callId,
42899
+ saved: getNativeReplayState(lkgOptions.db, recoverySessionId).toolInputs,
42900
+ canApply: false
42901
+ })
42902
+ });
42903
+ const drops = getTagsBySession2(lkgOptions.db, sessionIdForError).filter((tag) => tag.status === "dropped");
42904
+ const changed = applyFlushedStatuses(sessionIdForError, lkgOptions.db, buildPersistedDropTargets(recoveryTranscript, drops, {
42905
+ allowReasoningPrefixRewrite: true
42906
+ }), drops);
42907
+ const working = recoveryTranscript.getWorkingMessages();
42908
+ recoveryTranscript.commit();
42909
+ const idsByOutput = new Map(working.map((message, index) => [message, entryIds[index]]));
42910
+ recoveryTranscript.finalizeToolRemovals();
42911
+ if (JSON.stringify(recoveryReasoningPrefixes(replay.messages, "content")) !== JSON.stringify(reasoningPrefixes))
42912
+ throw new DegradedPassRefusalError("lkg-reasoning-prefix-changed", { cause: err });
42913
+ const recoveryIds = replay.messages.map((message) => idsByOutput.get(message) ?? null);
42304
42914
  assertPiRawFallbackFits(replay.messages, rawFallbackLimit, (line) => {
42305
42915
  if (sessionIdForError)
42306
42916
  logPiLkgRecovery(sessionIdForError, line);
42307
- }, err, readPiLkgFitEnvelope(ctx, pi, resolvePiContextModelKey(ctx), sessionDecisionCalibration(baseOptions.db, sessionIdForError), toolWireSchema), replay.measuredPrefix);
42917
+ }, err, readPiLkgFitEnvelope(ctx, pi, resolvePiContextModelKey(ctx), sessionDecisionCalibration(baseOptions.db, sessionIdForError), toolWireSchema), changed ? undefined : replay.measuredPrefix);
42308
42918
  const reason = piStorageErrorReason(err);
42309
42919
  logPiLkgRecovery(sessionIdForError, `${failureLabel} ${reason}: LKG replay served ${replay.messages.length} messages instead of raw ${rawMessageCount}`);
42310
42920
  budget.assertOutcome();
42311
- lkgCoordinator.noteRecovery(lkgPassSnapshot, replay.messages);
42921
+ lkgCoordinator.noteRecovery(lkgPassSnapshot, replay.messages, recoveryIds);
42312
42922
  if (!budget.sideTurn)
42313
42923
  captureServedEvidence(sessionIdForError, replay.messages, {
42314
42924
  recoveryWithoutTagState: true,
@@ -42321,6 +42931,8 @@ function registerPiContextHandler(pi, baseOptions) {
42321
42931
  logPiLkgRecovery(sessionIdForError, `${failureLabel} ${piStorageErrorReason(err)}: LKG unavailable (${replay.reason}); refusing unreduced ${rawMessageCount}-message input`);
42322
42932
  } catch (replayError) {
42323
42933
  budget.assertOutcome();
42934
+ if (replayError instanceof DegradedPassRefusalError)
42935
+ throw replayError;
42324
42936
  if (replayError instanceof PiStorageBusyError)
42325
42937
  throw replayError;
42326
42938
  logPiLkgRecovery(sessionIdForError, `${failureLabel} ${piStorageErrorReason(err)}: LKG replay unavailable (${replayError instanceof Error ? replayError.message : String(replayError)}); refusing unreduced ${rawMessageCount}-message input`);
@@ -43638,7 +44250,7 @@ async function runPipeline(args) {
43638
44250
  protectedToolTags,
43639
44251
  protectedCutoff: usesTokenProtection ? protectionWindowForPass.cutoff : undefined,
43640
44252
  routine: routineCleanupApplied,
43641
- staleReduceStripEnabled: args.canUseEmptySentinels,
44253
+ staleReduceStripEnabled: true,
43642
44254
  onStaleReduceEdit: recordStaleReduceEdit,
43643
44255
  emergency: args.forceMaterialization === true && args.emergencyCeilingTokens !== undefined && args.emergencyCeilingTokens > 0 ? {
43644
44256
  currentTotalInputTokens: args.contextUsage.inputTokens,
@@ -43654,7 +44266,7 @@ async function runPipeline(args) {
43654
44266
  protectedToolTags,
43655
44267
  protectedCutoff: usesTokenProtection ? protectionWindowForPass.cutoff : undefined,
43656
44268
  routine: true,
43657
- staleReduceStripEnabled: args.canUseEmptySentinels,
44269
+ staleReduceStripEnabled: true,
43658
44270
  onStaleReduceEdit: recordStaleReduceEdit,
43659
44271
  caveman: args.isSubagent ? undefined : args.heuristics.caveman
43660
44272
  }, getActiveTagsBySession2(args.db, args.sessionId), stableIdResolver);
@@ -43840,16 +44452,50 @@ async function runPipeline(args) {
43840
44452
  }
43841
44453
  }
43842
44454
  }
43843
- if (args.canUseEmptySentinels) {
44455
+ {
43844
44456
  const tProcessedImages = performance.now();
43845
44457
  try {
44458
+ const imagePositionTags = new Map;
44459
+ const noteImagePosition = (id, tag) => {
44460
+ if (id && tag > (imagePositionTags.get(id) ?? 0))
44461
+ imagePositionTags.set(id, tag);
44462
+ };
44463
+ for (const [tagNumber, target] of targets) {
44464
+ for (const { message, part } of target.mutationParts ?? []) {
44465
+ if (typeof part === "object" && part !== null && "kind" in part && part.kind === "text")
44466
+ noteImagePosition(message.info.id, tagNumber);
44467
+ }
44468
+ }
44469
+ const imageToolTags = new Map(allTagsForPass.filter((tag) => tag.type === "tool").map((tag) => [
44470
+ `${tag.toolOwnerMessageId ?? ""}\x00${tag.messageId}`,
44471
+ tag.tagNumber
44472
+ ]));
44473
+ const imageToolOwners = new Map;
44474
+ for (let index = 0;index < workingMessages.length; index++) {
44475
+ const message = workingMessages[index];
44476
+ const id = stableIdResolver(message, index);
44477
+ if (message.role === "assistant" && Array.isArray(message.content)) {
44478
+ for (const part of message.content) {
44479
+ if (part.type !== "toolCall")
44480
+ continue;
44481
+ imageToolOwners.set(part.id, id ?? "");
44482
+ noteImagePosition(id, imageToolTags.get(`${id ?? ""}\x00${part.id}`) ?? 0);
44483
+ }
44484
+ } else if (message.role === "toolResult") {
44485
+ noteImagePosition(id, imageToolTags.get(`${imageToolOwners.get(message.toolCallId) ?? ""}\x00${message.toolCallId}`) ?? 0);
44486
+ }
44487
+ }
44488
+ const protectedImageToolKeys = new Set(activeTags.filter((tag) => tag.type === "tool" && protectedToolTags.has(tag.tagNumber)).map((tag) => tag.toolOwnerMessageId ? `${tag.toolOwnerMessageId}\x00${tag.messageId}` : tag.messageId));
43846
44489
  const imageResult = stripPiProcessedImages({
43847
44490
  db: args.db,
43848
44491
  sessionId: args.sessionId,
43849
44492
  messages: workingMessages,
43850
44493
  detect: isCacheBustingPass && !protectedSignedPrefix,
43851
44494
  watermark: getMaxDroppedTagNumber2(args.db, args.sessionId),
43852
- messageIdToMaxTag,
44495
+ messageIdToMaxTag: imagePositionTags,
44496
+ protectedCutoff: usesTokenProtection ? protectionWindowForPass.cutoff : protectedTagNumbersForPass.size > 0 ? Math.min(...protectedTagNumbersForPass) : null,
44497
+ usagePercentage: args.contextUsage.percentage,
44498
+ protectedToolKeys: protectedImageToolKeys,
43853
44499
  stableId: stableIdResolver,
43854
44500
  onFirstApplication: recordFirstApplicationAt
43855
44501
  });
@@ -43999,6 +44645,18 @@ async function runPipeline(args) {
43999
44645
  const deferredHistoryDrainEligible = historyWasConsumedThisPass && materializationSatisfiedThisPass && (deferredHistoryWasPendingAtPassStart || hasPendingMaterializeSignal) && !suppressDeferredHistoryDrain && !casLost;
44000
44646
  let preserveDeferredMaterializationForMarkerDrain = false;
44001
44647
  let pendingForDrain = null;
44648
+ if (!deferredHistoryDrainEligible && (deferredHistoryWasPendingAtPassStart || hasPendingMaterializeSignal)) {
44649
+ try {
44650
+ const pending = getPendingPiCompactionMarkerState(args.db, args.sessionId);
44651
+ if (pending)
44652
+ logPiCompactionMarkerDrainGateOnce(args.sessionId, pending, "drain-not-eligible", {
44653
+ historyConsumed: historyWasConsumedThisPass,
44654
+ materializationSatisfied: materializationSatisfiedThisPass,
44655
+ drainSuppressed: suppressDeferredHistoryDrain,
44656
+ casLost
44657
+ });
44658
+ } catch {}
44659
+ }
44002
44660
  if (deferredHistoryDrainEligible) {
44003
44661
  try {
44004
44662
  args.assertCurrentPass?.();
@@ -44073,7 +44731,7 @@ async function runPipeline(args) {
44073
44731
  if (outcome.kind === "waiting-for-entry") {
44074
44732
  suppressDeferredHistoryDrain = true;
44075
44733
  preserveDeferredMaterializationForMarkerDrain = true;
44076
- } else if (outcome.kind === "retryable-failure") {} else if (markerEquivalent && clearPendingPiCompactionMarkerStateIf(args.db, args.sessionId, pending)) {
44734
+ } else if (outcome.kind === "retryable-failure") {} else if (markerEquivalent && clearPendingPiCompactionMarkerStateIf(args.db, args.sessionId, pending, outcome.kind === "stale-skip" ? outcome.reason : outcome.kind)) {
44077
44735
  consumeDeferredHistoryRefresh(args.sessionId);
44078
44736
  } else if (markerEquivalent) {
44079
44737
  casLost = true;
@@ -44291,12 +44949,6 @@ function applyNoteNudges(args) {
44291
44949
  for (const anchor of noteAnchors) {
44292
44950
  appendReminderToUserMessageByIdPi(messages, replayMessageIdByIndex, anchor.messageId, anchor.text);
44293
44951
  }
44294
- const autoSearchDecisions = args.postTransformSnapshot?.autoSearchDecisions ?? getAutoSearchHintDecisions(db, sessionId);
44295
- for (const decision of autoSearchDecisions) {
44296
- if (decision.decision === "hint" && getUnpublishedAutoSearchNoHint(sessionId, decision.messageId) === undefined) {
44297
- appendReminderToUserMessageByIdPi(messages, replayMessageIdByIndex, decision.messageId, decision.text);
44298
- }
44299
- }
44300
44952
  logTransformTiming(sessionId, "stickyReplayDecisions", tStickyReplay);
44301
44953
  const latestUser = findLatestUserMessageIdPi(messages, messageIdByIndex);
44302
44954
  const latestUserId = latestUser?.messageId ?? null;
@@ -44306,7 +44958,7 @@ function applyNoteNudges(args) {
44306
44958
  userMessageIds: [...messageIdByIndex].filter(([index]) => messages[index]?.role === "user").map(([, id]) => id),
44307
44959
  anchorMessageId: latestUserId,
44308
44960
  isLiveTail: latestUser?.index === messages.length - 1,
44309
- isCacheBustingPass: args.isCacheBusting
44961
+ prefixRewriteCoversAnchor: false
44310
44962
  });
44311
44963
  const noteReadStillVisible = hasVisibleNoteReadCallPi(messages);
44312
44964
  const deferredNoteText = args.postTransformSnapshot?.noteTriggerPending === false ? null : peekNoteNudgeText(db, sessionId, latestUserId, projectIdentity, noteReadStillVisible, eligibility);
@@ -44330,6 +44982,12 @@ function applyNoteNudges(args) {
44330
44982
  sessionLog2(sessionId, `Pi note-nudge delivery skipped wire append: ${outcome.kind}`);
44331
44983
  }
44332
44984
  }
44985
+ const autoSearchDecisions = args.postTransformSnapshot?.autoSearchDecisions ?? getAutoSearchHintDecisions(db, sessionId);
44986
+ for (const decision of autoSearchDecisions) {
44987
+ if (decision.decision === "hint" && getUnpublishedAutoSearchNoHint(sessionId, decision.messageId) === undefined) {
44988
+ appendReminderToUserMessageByIdPi(messages, replayMessageIdByIndex, decision.messageId, decision.text);
44989
+ }
44990
+ }
44333
44991
  if (args.isCacheBusting) {
44334
44992
  const visibleIds = new Set(messageIdByIndex.values());
44335
44993
  const realMessageCount = Math.max(0, messages.length - (args.syntheticLeadingCount ?? 0));
@@ -47403,9 +48061,9 @@ function readPiWrapupBranchEntries(ctx) {
47403
48061
  function observePiMessageActivity(db, sessionId, timestamp) {
47404
48062
  observeSessionActivity(db, sessionId, timestamp);
47405
48063
  }
47406
- function latestPiMessageTime(path) {
48064
+ async function latestPiMessageTime(path) {
47407
48065
  let latest = 0;
47408
- for (const entry of sessionEntries(path)) {
48066
+ for await (const entry of asyncSessionEntries(path)) {
47409
48067
  const row = entry;
47410
48068
  if (row.type === "message" && typeof row.message?.timestamp === "number")
47411
48069
  latest = Math.max(latest, row.message.timestamp);
@@ -47413,12 +48071,12 @@ function latestPiMessageTime(path) {
47413
48071
  return latest || undefined;
47414
48072
  }
47415
48073
  async function backfillPiSessionActivity(db, harness, paths) {
47416
- await backfillSessionActivity(db, harness, (sessionId) => {
48074
+ await backfillSessionActivity(db, harness, async (sessionId) => {
47417
48075
  const path = paths.get(sessionId);
47418
48076
  if (!path)
47419
48077
  return;
47420
48078
  try {
47421
- return latestPiMessageTime(path);
48079
+ return await latestPiMessageTime(path);
47422
48080
  } catch {
47423
48081
  return;
47424
48082
  }