@cortexkit/pi-magic-context 0.31.4 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -159849,8 +159849,11 @@ var MagicContextConfigSchema = exports_external.object({
159849
159849
  provider: "local",
159850
159850
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
159851
159851
  }).describe("Embedding provider configuration"),
159852
- temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
159852
+ temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add compact date ranges to compartment headings. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
159853
159853
  keep_subagents: exports_external.boolean().default(false).describe("Debug: keep the child sessions Magic Context spawns for its own subagents (historian, dreamer, sidekick, memory-migration) instead of deleting them on success. Useful for short-term inspection/data collection — their full transcript (prompt, tool calls, token usage, output) stays in the host session store. Kept sessions accumulate until manually cleared; leave false for normal use. Requires a restart to take effect."),
159854
+ shadow_transform: exports_external.object({
159855
+ enabled: exports_external.boolean().default(false).describe("Dev flag: mirror every transform pass to the Magic Context Rust module over subc and byte-compare outputs. No behavior impact; default off.")
159856
+ }).default({ enabled: false }).describe("Developer-only shadow transform lane. When enabled, the plugin fire-and-forgets a copy of each finalized transform to the Rust module for comparison; the live output is unchanged."),
159854
159857
  todowrite: exports_external.object({
159855
159858
  enabled: exports_external.boolean().default(true).describe("Pi only: register Magic Context's todowrite task-list tool. Disable if you use your own todo extension. OpenCode ships its own built-in todowrite; this setting has no effect there."),
159856
159859
  overlay: exports_external.boolean().default(true).describe("Pi only: show the persistent todo overlay above the editor while tasks are active.")
@@ -160007,6 +160010,10 @@ function stripUnsafeProjectConfigFields(projectRaw) {
160007
160010
  delete projectRaw.sqlite;
160008
160011
  warnings.push("Ignoring sqlite.* from project config (security: SQLite cache/mmap PRAGMAs apply to the " + "process-global shared database handle; only user-level config may set them).");
160009
160012
  }
160013
+ if ("shadow_transform" in projectRaw) {
160014
+ delete projectRaw.shadow_transform;
160015
+ warnings.push("Ignoring shadow_transform from project config (security: this developer-only mirror lane is user-level only).");
160016
+ }
160010
160017
  const embedding = projectRaw.embedding;
160011
160018
  if (isPlainObject3(embedding)) {
160012
160019
  const removed = [];
@@ -160679,6 +160686,9 @@ function loadPiConfigDetailed(opts = {}) {
160679
160686
  };
160680
160687
  }
160681
160688
 
160689
+ // src/embedding-bootstrap.ts
160690
+ import { statSync as statSync3 } from "node:fs";
160691
+
160682
160692
  // ../plugin/src/features/magic-context/memory/embedding.ts
160683
160693
  init_logger();
160684
160694
 
@@ -163471,8 +163481,38 @@ function handleUntrustedLoad(db, projectIdentity, directory, detailed) {
163471
163481
  }
163472
163482
 
163473
163483
  // src/embedding-bootstrap.ts
163484
+ var registrationFingerprintsByDatabase = new WeakMap;
163485
+ function configCandidatePaths(directory, loadedPaths) {
163486
+ const projectBase = cortexKitProjectConfigBasePath(directory);
163487
+ const userBase = cortexKitUserConfigBasePath();
163488
+ return [
163489
+ `${projectBase}.jsonc`,
163490
+ `${projectBase}.json`,
163491
+ `${userBase}.jsonc`,
163492
+ `${userBase}.json`,
163493
+ ...loadedPaths
163494
+ ].filter((path4, index, paths) => paths.indexOf(path4) === index);
163495
+ }
163496
+ function configFingerprint(paths) {
163497
+ return paths.map((path4) => {
163498
+ try {
163499
+ const stat2 = statSync3(path4);
163500
+ return `${path4}:${stat2.size}:${stat2.mtimeMs}`;
163501
+ } catch {
163502
+ return `${path4}:missing`;
163503
+ }
163504
+ }).join("|");
163505
+ }
163474
163506
  async function ensureProjectRegisteredFromPiDirectory(directory, db) {
163475
163507
  const projectIdentity = resolveProjectIdentity(directory);
163508
+ let registrationFingerprints = registrationFingerprintsByDatabase.get(db);
163509
+ if (!registrationFingerprints) {
163510
+ registrationFingerprints = new Map;
163511
+ registrationFingerprintsByDatabase.set(db, registrationFingerprints);
163512
+ }
163513
+ const cached2 = registrationFingerprints.get(projectIdentity);
163514
+ if (cached2 && configFingerprint(cached2.paths) === cached2.fingerprint)
163515
+ return;
163476
163516
  const detailed = loadPiConfigDetailed({ cwd: directory });
163477
163517
  if (isConfigLoadUntrusted(detailed)) {
163478
163518
  handleUntrustedLoad(db, projectIdentity, directory, detailed);
@@ -163483,6 +163523,11 @@ async function ensureProjectRegisteredFromPiDirectory(directory, db) {
163483
163523
  gitCommitEnabled: detailed.config.memory.git_commit_indexing.enabled
163484
163524
  };
163485
163525
  registerProjectEmbedding(db, projectIdentity, detailed.config.embedding, features, directory);
163526
+ const fingerprintPaths = configCandidatePaths(directory, detailed.loadedFromPaths);
163527
+ registrationFingerprints.set(projectIdentity, {
163528
+ paths: fingerprintPaths,
163529
+ fingerprint: configFingerprint(fingerprintPaths)
163530
+ });
163486
163531
  }
163487
163532
  // ../plugin/src/features/magic-context/compartment-lease.ts
163488
163533
  var COMPARTMENT_LEASE_TTL_MS = 5 * 60 * 1000;
@@ -164102,6 +164147,12 @@ function parseJsonRecord(value) {
164102
164147
  return null;
164103
164148
  }
164104
164149
  }
164150
+ function isRawCompactionSummaryInfo(info) {
164151
+ if (info === null || typeof info !== "object" || Array.isArray(info))
164152
+ return false;
164153
+ const candidate = info;
164154
+ return candidate.summary === true && candidate.finish === "stop";
164155
+ }
164105
164156
  function parseJsonUnknown(value) {
164106
164157
  try {
164107
164158
  return JSON.parse(value);
@@ -164124,7 +164175,7 @@ function attachRawPartVersion(value, timeUpdated) {
164124
164175
  return value;
164125
164176
  }
164126
164177
  function readRawSessionMessagesFromDb(db, sessionId) {
164127
- const messageRows = db.prepare("SELECT id, data, time_updated FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
164178
+ const messageRows = db.prepare("SELECT id, data, time_created, time_updated FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
164128
164179
  const partRows = db.prepare("SELECT message_id, data, time_updated FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
164129
164180
  const partsByMessageId = new Map;
164130
164181
  for (const part of partRows) {
@@ -164132,10 +164183,7 @@ function readRawSessionMessagesFromDb(db, sessionId) {
164132
164183
  list.push(attachRawPartVersion(parseJsonUnknown(part.data), part.time_updated));
164133
164184
  partsByMessageId.set(part.message_id, list);
164134
164185
  }
164135
- const filtered = messageRows.filter((row) => {
164136
- const info = parseJsonRecord(row.data);
164137
- return !(info?.summary === true && info?.finish === "stop");
164138
- });
164186
+ const filtered = messageRows.filter((row) => !isRawCompactionSummaryInfo(parseJsonRecord(row.data)));
164139
164187
  return filtered.flatMap((row, index) => {
164140
164188
  const info = parseJsonRecord(row.data);
164141
164189
  if (!info)
@@ -164146,6 +164194,7 @@ function readRawSessionMessagesFromDb(db, sessionId) {
164146
164194
  id: row.id,
164147
164195
  role,
164148
164196
  parts: partsByMessageId.get(row.id) ?? [],
164197
+ createdAt: row.time_created ?? null,
164149
164198
  version: row.time_updated ?? null
164150
164199
  };
164151
164200
  });
@@ -164160,7 +164209,7 @@ function readRawSessionTailFromDb(db, sessionId, baseOrdinal, anchorMessageId) {
164160
164209
  const anchorInfo = parseJsonRecord(anchorRow.data ?? "");
164161
164210
  if (anchorInfo?.summary === true && anchorInfo?.finish === "stop")
164162
164211
  return null;
164163
- const messageRows = db.prepare(`SELECT id, data, time_updated FROM message
164212
+ const messageRows = db.prepare(`SELECT id, data, time_created, time_updated FROM message
164164
164213
  WHERE session_id = ?
164165
164214
  AND (time_created > ? OR (time_created = ? AND id >= ?))
164166
164215
  ORDER BY time_created ASC, id ASC`).all(sessionId, anchorRow.time_created, anchorRow.time_created, anchorRow.id).filter(isRawMessageRow);
@@ -164196,6 +164245,7 @@ function readRawSessionTailFromDb(db, sessionId, baseOrdinal, anchorMessageId) {
164196
164245
  id: row.id,
164197
164246
  role: typeof info.role === "string" ? info.role : "unknown",
164198
164247
  parts: partsByMessageId.get(row.id) ?? [],
164248
+ createdAt: row.time_created ?? null,
164199
164249
  version: row.time_updated ?? null
164200
164250
  });
164201
164251
  ord += 1;
@@ -164714,9 +164764,9 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
164714
164764
  // ../plugin/src/tools/ctx-expand/constants.ts
164715
164765
  var CTX_EXPAND_DESCRIPTION = `Recover the original conversation from your compacted history.
164716
164766
 
164717
- Older parts of this session are summarized into <compartment> blocks inside <session-history> — e.g. <compartment start="120" end="245" title="Fixed tagger collision">. Each one replaces the raw messages in that ordinal range with a summary. When the summary isn't enough — you need exact wording, a specific value, an error message, or the reasoning behind a decision — expand the range:
164767
+ Older parts of this session are summarized under \`## start-end · date · title\` headings inside <session-history> — e.g. \`## 120-245 · … · Fixed tagger collision\`. Each heading replaces the raw messages in that ordinal range with a summary. When the summary isn't enough — you need exact wording, a specific value, an error message, or the reasoning behind a decision — expand the range:
164718
164768
 
164719
- ctx_expand(start=120, end=245) ← the compartment's own start/end attributes
164769
+ ctx_expand(start=120, end=245) ← the heading's start/end range
164720
164770
 
164721
164771
  Returns the raw transcript as [N] U:/A: lines, capped at ~15K tokens; an oversized range returns the head and tells you where to continue. Also works with ordinals from ctx_search message results — expand a window around a hit (e.g. start=N-10, end=N+5). Ranges after the last compartment are your live tail — already visible in context, not expandable.
164722
164772
 
@@ -169128,7 +169178,7 @@ function resolvePiStableId(msg, index, entryIds, entryIdByRef) {
169128
169178
  const role = m.role ?? "unknown";
169129
169179
  return typeof m.timestamp === "number" ? `pi-msg-${index}-${m.timestamp}-${role}` : `pi-msg-${index}-${role}`;
169130
169180
  }
169131
- function isMidTurnPi(event, _sessionId) {
169181
+ function isMidTurnPi(event, _sessionId, branchEntries) {
169132
169182
  const messages = event?.messages;
169133
169183
  if (!Array.isArray(messages))
169134
169184
  return false;
@@ -169147,7 +169197,7 @@ function isMidTurnPi(event, _sessionId) {
169147
169197
  }
169148
169198
  if (latestAssistant === null)
169149
169199
  return false;
169150
- if (hasRealUserAfter(messages, latestAssistantIndex))
169200
+ if (hasRealUserAfter(messages, latestAssistantIndex, branchEntries))
169151
169201
  return false;
169152
169202
  if (latestAssistant.stopReason === "toolUse")
169153
169203
  return true;
@@ -169171,12 +169221,27 @@ function isMidTurnPi(event, _sessionId) {
169171
169221
  }
169172
169222
  return false;
169173
169223
  }
169174
- function hasRealUserAfter(messages, latestAssistantIndex) {
169175
- for (const msg of messages.slice(latestAssistantIndex + 1)) {
169176
- if (msg === null || typeof msg !== "object")
169224
+ function hasRealUserAfter(messages, latestAssistantIndex, branchEntries) {
169225
+ if (!branchEntries)
169226
+ return false;
169227
+ const genuineUserMessages = new Set;
169228
+ for (const entry of branchEntries) {
169229
+ if (entry === null || typeof entry !== "object")
169177
169230
  continue;
169178
- if (msg.role === "user")
169231
+ const record4 = entry;
169232
+ if (record4.type !== "message")
169233
+ continue;
169234
+ const message = record4.message;
169235
+ if (message === null || typeof message !== "object")
169236
+ continue;
169237
+ if (message.role === "user") {
169238
+ genuineUserMessages.add(message);
169239
+ }
169240
+ }
169241
+ for (const msg of messages.slice(latestAssistantIndex + 1)) {
169242
+ if (msg !== null && typeof msg === "object" && genuineUserMessages.has(msg)) {
169179
169243
  return true;
169244
+ }
169180
169245
  }
169181
169246
  return false;
169182
169247
  }
@@ -170406,7 +170471,7 @@ function getMemoryVerifications(db, memoryIds) {
170406
170471
  }
170407
170472
  // ../plugin/src/features/magic-context/memory/verification-paths.ts
170408
170473
  import { execFile } from "node:child_process";
170409
- import { existsSync as existsSync9, realpathSync as realpathSync2, statSync as statSync3 } from "node:fs";
170474
+ import { existsSync as existsSync9, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
170410
170475
  import path4 from "node:path";
170411
170476
  import { promisify } from "node:util";
170412
170477
  var execFileAsync = promisify(execFile);
@@ -170548,7 +170613,7 @@ async function normalizeVerificationFiles(args) {
170548
170613
  }
170549
170614
  if (existsSync9(candidate)) {
170550
170615
  try {
170551
- if (statSync3(candidate).isDirectory()) {
170616
+ if (statSync4(candidate).isDirectory()) {
170552
170617
  warnings.push(`Skipped verification path "${value}" because it is a directory.`);
170553
170618
  continue;
170554
170619
  }
@@ -170798,7 +170863,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
170798
170863
  }
170799
170864
  // ../plugin/src/features/magic-context/project-docs-hash.ts
170800
170865
  import { createHash as createHash7 } from "node:crypto";
170801
- import { lstatSync, readFileSync as readFileSync5, statSync as statSync4 } from "node:fs";
170866
+ import { lstatSync, readFileSync as readFileSync5, statSync as statSync5 } from "node:fs";
170802
170867
  import path5 from "node:path";
170803
170868
  var PROJECT_DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
170804
170869
  var PROJECT_DOCS_DELIMITER = `
@@ -170829,7 +170894,7 @@ function fingerprintFile(filePath) {
170829
170894
  }
170830
170895
  function readDirectoryMtimeMs(projectDirectory) {
170831
170896
  try {
170832
- return statSync4(projectDirectory).mtimeMs;
170897
+ return statSync5(projectDirectory).mtimeMs;
170833
170898
  } catch {
170834
170899
  return 0;
170835
170900
  }
@@ -170919,6 +170984,160 @@ function readProjectDocsCanonical(projectDirectory) {
170919
170984
  });
170920
170985
  return { renderedBlock, canonicalHash };
170921
170986
  }
170987
+ // ../plugin/src/features/magic-context/storage-clone.ts
170988
+ function runImmediate2(db, body) {
170989
+ db.exec("BEGIN IMMEDIATE");
170990
+ let committed = false;
170991
+ try {
170992
+ const result = body();
170993
+ db.exec("COMMIT");
170994
+ committed = true;
170995
+ return result;
170996
+ } finally {
170997
+ if (!committed) {
170998
+ try {
170999
+ db.exec("ROLLBACK");
171000
+ } catch {}
171001
+ }
171002
+ }
171003
+ }
171004
+ function countRows(db, table, sessionId) {
171005
+ const row = db.prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE session_id = ?`).get(sessionId);
171006
+ return typeof row?.count === "number" ? row.count : 0;
171007
+ }
171008
+ function filterIdBlob(raw, includeMessageId) {
171009
+ if (!raw)
171010
+ return "";
171011
+ try {
171012
+ const value = JSON.parse(raw);
171013
+ if (!Array.isArray(value))
171014
+ return "";
171015
+ const filtered = value.filter((id) => typeof id === "string" && includeMessageId(id));
171016
+ return filtered.length > 0 ? JSON.stringify(filtered) : "";
171017
+ } catch {
171018
+ return "";
171019
+ }
171020
+ }
171021
+ function clampWatermark(value, maxCopiedTag) {
171022
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
171023
+ return 0;
171024
+ return Math.min(Math.floor(value), maxCopiedTag);
171025
+ }
171026
+ function copySessionStateForClone(db, sourceSessionId, destinationSessionId, filter) {
171027
+ return runImmediate2(db, () => {
171028
+ if (countRows(db, "compartments", destinationSessionId) > 0 || countRows(db, "tags", destinationSessionId) > 0) {
171029
+ return {
171030
+ kind: "destination-not-empty",
171031
+ compartmentsCopied: 0,
171032
+ tagsCopied: 0,
171033
+ pendingOpsCopied: 0,
171034
+ pendingMarkerMigrated: false
171035
+ };
171036
+ }
171037
+ const sourceCompartments = db.prepare(`SELECT sequence, start_message, end_message, start_message_id, end_message_id,
171038
+ title, content, p1, p2, p3, p4, importance, episode_type, legacy,
171039
+ created_at, harness
171040
+ FROM compartments WHERE session_id = ? ORDER BY sequence ASC`).all(sourceSessionId);
171041
+ const insertCompartment = db.prepare(`INSERT INTO compartments
171042
+ (session_id, sequence, start_message, end_message, start_message_id,
171043
+ end_message_id, title, content, p1, p2, p3, p4, importance,
171044
+ episode_type, legacy, created_at, harness)
171045
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
171046
+ const copiedCompartments = [];
171047
+ for (const row of sourceCompartments) {
171048
+ const startMessage = filter.resolveBoundaryOrdinal(row.start_message_id);
171049
+ const endMessage = filter.resolveBoundaryOrdinal(row.end_message_id);
171050
+ if (startMessage === undefined || endMessage === undefined)
171051
+ continue;
171052
+ insertCompartment.run(destinationSessionId, row.sequence, startMessage, endMessage, row.start_message_id, row.end_message_id, row.title, row.content, row.p1, row.p2, row.p3, row.p4, row.importance, row.episode_type, row.legacy, row.created_at, row.harness);
171053
+ copiedCompartments.push({
171054
+ sequence: row.sequence,
171055
+ startMessage,
171056
+ endMessage,
171057
+ startMessageId: row.start_message_id,
171058
+ endMessageId: row.end_message_id
171059
+ });
171060
+ }
171061
+ const sourceTags = db.prepare(`SELECT message_id, type, status, byte_size, tag_number, harness,
171062
+ entry_fingerprint, token_count, input_token_count,
171063
+ reasoning_token_count, reasoning_byte_size, drop_mode, tool_name,
171064
+ input_byte_size, caveman_depth, tool_owner_message_id
171065
+ FROM tags WHERE session_id = ? ORDER BY tag_number ASC`).all(sourceSessionId);
171066
+ const insertTag = db.prepare(`INSERT INTO tags
171067
+ (session_id, message_id, type, status, byte_size, tag_number, harness,
171068
+ entry_fingerprint, token_count, input_token_count, reasoning_token_count,
171069
+ reasoning_byte_size, drop_mode, tool_name, input_byte_size,
171070
+ caveman_depth, tool_owner_message_id)
171071
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
171072
+ const copiedTagNumbers = [];
171073
+ for (const row of sourceTags) {
171074
+ if (!filter.includeTag({
171075
+ messageId: row.message_id,
171076
+ type: row.type,
171077
+ tagNumber: row.tag_number,
171078
+ toolOwnerMessageId: row.tool_owner_message_id
171079
+ })) {
171080
+ continue;
171081
+ }
171082
+ insertTag.run(destinationSessionId, row.message_id, row.type, row.status, row.byte_size, row.tag_number, row.harness, row.entry_fingerprint, row.token_count, row.input_token_count, row.reasoning_token_count, row.reasoning_byte_size, row.drop_mode, row.tool_name, row.input_byte_size, row.caveman_depth, row.tool_owner_message_id);
171083
+ copiedTagNumbers.push(row.tag_number);
171084
+ }
171085
+ if (copiedTagNumbers.length > 0) {
171086
+ const placeholders3 = copiedTagNumbers.map(() => "?").join(", ");
171087
+ db.prepare(`INSERT INTO source_contents (tag_id, session_id, content, created_at, harness)
171088
+ SELECT tag_id, ?, content, created_at, harness
171089
+ FROM source_contents
171090
+ WHERE session_id = ? AND tag_id IN (${placeholders3})`).run(destinationSessionId, sourceSessionId, ...copiedTagNumbers);
171091
+ db.prepare(`INSERT INTO pending_ops (session_id, tag_id, operation, queued_at, harness)
171092
+ SELECT ?, tag_id, operation, queued_at, harness
171093
+ FROM pending_ops
171094
+ WHERE session_id = ? AND tag_id IN (${placeholders3})`).run(destinationSessionId, sourceSessionId, ...copiedTagNumbers);
171095
+ }
171096
+ const meta3 = db.prepare(`SELECT cleared_reasoning_through_tag, tool_reclaim_watermark,
171097
+ pi_stable_id_scheme, stripped_placeholder_ids,
171098
+ stale_reduce_stripped_ids, processed_image_stripped_ids,
171099
+ pending_pi_compaction_marker_state, last_todo_state,
171100
+ todo_synthetic_call_id, todo_synthetic_anchor_message_id,
171101
+ todo_synthetic_state_json
171102
+ FROM session_meta WHERE session_id = ?`).get(sourceSessionId);
171103
+ const maxCopiedTag = copiedTagNumbers.reduce((max, value) => Math.max(max, value), 0);
171104
+ const pendingMarker = filter.selectPendingPiMarker(meta3?.pending_pi_compaction_marker_state ?? null, copiedCompartments);
171105
+ const todoAnchor = meta3?.todo_synthetic_anchor_message_id ?? "";
171106
+ const migrateTodo = todoAnchor.length > 0 && filter.includeMessageId(todoAnchor);
171107
+ db.prepare(`INSERT INTO session_meta
171108
+ (session_id, harness, counter, cleared_reasoning_through_tag,
171109
+ tool_reclaim_watermark, pi_stable_id_scheme, stripped_placeholder_ids,
171110
+ stale_reduce_stripped_ids, processed_image_stripped_ids,
171111
+ pending_pi_compaction_marker_state, last_todo_state,
171112
+ todo_synthetic_call_id, todo_synthetic_anchor_message_id,
171113
+ todo_synthetic_state_json)
171114
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
171115
+ ON CONFLICT(session_id) DO UPDATE SET
171116
+ harness = excluded.harness,
171117
+ counter = excluded.counter,
171118
+ cleared_reasoning_through_tag = excluded.cleared_reasoning_through_tag,
171119
+ tool_reclaim_watermark = excluded.tool_reclaim_watermark,
171120
+ pi_stable_id_scheme = excluded.pi_stable_id_scheme,
171121
+ stripped_placeholder_ids = excluded.stripped_placeholder_ids,
171122
+ stale_reduce_stripped_ids = excluded.stale_reduce_stripped_ids,
171123
+ processed_image_stripped_ids = excluded.processed_image_stripped_ids,
171124
+ pending_pi_compaction_marker_state = excluded.pending_pi_compaction_marker_state,
171125
+ last_todo_state = excluded.last_todo_state,
171126
+ todo_synthetic_call_id = excluded.todo_synthetic_call_id,
171127
+ todo_synthetic_anchor_message_id = excluded.todo_synthetic_anchor_message_id,
171128
+ todo_synthetic_state_json = excluded.todo_synthetic_state_json,
171129
+ cached_m0_bytes = NULL,
171130
+ cached_m1_bytes = NULL`).run(destinationSessionId, getHarness(), maxCopiedTag, clampWatermark(meta3?.cleared_reasoning_through_tag ?? null, maxCopiedTag), clampWatermark(meta3?.tool_reclaim_watermark ?? null, maxCopiedTag), typeof meta3?.pi_stable_id_scheme === "number" && Number.isFinite(meta3.pi_stable_id_scheme) ? meta3.pi_stable_id_scheme : null, filterIdBlob(meta3?.stripped_placeholder_ids ?? null, filter.includeMessageId), filterIdBlob(meta3?.stale_reduce_stripped_ids ?? null, filter.includeMessageId), filterIdBlob(meta3?.processed_image_stripped_ids ?? null, filter.includeMessageId), pendingMarker, migrateTodo ? meta3?.last_todo_state ?? "" : "", migrateTodo ? meta3?.todo_synthetic_call_id ?? "" : "", migrateTodo ? todoAnchor : "", migrateTodo ? meta3?.todo_synthetic_state_json ?? "" : "");
171131
+ const pendingOpsRow = db.prepare("SELECT COUNT(*) AS count FROM pending_ops WHERE session_id = ?").get(destinationSessionId);
171132
+ return {
171133
+ kind: "migrated",
171134
+ compartmentsCopied: copiedCompartments.length,
171135
+ tagsCopied: copiedTagNumbers.length,
171136
+ pendingOpsCopied: typeof pendingOpsRow?.count === "number" ? pendingOpsRow.count : 0,
171137
+ pendingMarkerMigrated: pendingMarker !== null
171138
+ };
171139
+ });
171140
+ }
170922
171141
  // ../plugin/src/features/magic-context/storage-m0-mutation-log.ts
170923
171142
  var M0_MUTATION_TYPES = new Set([
170924
171143
  "compartment_delete",
@@ -171885,6 +172104,36 @@ function parseStrippedBlob(raw) {
171885
172104
  } catch {}
171886
172105
  return [];
171887
172106
  }
172107
+ function getProcessedImageStrippedIds(db, sessionId) {
172108
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
172109
+ return new Set(parseStrippedBlob(row?.processed_image_stripped_ids));
172110
+ }
172111
+ function addProcessedImageStrippedIds(db, sessionId, ids) {
172112
+ const add = [...ids];
172113
+ if (add.length === 0)
172114
+ return true;
172115
+ ensureSessionMetaRow(db, sessionId);
172116
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
172117
+ const row = db.prepare("SELECT processed_image_stripped_ids FROM session_meta WHERE session_id = ?").get(sessionId);
172118
+ const rawStored = row ? row.processed_image_stripped_ids ?? null : null;
172119
+ const current = new Set(parseStrippedBlob(rawStored));
172120
+ let changed = false;
172121
+ for (const id of add) {
172122
+ if (!current.has(id)) {
172123
+ current.add(id);
172124
+ changed = true;
172125
+ }
172126
+ }
172127
+ if (!changed)
172128
+ return true;
172129
+ const nextBlob = JSON.stringify([...current]);
172130
+ const result = db.prepare("UPDATE session_meta SET processed_image_stripped_ids = ? WHERE session_id = ? AND processed_image_stripped_ids IS ?").run(nextBlob, sessionId, rawStored);
172131
+ if (result.changes > 0)
172132
+ return true;
172133
+ }
172134
+ sessionLog(sessionId, `processed_image_stripped_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
172135
+ return false;
172136
+ }
171888
172137
  function isPendingCompactionMarker(value) {
171889
172138
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
171890
172139
  }
@@ -172607,6 +172856,7 @@ var getTagNumbersByMessageIdStatements = new WeakMap;
172607
172856
  var deleteTagsByMessageIdStatements = new WeakMap;
172608
172857
  var getMaxTagNumberBySessionStatements = new WeakMap;
172609
172858
  var getTagNumberByMessageIdStatements = new WeakMap;
172859
+ var hasPiFallbackMessageTagStatements = new WeakMap;
172610
172860
  function getInsertTagStatement(db) {
172611
172861
  let stmt = insertTagStatements.get(db);
172612
172862
  if (!stmt) {
@@ -172750,6 +173000,18 @@ function getUpdateTagInputTokenCountStatement(db) {
172750
173000
  function updateTagTokenCount(db, sessionId, tagNumber, newTokenCount) {
172751
173001
  getUpdateTagTokenCountStatement(db).run(newTokenCount, sessionId, tagNumber);
172752
173002
  }
173003
+ function getPersistedToolTagAccounting(db, sessionId, tagNumber) {
173004
+ const row = db.prepare(`SELECT byte_size AS byteSize,
173005
+ token_count AS tokenCount,
173006
+ input_byte_size AS inputByteSize,
173007
+ input_token_count AS inputTokenCount
173008
+ FROM tags
173009
+ WHERE session_id = ? AND tag_number = ? AND type = 'tool'`).get(sessionId, tagNumber);
173010
+ if (!row || typeof row.byteSize !== "number" || row.tokenCount !== null && typeof row.tokenCount !== "number" || typeof row.inputByteSize !== "number" || row.inputTokenCount !== null && typeof row.inputTokenCount !== "number") {
173011
+ return null;
173012
+ }
173013
+ return row;
173014
+ }
172753
173015
  function getAllStatusTagTokenTotalsFlat(db, sessionId, floor = 0) {
172754
173016
  const rows = floor > 0 ? db.prepare(`SELECT type, message_id, tool_owner_message_id, token_count, input_token_count, reasoning_token_count
172755
173017
  FROM tags
@@ -172851,6 +173113,19 @@ function updateTagDropMode(db, sessionId, tagNumber, dropMode) {
172851
173113
  function updateCavemanDepth(db, sessionId, tagNumber, depth) {
172852
173114
  db.prepare("UPDATE tags SET caveman_depth = ? WHERE session_id = ? AND tag_number = ?").run(depth, sessionId, tagNumber);
172853
173115
  }
173116
+ function hasPiFallbackMessageTags(db, sessionId) {
173117
+ let statement = hasPiFallbackMessageTagStatements.get(db);
173118
+ if (!statement) {
173119
+ statement = db.prepare(`SELECT 1
173120
+ FROM tags
173121
+ WHERE session_id = ?
173122
+ AND type = 'message'
173123
+ AND message_id LIKE 'pi-msg-%'
173124
+ LIMIT 1`);
173125
+ hasPiFallbackMessageTagStatements.set(db, statement);
173126
+ }
173127
+ return statement.get(sessionId) != null;
173128
+ }
172854
173129
  function findAdoptableFallbackTags(db, sessionId, entryFingerprint) {
172855
173130
  const rows = db.prepare(`SELECT tag_number AS tagNumber, message_id AS messageId
172856
173131
  FROM tags
@@ -173021,27 +173296,24 @@ function adoptPiFallbackToolOwnerTag(db, sessionId, tagNumber, callId, oldOwnerM
173021
173296
  }
173022
173297
  const existing = getPiFallbackToolFoldTagRowByOwner(db, sessionId, callId, newOwnerMessageId);
173023
173298
  if (existing === null) {
173024
- const result2 = db.prepare(`UPDATE tags
173299
+ const result = db.prepare(`UPDATE tags
173025
173300
  SET tool_owner_message_id = ?
173026
173301
  WHERE session_id = ?
173027
173302
  AND tag_number = ?
173028
173303
  AND type = 'tool'
173029
173304
  AND message_id = ?
173030
173305
  AND tool_owner_message_id = ?`).run(newOwnerMessageId, sessionId, tagNumber, callId, oldOwnerMessageId);
173031
- return (result2.changes ?? 0) === 1 ? { action: "rekeyed", tagNumber } : { action: "skipped" };
173306
+ return (result.changes ?? 0) === 1 ? { action: "rekeyed", tagNumber } : { action: "skipped" };
173032
173307
  }
173033
173308
  if (existing.tagNumber === tagNumber) {
173034
173309
  return { action: "skipped" };
173035
173310
  }
173036
- foldDuplicateIntoSurvivor(db, sessionId, survivor, existing);
173037
- const result = db.prepare(`UPDATE tags
173038
- SET tool_owner_message_id = ?
173039
- WHERE session_id = ?
173040
- AND tag_number = ?
173041
- AND type = 'tool'
173042
- AND message_id = ?
173043
- AND tool_owner_message_id = ?`).run(newOwnerMessageId, sessionId, tagNumber, callId, oldOwnerMessageId);
173044
- return (result.changes ?? 0) === 1 ? { action: "folded", tagNumber, deletedTagNumbers: [existing.tagNumber] } : { action: "skipped" };
173311
+ foldDuplicateIntoSurvivor(db, sessionId, existing, survivor);
173312
+ return {
173313
+ action: "folded",
173314
+ tagNumber: existing.tagNumber,
173315
+ deletedTagNumbers: [tagNumber]
173316
+ };
173045
173317
  }
173046
173318
  function adoptPiFallbackMessageTag(db, sessionId, tagNumber, oldFallbackMessageId, newRealMessageId) {
173047
173319
  const survivor = getPiFallbackFoldTagRowByNumber(db, sessionId, tagNumber);
@@ -173050,26 +173322,28 @@ function adoptPiFallbackMessageTag(db, sessionId, tagNumber, oldFallbackMessageI
173050
173322
  }
173051
173323
  const duplicates = getPiFallbackMessageFoldTagRowsByMessageId(db, sessionId, newRealMessageId).filter((row) => row.tagNumber !== tagNumber);
173052
173324
  if (duplicates.length === 0) {
173053
- const result2 = db.prepare(`UPDATE tags
173325
+ const result = db.prepare(`UPDATE tags
173054
173326
  SET message_id = ?
173055
173327
  WHERE session_id = ?
173056
173328
  AND tag_number = ?
173057
173329
  AND type = 'message'
173058
173330
  AND message_id = ?`).run(newRealMessageId, sessionId, tagNumber, oldFallbackMessageId);
173059
- return (result2.changes ?? 0) === 1 ? { action: "rekeyed", tagNumber } : { action: "skipped" };
173331
+ return (result.changes ?? 0) === 1 ? { action: "rekeyed", tagNumber } : { action: "skipped" };
173060
173332
  }
173061
- const deletedTagNumbers = [];
173062
- for (const duplicate of duplicates) {
173063
- foldDuplicateIntoSurvivor(db, sessionId, survivor, duplicate);
173333
+ const realSurvivor = duplicates[0];
173334
+ if (!realSurvivor)
173335
+ return { action: "skipped" };
173336
+ const deletedTagNumbers = [tagNumber];
173337
+ foldDuplicateIntoSurvivor(db, sessionId, realSurvivor, survivor);
173338
+ for (const duplicate of duplicates.slice(1)) {
173339
+ foldDuplicateIntoSurvivor(db, sessionId, realSurvivor, duplicate);
173064
173340
  deletedTagNumbers.push(duplicate.tagNumber);
173065
173341
  }
173066
- const result = db.prepare(`UPDATE tags
173067
- SET message_id = ?
173068
- WHERE session_id = ?
173069
- AND tag_number = ?
173070
- AND type = 'message'
173071
- AND message_id = ?`).run(newRealMessageId, sessionId, tagNumber, oldFallbackMessageId);
173072
- return (result.changes ?? 0) === 1 ? { action: "folded", tagNumber, deletedTagNumbers } : { action: "skipped" };
173342
+ return {
173343
+ action: "folded",
173344
+ tagNumber: realSurvivor.tagNumber,
173345
+ deletedTagNumbers
173346
+ };
173073
173347
  }
173074
173348
  var getOwnerScopedToolTagNumbersStatements = new WeakMap;
173075
173349
  function getMaxTagNumberBySession(db, sessionId) {
@@ -173141,10 +173415,40 @@ function getActiveTagsBySessionStatement(db) {
173141
173415
  }
173142
173416
  return stmt;
173143
173417
  }
173418
+ function getMaxDroppedTagNumberStatement(db) {
173419
+ let stmt = getMaxDroppedTagNumberStatements.get(db);
173420
+ if (!stmt) {
173421
+ stmt = db.prepare("SELECT COALESCE(MAX(tag_number), 0) AS max_tag_number FROM tags WHERE session_id = ? AND status = 'dropped'");
173422
+ getMaxDroppedTagNumberStatements.set(db, stmt);
173423
+ }
173424
+ return stmt;
173425
+ }
173144
173426
  function getActiveTagsBySession(db, sessionId) {
173145
173427
  const rows = getActiveTagsBySessionStatement(db).all(sessionId).filter(isTagRow);
173146
173428
  return rows.map(toTagEntry);
173147
173429
  }
173430
+ function getTagsForPendingOperations(db, sessionId, pendingTagNumbers, protectedTags, recentToolWindow) {
173431
+ const byNumber = new Map;
173432
+ for (const tag of getTagsByNumbers(db, sessionId, pendingTagNumbers)) {
173433
+ byNumber.set(tag.tagNumber, tag);
173434
+ }
173435
+ const addRows = (sql, limit) => {
173436
+ if (limit <= 0)
173437
+ return;
173438
+ const rows = db.prepare(sql).all(sessionId, limit).filter(isTagRow);
173439
+ for (const row of rows) {
173440
+ const tag = toTagEntry(row);
173441
+ byNumber.set(tag.tagNumber, tag);
173442
+ }
173443
+ };
173444
+ addRows(`SELECT ${TAG_SELECT_COLUMNS} FROM tags
173445
+ WHERE session_id = ? AND status = 'active'
173446
+ ORDER BY tag_number DESC, id DESC LIMIT ?`, protectedTags);
173447
+ addRows(`SELECT ${TAG_SELECT_COLUMNS} FROM tags
173448
+ WHERE session_id = ? AND type = 'tool'
173449
+ ORDER BY tag_number DESC, id DESC LIMIT ?`, recentToolWindow);
173450
+ return [...byNumber.values()].sort((left, right) => left.tagNumber - right.tagNumber);
173451
+ }
173148
173452
  function getTagsByNumbers(db, sessionId, tagNumbers) {
173149
173453
  if (tagNumbers.length === 0)
173150
173454
  return [];
@@ -173159,6 +173463,10 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
173159
173463
  const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders4}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
173160
173464
  return rows.map(toTagEntry);
173161
173465
  }
173466
+ function getMaxDroppedTagNumber(db, sessionId) {
173467
+ const row = getMaxDroppedTagNumberStatement(db).get(sessionId);
173468
+ return isMaxTagNumberRow(row) ? row.max_tag_number : 0;
173469
+ }
173162
173470
  var getToolTagNumberByOwnerStatements = new WeakMap;
173163
173471
  var getNullOwnerToolTagStatements = new WeakMap;
173164
173472
  var adoptNullOwnerToolTagStatements = new WeakMap;
@@ -175517,12 +175825,26 @@ function computeBudgetPressure(compartments, historyBudget) {
175517
175825
 
175518
175826
  // ../plugin/src/hooks/magic-context/decay-render.ts
175519
175827
  var DEFAULT_HISTORY_BUDGET_TOKENS = 60000;
175520
- function escapeXmlAttr2(s) {
175521
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
175522
- }
175523
175828
  function escapeXmlContent2(s) {
175524
175829
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
175525
175830
  }
175831
+ function formatDateRange(startDate, endDate) {
175832
+ if (!startDate || !endDate)
175833
+ return "";
175834
+ if (startDate === endDate)
175835
+ return startDate;
175836
+ if (startDate.slice(0, 7) === endDate.slice(0, 7))
175837
+ return `${startDate}→${endDate.slice(8)}`;
175838
+ return `${startDate}→${endDate}`;
175839
+ }
175840
+ function compartmentHeading(c) {
175841
+ const dateRange = formatDateRange(c.startDate, c.endDate);
175842
+ const dateSegment = dateRange ? ` · ${dateRange}` : "";
175843
+ return `## ${c.startMessage}-${c.endMessage}${dateSegment} · ${c.title}`;
175844
+ }
175845
+ function guardCompartmentBody(body) {
175846
+ return body.replace(/^## /gm, " ## ");
175847
+ }
175526
175848
  function isTieredRow(c) {
175527
175849
  return typeof c.p1 === "string" && c.p1.length > 0;
175528
175850
  }
@@ -175552,25 +175874,22 @@ function renderCompartmentAtTier(c, tier2) {
175552
175874
  return renderOneCompartment(c, tier2);
175553
175875
  }
175554
175876
  function renderOneCompartment(c, tier2) {
175555
- const baseAttrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr2(c.title)}"`;
175556
175877
  if (tier2 >= 5)
175557
175878
  return "";
175879
+ const heading = compartmentHeading(c);
175558
175880
  if (c.legacy === 1 || !isTieredRow(c)) {
175559
175881
  const flat = (c.content ?? "").trim();
175560
175882
  if (tier2 >= 4 || flat.length === 0)
175561
- return `<compartment ${baseAttrs} />`;
175562
- return [
175563
- `<compartment ${baseAttrs}>`,
175564
- escapeXmlContent2(legacyBodyForTier(flat, tier2)),
175565
- "</compartment>"
175566
- ].join(`
175567
- `);
175883
+ return heading;
175884
+ const body2 = guardCompartmentBody(escapeXmlContent2(legacyBodyForTier(flat, tier2)));
175885
+ return `${heading}
175886
+ ${body2}`;
175568
175887
  }
175569
175888
  const body = tierBody(c, tier2);
175570
175889
  if (body.length === 0)
175571
- return `<compartment ${baseAttrs} />`;
175572
- return [`<compartment ${baseAttrs}>`, escapeXmlContent2(body), "</compartment>"].join(`
175573
- `);
175890
+ return heading;
175891
+ return `${heading}
175892
+ ${guardCompartmentBody(escapeXmlContent2(body))}`;
175574
175893
  }
175575
175894
  function computeTiers(compartments, historyBudgetTokens) {
175576
175895
  const v2Compartments = compartments.map((c, originalIndex) => ({ c, originalIndex })).filter(({ c }) => c.legacy !== 1);
@@ -175693,33 +176012,9 @@ function getVisibleMemoryIds(db, sessionId) {
175693
176012
  }
175694
176013
  }
175695
176014
  function renderMemoryBlock(memories) {
175696
- const byCategory = new Map;
175697
- for (const m of memories) {
175698
- const existing = byCategory.get(m.category);
175699
- if (existing) {
175700
- existing.push(m);
175701
- } else {
175702
- byCategory.set(m.category, [m]);
175703
- }
175704
- }
175705
- const sections = [];
175706
- for (const category of CATEGORY_PRIORITY) {
175707
- const categoryMemories = byCategory.get(category);
175708
- if (!categoryMemories || categoryMemories.length === 0) {
175709
- continue;
175710
- }
175711
- sections.push(`<${category}>`, ...categoryMemories.map((m) => `- ${escapeXmlContent(m.content)}`), `</${category}>`);
175712
- }
175713
- if (sections.length === 0) {
175714
- return null;
175715
- }
175716
- return `<project-memory>
175717
- ${sections.join(`
175718
- `)}
175719
- </project-memory>`;
176015
+ return renderMemoryBlockV2(memories) || null;
175720
176016
  }
175721
176017
  var DEFAULT_MEMORY_BUDGET_TOKENS = 8000;
175722
- var MEMORY_BLOCK_WRAPPER_TOKENS = 6;
175723
176018
  var DEFAULT_USER_PROFILE_BUDGET_TOKENS = 4000;
175724
176019
  function memoryCanonicalIdentity(memory2, workspace) {
175725
176020
  return resolveStoredPathWorkspaceIdentity(memory2.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
@@ -175737,11 +176032,18 @@ function memorySelectionOrder(left, right) {
175737
176032
  return left.id - right.id;
175738
176033
  }
175739
176034
  function memoryRenderOrder(left, right) {
175740
- const aPriority = MEMORY_CATEGORY_ORDER_PRIORITY[left.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
175741
- const bPriority = MEMORY_CATEGORY_ORDER_PRIORITY[right.category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
175742
- const categoryDiff = aPriority - bPriority;
175743
- if (categoryDiff !== 0)
175744
- return categoryDiff;
176035
+ const leftPriority = V2_MEMORY_CATEGORIES.indexOf(left.category);
176036
+ const rightPriority = V2_MEMORY_CATEGORIES.indexOf(right.category);
176037
+ if (leftPriority >= 0 || rightPriority >= 0) {
176038
+ if (leftPriority < 0)
176039
+ return 1;
176040
+ if (rightPriority < 0)
176041
+ return -1;
176042
+ if (leftPriority !== rightPriority)
176043
+ return leftPriority - rightPriority;
176044
+ } else if (left.category !== right.category) {
176045
+ return left.category < right.category ? -1 : 1;
176046
+ }
175745
176047
  return left.id - right.id;
175746
176048
  }
175747
176049
  var maxCompartmentSeqStatements = new WeakMap;
@@ -175752,13 +176054,13 @@ var newCompartmentStatements = new WeakMap;
175752
176054
  function trimMemoriesToBudgetV2(sessionId, memories, budgetTokens, renderOptions = {}) {
175753
176055
  const selectionOrder = [...memories].sort(memorySelectionOrder);
175754
176056
  const selected = [];
175755
- let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
176057
+ const accounting = createMemoryBlockAccounting(renderOptions);
175756
176058
  for (const memory2 of selectionOrder) {
175757
- const memoryTokens = estimateTokens(renderMemoryLineV2(memory2, renderOptions.sourceNameByMemoryId?.get(memory2.id)));
175758
- if (usedTokens + memoryTokens > budgetTokens)
176059
+ const cost = accounting.candidateCost(memory2);
176060
+ if (accounting.usedTokens + cost > budgetTokens)
175759
176061
  continue;
176062
+ accounting.admit(memory2, cost);
175760
176063
  selected.push(memory2);
175761
- usedTokens += memoryTokens;
175762
176064
  }
175763
176065
  if (selected.length < memories.length) {
175764
176066
  sessionLog(sessionId, `v2 trimmed memories from ${memories.length} to ${selected.length} to fit injection budget of ${budgetTokens} tokens`);
@@ -175772,23 +176074,22 @@ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, work
175772
176074
  }
175773
176075
  const selected = [];
175774
176076
  const selectedIds = new Set;
175775
- let usedTokens = MEMORY_BLOCK_WRAPPER_TOKENS;
175776
- const tokenCost = (memory2) => estimateTokens(renderMemoryLineV2(memory2, renderOptions.sourceNameByMemoryId?.get(memory2.id)));
176077
+ const accounting = createMemoryBlockAccounting(renderOptions);
175777
176078
  const trySelect = (memory2) => {
175778
176079
  if (selectedIds.has(memory2.id))
175779
176080
  return false;
175780
- const tokens = tokenCost(memory2);
175781
- if (usedTokens + tokens > budgetTokens)
176081
+ const cost = accounting.candidateCost(memory2);
176082
+ if (accounting.usedTokens + cost > budgetTokens)
175782
176083
  return false;
175783
176084
  selected.push(memory2);
175784
176085
  selectedIds.add(memory2.id);
175785
- usedTokens += tokens;
176086
+ accounting.admit(memory2, cost);
175786
176087
  return true;
175787
176088
  };
175788
176089
  for (const memory2 of memories.filter((candidate) => candidate.status === "permanent").sort(memorySelectionOrder)) {
175789
176090
  trySelect(memory2);
175790
176091
  }
175791
- const remainingAfterPermanent = Math.max(0, budgetTokens - usedTokens);
176092
+ const remainingAfterPermanent = Math.max(0, budgetTokens - accounting.usedTokens);
175792
176093
  const floorTokens = remainingAfterPermanent / Math.max(1, workspace.identities.length);
175793
176094
  const byIdentity = new Map;
175794
176095
  for (const memory2 of memories) {
@@ -175807,15 +176108,15 @@ function trimWorkspaceMemoriesToBudgetV2(sessionId, memories, budgetTokens, work
175807
176108
  for (const memory2 of candidates2) {
175808
176109
  if (selectedIds.has(memory2.id))
175809
176110
  continue;
175810
- const tokens = tokenCost(memory2);
175811
- if (memberTokens + tokens > floorTokens)
176111
+ const cost = accounting.candidateCost(memory2);
176112
+ if (memberTokens + cost > floorTokens)
175812
176113
  continue;
175813
- if (usedTokens + tokens > budgetTokens)
176114
+ if (accounting.usedTokens + cost > budgetTokens)
175814
176115
  continue;
175815
176116
  selected.push(memory2);
175816
176117
  selectedIds.add(memory2.id);
175817
- usedTokens += tokens;
175818
- memberTokens += tokens;
176118
+ accounting.admit(memory2, cost);
176119
+ memberTokens += cost;
175819
176120
  }
175820
176121
  }
175821
176122
  const remaining = memories.filter((memory2) => !selectedIds.has(memory2.id)).sort(memorySelectionOrder);
@@ -175839,17 +176140,55 @@ function trimUserMemoriesToBudget(memories, budgetTokens) {
175839
176140
  }
175840
176141
  return selected;
175841
176142
  }
176143
+ function createMemoryBlockAccounting(renderOptions) {
176144
+ const seenCategories = new Set;
176145
+ const categoryCost = new Map;
176146
+ return {
176147
+ usedTokens: estimateTokens(`<project-memory>
176148
+ </project-memory>`),
176149
+ candidateCost(memory2) {
176150
+ const line = renderMemoryLineV2(memory2, renderOptions.sourceNameByMemoryId?.get(memory2.id));
176151
+ let cost = estimateTokens(`${line}
176152
+ `);
176153
+ if (!seenCategories.has(memory2.category)) {
176154
+ let tags = categoryCost.get(memory2.category);
176155
+ if (tags === undefined) {
176156
+ tags = estimateTokens(`<${escapeXmlAttr(memory2.category)}>
176157
+ </${escapeXmlAttr(memory2.category)}>
176158
+ `);
176159
+ categoryCost.set(memory2.category, tags);
176160
+ }
176161
+ cost += tags;
176162
+ }
176163
+ return cost;
176164
+ },
176165
+ admit(memory2, cost) {
176166
+ this.usedTokens += cost;
176167
+ seenCategories.add(memory2.category);
176168
+ }
176169
+ };
176170
+ }
175842
176171
  function renderMemoryLineV2(memory2, sourceName) {
175843
- const sourceAttr = sourceName ? ` source="${escapeXmlAttr(sourceName)}"` : "";
175844
- return ` <memory id="${memory2.id}" category="${escapeXmlAttr(memory2.category)}"${sourceAttr} importance="${memory2.importance ?? 50}">${escapeXmlContent(memory2.content)}</memory>`;
176172
+ const source = sourceName ? ` [${escapeXmlContent(sourceName)}]` : "";
176173
+ return `#${memory2.id}${source}: ${escapeXmlContent(memory2.content)}`;
175845
176174
  }
175846
176175
  function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions = {}) {
175847
176176
  if (memories.length === 0)
175848
176177
  return "";
176178
+ const ordered = [...memories].sort(memoryRenderOrder);
175849
176179
  const lines = [`<${wrapper}>`];
175850
- for (const memory2 of memories) {
176180
+ let openCategory;
176181
+ for (const memory2 of ordered) {
176182
+ if (memory2.category !== openCategory) {
176183
+ if (openCategory !== undefined)
176184
+ lines.push(`</${escapeXmlAttr(openCategory)}>`);
176185
+ openCategory = memory2.category;
176186
+ lines.push(`<${escapeXmlAttr(openCategory)}>`);
176187
+ }
175851
176188
  lines.push(renderMemoryLineV2(memory2, renderOptions.sourceNameByMemoryId?.get(memory2.id)));
175852
176189
  }
176190
+ if (openCategory !== undefined)
176191
+ lines.push(`</${escapeXmlAttr(openCategory)}>`);
175853
176192
  lines.push(`</${wrapper}>`);
175854
176193
  return lines.join(`
175855
176194
  `);
@@ -176163,6 +176502,8 @@ var STATUS_COLOR = {
176163
176502
  cancelled: "error"
176164
176503
  };
176165
176504
  var snapshotsBySession = new Map;
176505
+ var MAX_TODOWRITE_RENDER_CACHE_ENTRIES = 50;
176506
+ var cachedTodowriteTodosByToolCallId = new Map;
176166
176507
  function isTodoStatus2(value) {
176167
176508
  return TODO_STATUSES.includes(value);
176168
176509
  }
@@ -176172,6 +176513,27 @@ function isTodoPriority2(value) {
176172
176513
  function cloneTodos(todos) {
176173
176514
  return todos.map((todo) => ({ ...todo }));
176174
176515
  }
176516
+ function rememberTodowriteToolCallTodos(toolCallId, todos) {
176517
+ if (!toolCallId)
176518
+ return;
176519
+ cachedTodowriteTodosByToolCallId.delete(toolCallId);
176520
+ cachedTodowriteTodosByToolCallId.set(toolCallId, cloneTodos(todos));
176521
+ while (cachedTodowriteTodosByToolCallId.size > MAX_TODOWRITE_RENDER_CACHE_ENTRIES) {
176522
+ const oldestKey = cachedTodowriteTodosByToolCallId.keys().next().value;
176523
+ if (typeof oldestKey !== "string")
176524
+ break;
176525
+ cachedTodowriteTodosByToolCallId.delete(oldestKey);
176526
+ }
176527
+ }
176528
+ function getCachedTodowriteToolCallTodos(toolCallId) {
176529
+ if (!toolCallId)
176530
+ return null;
176531
+ const todos = cachedTodowriteTodosByToolCallId.get(toolCallId);
176532
+ return todos ? cloneTodos(todos) : null;
176533
+ }
176534
+ function clearTodowriteToolCallTodos() {
176535
+ cachedTodowriteTodosByToolCallId.clear();
176536
+ }
176175
176537
  function parseTodos(input) {
176176
176538
  if (!Array.isArray(input))
176177
176539
  return null;
@@ -176308,25 +176670,27 @@ function capTodoRows(rows) {
176308
176670
  hiddenCount: rows.length - visibleCount
176309
176671
  };
176310
176672
  }
176311
- function renderTodowriteCall(args, theme) {
176312
- const todos = parseTodos(args.todos) ?? [];
176673
+ function renderTodowriteCall(args, theme, context) {
176674
+ const todos = getCachedTodowriteToolCallTodos(context?.toolCallId) ?? parseTodos(args.todos) ?? [];
176313
176675
  const active = activeTitleCount(todos);
176314
176676
  const activeColor = active > 0 ? "warning" : "success";
176315
176677
  const line = `${theme.fg("toolTitle", theme.bold("Todos"))} ${theme.fg("muted", "—")} ${theme.fg(activeColor, `${active} active`)}`;
176316
176678
  return lineComponent((width) => [truncateToWidth(line, width, "…")]);
176317
176679
  }
176318
- function renderTodowriteResult(result, theme) {
176680
+ function renderTodowriteResult(result, theme, context) {
176319
176681
  const detailsTodos = result.details?.todos;
176320
176682
  let todos = parseTodos(detailsTodos);
176321
- if (todos === null && Array.isArray(result.content)) {
176683
+ if ((todos === null || todos.length === 0) && Array.isArray(result.content)) {
176322
176684
  const text = result.content.find((part) => part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string")?.text;
176323
176685
  if (text) {
176324
176686
  try {
176325
- todos = parseTodos(JSON.parse(text));
176687
+ const parsed = parseTodos(JSON.parse(text));
176688
+ if (parsed !== null)
176689
+ todos = parsed;
176326
176690
  } catch {}
176327
176691
  }
176328
176692
  }
176329
- const renderedTodos = todos ?? [];
176693
+ const renderedTodos = todos !== null && todos.length > 0 ? todos : getCachedTodowriteToolCallTodos(context?.toolCallId) ?? todos ?? [];
176330
176694
  return lineComponent((width) => {
176331
176695
  if (renderedTodos.length === 0)
176332
176696
  return [theme.fg("dim", "No todos")];
@@ -176527,6 +176891,7 @@ function registerTodoOverlay(pi, deps) {
176527
176891
  overlay.hideCompletedTasksFromPreviousTurn();
176528
176892
  });
176529
176893
  pi.on("session_shutdown", async (_event, ctx) => {
176894
+ clearTodowriteToolCallTodos();
176530
176895
  const sessionId = getSessionId(ctx);
176531
176896
  if (!sessionId)
176532
176897
  return;
@@ -176534,6 +176899,7 @@ function registerTodoOverlay(pi, deps) {
176534
176899
  overlay.disposeSession(sessionId);
176535
176900
  });
176536
176901
  pi.on("session_before_switch", async (_event, ctx) => {
176902
+ clearTodowriteToolCallTodos();
176537
176903
  const sessionId = getSessionId(ctx);
176538
176904
  if (!sessionId)
176539
176905
  return;
@@ -176550,11 +176916,13 @@ function registerTodoStateLifecycle(pi, deps) {
176550
176916
  seedTodoSnapshotFromStateJson(sessionId, deps.readLastTodoState(sessionId));
176551
176917
  });
176552
176918
  pi.on("session_shutdown", async (_event, ctx) => {
176919
+ clearTodowriteToolCallTodos();
176553
176920
  const sessionId = getSessionId(ctx);
176554
176921
  if (sessionId)
176555
176922
  clearTodoSnapshot(sessionId);
176556
176923
  });
176557
176924
  pi.on("session_before_switch", async (_event, ctx) => {
176925
+ clearTodowriteToolCallTodos();
176558
176926
  const sessionId = getSessionId(ctx);
176559
176927
  if (sessionId)
176560
176928
  clearTodoSnapshot(sessionId);
@@ -176605,11 +176973,11 @@ function createTodowriteTool() {
176605
176973
  }
176606
176974
  };
176607
176975
  },
176608
- renderCall(args, theme) {
176609
- return renderTodowriteCall(args, theme);
176976
+ renderCall(args, theme, context) {
176977
+ return renderTodowriteCall(args, theme, context);
176610
176978
  },
176611
- renderResult(result, _opts, theme) {
176612
- return renderTodowriteResult(result, theme);
176979
+ renderResult(result, _opts, theme, context) {
176980
+ return renderTodowriteResult(result, theme, context);
176613
176981
  }
176614
176982
  };
176615
176983
  }