@cortexkit/opencode-magic-context 0.16.3 → 0.17.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.
Files changed (64) hide show
  1. package/dist/features/magic-context/message-index-async.d.ts +12 -0
  2. package/dist/features/magic-context/message-index-async.d.ts.map +1 -0
  3. package/dist/features/magic-context/message-index.d.ts +4 -0
  4. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  5. package/dist/features/magic-context/migrations.d.ts +7 -0
  6. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  7. package/dist/features/magic-context/search.d.ts +2 -2
  8. package/dist/features/magic-context/search.d.ts.map +1 -1
  9. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  10. package/dist/features/magic-context/storage-meta-persisted.d.ts +3 -6
  11. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  12. package/dist/features/magic-context/storage-tags.d.ts +163 -1
  13. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  14. package/dist/features/magic-context/storage.d.ts +1 -1
  15. package/dist/features/magic-context/storage.d.ts.map +1 -1
  16. package/dist/features/magic-context/tagger.d.ts +52 -2
  17. package/dist/features/magic-context/tagger.d.ts.map +1 -1
  18. package/dist/features/magic-context/tool-definition-tokens.d.ts +26 -3
  19. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  20. package/dist/features/magic-context/tool-owner-backfill.d.ts +90 -0
  21. package/dist/features/magic-context/tool-owner-backfill.d.ts.map +1 -0
  22. package/dist/features/magic-context/types.d.ts +17 -0
  23. package/dist/features/magic-context/types.d.ts.map +1 -1
  24. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/compartment-runner-drop-queue.d.ts +23 -0
  26. package/dist/hooks/magic-context/compartment-runner-drop-queue.d.ts.map +1 -1
  27. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  28. package/dist/hooks/magic-context/event-payloads.d.ts +8 -0
  29. package/dist/hooks/magic-context/event-payloads.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/inject-compartments.d.ts +16 -0
  34. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/nudger.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/read-session-chunk.d.ts +24 -1
  37. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/read-session-db.d.ts +1 -0
  39. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/read-session-raw.d.ts +1 -0
  41. package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/strip-content.d.ts +9 -6
  43. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/tag-messages.d.ts +1 -1
  45. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/tool-drop-target.d.ts +16 -1
  47. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +0 -11
  49. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +1731 -758
  53. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  54. package/dist/plugin/rpc-handlers.d.ts +3 -0
  55. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  56. package/dist/shared/models-dev-cache.d.ts +3 -10
  57. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  58. package/dist/shared/tag-transcript.d.ts.map +1 -1
  59. package/package.json +1 -1
  60. package/src/shared/models-dev-cache.test.ts +64 -57
  61. package/src/shared/models-dev-cache.ts +49 -68
  62. package/src/shared/tag-transcript.ts +137 -126
  63. package/dist/hooks/magic-context/reasoning-capability.d.ts +0 -23
  64. package/dist/hooks/magic-context/reasoning-capability.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -147149,6 +147149,74 @@ var init_read_session_formatting = __esm(() => {
147149
147149
  tokenizer = new src_default(exports_claude);
147150
147150
  });
147151
147151
 
147152
+ // src/features/magic-context/tool-definition-tokens.ts
147153
+ function keyFor(providerID, modelID, agentName) {
147154
+ const agent = agentName && agentName.length > 0 ? agentName : "default";
147155
+ return `${providerID}/${modelID}/${agent}`;
147156
+ }
147157
+ function setDatabase(db) {
147158
+ persistenceDb = db;
147159
+ }
147160
+ function loadToolDefinitionMeasurements(db) {
147161
+ let rows = [];
147162
+ try {
147163
+ rows = db.prepare("SELECT provider_id, model_id, agent_name, tool_id, token_count FROM tool_definition_measurements").all();
147164
+ } catch {
147165
+ return;
147166
+ }
147167
+ for (const row of rows) {
147168
+ const key = keyFor(row.provider_id, row.model_id, row.agent_name);
147169
+ let inner = measurements.get(key);
147170
+ if (!inner) {
147171
+ inner = new Map;
147172
+ measurements.set(key, inner);
147173
+ }
147174
+ inner.set(row.tool_id, row.token_count);
147175
+ }
147176
+ }
147177
+ function recordToolDefinition(providerID, modelID, agentName, toolID, description, parameters) {
147178
+ if (!providerID || !modelID || !toolID)
147179
+ return;
147180
+ const key = keyFor(providerID, modelID, agentName);
147181
+ let paramsText = "";
147182
+ try {
147183
+ paramsText = parameters === undefined ? "" : JSON.stringify(parameters);
147184
+ } catch {
147185
+ paramsText = "";
147186
+ }
147187
+ const tokens = estimateTokens(description ?? "") + estimateTokens(paramsText);
147188
+ let inner = measurements.get(key);
147189
+ if (!inner) {
147190
+ inner = new Map;
147191
+ measurements.set(key, inner);
147192
+ }
147193
+ inner.set(toolID, tokens);
147194
+ if (persistenceDb) {
147195
+ try {
147196
+ const agent = agentName && agentName.length > 0 ? agentName : "default";
147197
+ persistenceDb.prepare(`INSERT OR REPLACE INTO tool_definition_measurements
147198
+ (provider_id, model_id, agent_name, tool_id, token_count, recorded_at)
147199
+ VALUES (?, ?, ?, ?, ?, ?)`).run(providerID, modelID, agent, toolID, tokens, Date.now());
147200
+ } catch {}
147201
+ }
147202
+ }
147203
+ function getMeasuredToolDefinitionTokens(providerID, modelID, agentName) {
147204
+ if (!providerID || !modelID)
147205
+ return;
147206
+ const inner = measurements.get(keyFor(providerID, modelID, agentName));
147207
+ if (!inner || inner.size === 0)
147208
+ return;
147209
+ let total = 0;
147210
+ for (const tokens of inner.values())
147211
+ total += tokens;
147212
+ return total;
147213
+ }
147214
+ var measurements, persistenceDb = null;
147215
+ var init_tool_definition_tokens = __esm(() => {
147216
+ init_read_session_formatting();
147217
+ measurements = new Map;
147218
+ });
147219
+
147152
147220
  // ../../node_modules/.bun/esprima@4.0.1/node_modules/esprima/dist/esprima.js
147153
147221
  var require_esprima = __commonJS((exports, module) => {
147154
147222
  (function webpackUniversalModuleDefinition(root, factory) {
@@ -157228,7 +157296,8 @@ function findLastAssistantModelFromOpenCodeDb(sessionId) {
157228
157296
  try {
157229
157297
  return withReadOnlySessionDb((db) => {
157230
157298
  const row = db.prepare(`SELECT json_extract(data, '$.providerID') as providerID,
157231
- json_extract(data, '$.modelID') as modelID
157299
+ json_extract(data, '$.modelID') as modelID,
157300
+ json_extract(data, '$.agent') as agent
157232
157301
  FROM message
157233
157302
  WHERE session_id = ?
157234
157303
  AND json_extract(data, '$.role') = 'assistant'
@@ -157239,7 +157308,12 @@ function findLastAssistantModelFromOpenCodeDb(sessionId) {
157239
157308
  if (!row || typeof row.providerID !== "string" || typeof row.modelID !== "string") {
157240
157309
  return null;
157241
157310
  }
157242
- return { providerID: row.providerID, modelID: row.modelID };
157311
+ const agent = typeof row.agent === "string" && row.agent.length > 0 ? row.agent : undefined;
157312
+ return {
157313
+ providerID: row.providerID,
157314
+ modelID: row.modelID,
157315
+ ...agent ? { agent } : {}
157316
+ };
157243
157317
  });
157244
157318
  } catch (error48) {
157245
157319
  log("[magic-context] failed to recover live model from OpenCode DB:", error48);
@@ -158142,6 +158216,33 @@ function readRawSessionMessagesFromDb(db, sessionId) {
158142
158216
  };
158143
158217
  });
158144
158218
  }
158219
+ function readRawSessionMessageByIdFromDb(db, sessionId, messageId) {
158220
+ const row = db.prepare("SELECT id, data, time_created FROM message WHERE session_id = ? AND id = ?").get(sessionId, messageId);
158221
+ if (!row || !isRawMessageRow(row) || typeof row.time_created !== "number") {
158222
+ return null;
158223
+ }
158224
+ const info = parseJsonRecord(row.data);
158225
+ if (!info || info.summary === true && info.finish === "stop") {
158226
+ return null;
158227
+ }
158228
+ const ordinalRow = db.prepare(`SELECT COUNT(*) AS ordinal FROM message
158229
+ WHERE session_id = ?
158230
+ AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
158231
+ AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')
158232
+ AND (time_created < ? OR (time_created = ? AND id <= ?))`).get(sessionId, row.time_created, row.time_created, messageId);
158233
+ const ordinal = typeof ordinalRow?.ordinal === "number" ? ordinalRow.ordinal : 0;
158234
+ if (ordinal <= 0) {
158235
+ return null;
158236
+ }
158237
+ const partRows = db.prepare("SELECT message_id, data FROM part WHERE session_id = ? AND message_id = ? ORDER BY time_created ASC, id ASC").all(sessionId, messageId).filter(isRawPartRow);
158238
+ const role = typeof info.role === "string" ? info.role : "unknown";
158239
+ return {
158240
+ ordinal,
158241
+ id: row.id,
158242
+ role,
158243
+ parts: partRows.map((part) => parseJsonUnknown(part.data))
158244
+ };
158245
+ }
158145
158246
 
158146
158247
  // src/hooks/magic-context/tag-content-primitives.ts
158147
158248
  function byteSize(value) {
@@ -158201,127 +158302,380 @@ var init_tag_part_guards = __esm(() => {
158201
158302
  init_tag_content_primitives();
158202
158303
  });
158203
158304
 
158204
- // src/hooks/magic-context/read-session-chunk.ts
158205
- function cleanUserText(text) {
158206
- return removeSystemReminders(text).replace(OMO_INTERNAL_INITIATOR_MARKER, "").trim();
158305
+ // src/hooks/magic-context/tool-drop-target.ts
158306
+ function isToolCallId(value) {
158307
+ return typeof value === "string" && value.length > 0;
158207
158308
  }
158208
- function withRawSessionMessageCache(fn) {
158209
- const outerCache = activeRawMessageCache;
158210
- if (!outerCache) {
158211
- activeRawMessageCache = new Map;
158309
+ function getToolContent(part) {
158310
+ if (!isRecord(part))
158311
+ return;
158312
+ if (part.type === "tool" && isRecord(part.state)) {
158313
+ return typeof part.state.output === "string" ? part.state.output : undefined;
158212
158314
  }
158213
- try {
158214
- return fn();
158215
- } finally {
158216
- if (!outerCache) {
158217
- activeRawMessageCache = null;
158218
- }
158315
+ if (part.type === "tool_result") {
158316
+ return typeof part.content === "string" ? part.content : undefined;
158219
158317
  }
158318
+ return;
158220
158319
  }
158221
- function readRawSessionMessages(sessionId) {
158222
- if (activeRawMessageCache) {
158223
- const cached2 = activeRawMessageCache.get(sessionId);
158224
- if (cached2) {
158225
- return cached2;
158320
+ function setToolContent(part, content) {
158321
+ if (!isRecord(part))
158322
+ return;
158323
+ if (part.type === "tool" && isRecord(part.state)) {
158324
+ part.state.output = content;
158325
+ return;
158326
+ }
158327
+ if (part.type === "tool_result") {
158328
+ part.content = content;
158329
+ }
158330
+ }
158331
+ function truncateToolPart(part) {
158332
+ if (!isRecord(part))
158333
+ return;
158334
+ if (part.type === "tool" && isRecord(part.state)) {
158335
+ const state = part.state;
158336
+ state.output = "[truncated]";
158337
+ if (isRecord(state.input)) {
158338
+ const inputSize = estimateInputSize(state.input);
158339
+ if (inputSize > 500) {
158340
+ truncateInputValues(state.input);
158341
+ }
158342
+ }
158343
+ return;
158344
+ }
158345
+ if (part.type === "tool_result") {
158346
+ part.content = "[truncated]";
158347
+ return;
158348
+ }
158349
+ if (part.type === "tool-invocation" && isRecord(part.args)) {
158350
+ const inputSize = estimateInputSize(part.args);
158351
+ if (inputSize > 500) {
158352
+ truncateInputValues(part.args);
158353
+ }
158354
+ return;
158355
+ }
158356
+ if (part.type === "tool_use" && isRecord(part.input)) {
158357
+ const inputSize = estimateInputSize(part.input);
158358
+ if (inputSize > 500) {
158359
+ truncateInputValues(part.input);
158226
158360
  }
158227
- const messages = readRawSessionMessagesFromSource(sessionId);
158228
- activeRawMessageCache.set(sessionId, messages);
158229
- return messages;
158230
158361
  }
158231
- return readRawSessionMessagesFromSource(sessionId);
158232
158362
  }
158233
- function readRawSessionMessagesFromSource(sessionId) {
158234
- const provider2 = sessionProviders.get(sessionId);
158235
- if (provider2)
158236
- return provider2.readMessages();
158237
- return withReadOnlySessionDb((db) => readRawSessionMessagesFromDb(db, sessionId));
158363
+ function estimateInputSize(input) {
158364
+ try {
158365
+ return JSON.stringify(input).length;
158366
+ } catch {
158367
+ return 0;
158368
+ }
158238
158369
  }
158239
- function getRawSessionMessageCount(sessionId) {
158240
- const provider2 = sessionProviders.get(sessionId);
158241
- if (provider2) {
158242
- if (provider2.getMessageCount)
158243
- return provider2.getMessageCount();
158244
- return provider2.readMessages().length;
158370
+ function safeSlice(str, maxLen) {
158371
+ if (str.length <= maxLen)
158372
+ return str;
158373
+ const lastCharCode = str.charCodeAt(maxLen - 1);
158374
+ if (lastCharCode >= 55296 && lastCharCode <= 56319) {
158375
+ return str.slice(0, maxLen - 1);
158245
158376
  }
158246
- return withReadOnlySessionDb((db) => getRawSessionMessageCountFromDb(db, sessionId));
158377
+ return str.slice(0, maxLen);
158247
158378
  }
158248
- function getRawSessionTagKeysThrough(sessionId, upToMessageIndex) {
158249
- const messages = readRawSessionMessages(sessionId);
158250
- const keys = [];
158251
- for (const message of messages) {
158252
- if (message.ordinal > upToMessageIndex)
158253
- break;
158254
- for (const [partIndex, part] of message.parts.entries()) {
158255
- if (isTextPart(part)) {
158256
- keys.push(`${message.id}:p${partIndex}`);
158257
- }
158258
- if (isFilePart(part)) {
158259
- keys.push(`${message.id}:file${partIndex}`);
158260
- }
158261
- if (isToolPartWithOutput(part)) {
158262
- keys.push(part.callID);
158263
- }
158379
+ function truncateInputValues(input) {
158380
+ for (const key of Object.keys(input)) {
158381
+ const value = input[key];
158382
+ if (typeof value === "string") {
158383
+ if (value.endsWith(TRUNCATION_SENTINEL) || value === "[object]" || /^\[\d+ items\]$/.test(value))
158384
+ continue;
158385
+ input[key] = value.length > 5 ? `${safeSlice(value, 5)}${TRUNCATION_SENTINEL}` : value;
158386
+ } else if (Array.isArray(value)) {
158387
+ input[key] = `[${value.length} items]`;
158388
+ } else if (value !== null && typeof value === "object") {
158389
+ input[key] = "[object]";
158264
158390
  }
158265
158391
  }
158266
- return keys;
158267
158392
  }
158268
- function getProtectedTailStartOrdinal(sessionId) {
158269
- const messages = readRawSessionMessages(sessionId);
158270
- const userOrdinals = messages.filter((m) => m.role === "user" && hasMeaningfulUserText(m.parts)).map((m) => m.ordinal);
158271
- if (userOrdinals.length < PROTECTED_TAIL_USER_TURNS) {
158272
- return 1;
158393
+ function hasMeaningfulPart(part) {
158394
+ if (!isRecord(part))
158395
+ return false;
158396
+ const type = part.type;
158397
+ if (type === "text") {
158398
+ return typeof part.text === "string" && part.text.trim().length > 0;
158273
158399
  }
158274
- return userOrdinals[userOrdinals.length - PROTECTED_TAIL_USER_TURNS];
158400
+ if (typeof type !== "string")
158401
+ return false;
158402
+ if (IGNORE_PART_TYPES.has(type))
158403
+ return false;
158404
+ return true;
158275
158405
  }
158276
- function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal) {
158277
- const messages = readRawSessionMessages(sessionId);
158278
- const startOrdinal = Math.max(1, offset);
158279
- const lines = [];
158280
- const lineMeta = [];
158281
- const flushedToolOnlyBlocks = [];
158282
- let totalTokens = 0;
158283
- let messagesProcessed = 0;
158284
- let lastOrdinal = startOrdinal - 1;
158285
- let lastMessageId = "";
158286
- let firstMessageId = "";
158287
- let currentBlock = null;
158288
- let pendingNoiseMeta = [];
158289
- let commitClusters = 0;
158290
- let lastFlushedRole = "";
158291
- function flushCurrentBlock() {
158292
- if (!currentBlock)
158293
- return true;
158294
- const blockText = formatBlock(currentBlock);
158295
- const blockTokens = estimateTokens(blockText);
158296
- if (totalTokens + blockTokens > tokenBudget && totalTokens > 0) {
158297
- return false;
158298
- }
158299
- if (currentBlock.role === "A" && currentBlock.commitHashes.length > 0 && lastFlushedRole !== "A") {
158300
- commitClusters++;
158301
- }
158302
- lastFlushedRole = currentBlock.role;
158303
- if (!firstMessageId)
158304
- firstMessageId = currentBlock.meta[0]?.messageId ?? "";
158305
- lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
158306
- lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
158307
- messagesProcessed += currentBlock.meta.length;
158308
- lines.push(blockText);
158309
- lineMeta.push(...currentBlock.meta);
158310
- totalTokens += blockTokens;
158311
- if (currentBlock.isToolOnly) {
158312
- flushedToolOnlyBlocks.push({
158313
- start: currentBlock.startOrdinal,
158314
- end: currentBlock.endOrdinal
158315
- });
158316
- }
158317
- currentBlock = null;
158318
- return true;
158406
+ function clearThinkingParts(thinkingParts) {
158407
+ for (const part of thinkingParts) {
158408
+ if (part.thinking !== undefined)
158409
+ part.thinking = "[cleared]";
158410
+ if (part.text !== undefined)
158411
+ part.text = "[cleared]";
158319
158412
  }
158320
- for (const msg of messages) {
158321
- if (eligibleEndOrdinal !== undefined && msg.ordinal >= eligibleEndOrdinal)
158322
- break;
158323
- if (msg.ordinal < startOrdinal)
158324
- continue;
158413
+ }
158414
+ function extractToolCallObservation(part) {
158415
+ if (!isRecord(part))
158416
+ return null;
158417
+ if (part.type === "tool" && isToolCallId(part.callID)) {
158418
+ return { callId: part.callID, kind: "result" };
158419
+ }
158420
+ if (part.type === "tool-invocation" && isToolCallId(part.callID)) {
158421
+ return { callId: part.callID, kind: "invocation" };
158422
+ }
158423
+ if (part.type === "tool_use" && isToolCallId(part.id)) {
158424
+ return { callId: part.id, kind: "invocation" };
158425
+ }
158426
+ if (part.type === "tool_result" && isToolCallId(part.tool_use_id)) {
158427
+ return { callId: part.tool_use_id, kind: "result" };
158428
+ }
158429
+ return null;
158430
+ }
158431
+ function isDropContent(content) {
158432
+ return content.startsWith(DROP_PREFIX);
158433
+ }
158434
+
158435
+ class ToolMutationBatch {
158436
+ partsToRemove = new Set;
158437
+ affectedMessages = new Set;
158438
+ messages;
158439
+ constructor(messages) {
158440
+ this.messages = messages;
158441
+ }
158442
+ markForRemoval(occurrence) {
158443
+ this.partsToRemove.add(occurrence.part);
158444
+ this.affectedMessages.add(occurrence.message);
158445
+ }
158446
+ finalize() {
158447
+ if (this.partsToRemove.size === 0)
158448
+ return;
158449
+ for (const message of this.affectedMessages) {
158450
+ message.parts = message.parts.filter((p) => !this.partsToRemove.has(p));
158451
+ }
158452
+ for (let i = this.messages.length - 1;i >= 0; i -= 1) {
158453
+ if (!this.messages[i].parts.some(hasMeaningfulPart)) {
158454
+ this.messages.splice(i, 1);
158455
+ }
158456
+ }
158457
+ this.partsToRemove.clear();
158458
+ this.affectedMessages.clear();
158459
+ }
158460
+ }
158461
+ function createToolDropTarget(compositeKey, thinkingParts, index, batch) {
158462
+ const drop = () => {
158463
+ const entry = index.get(compositeKey);
158464
+ if (!entry || entry.occurrences.length === 0)
158465
+ return "absent";
158466
+ if (!entry.hasResult)
158467
+ return "incomplete";
158468
+ for (const occurrence of entry.occurrences) {
158469
+ batch.markForRemoval(occurrence);
158470
+ }
158471
+ clearThinkingParts(thinkingParts);
158472
+ index.delete(compositeKey);
158473
+ return "removed";
158474
+ };
158475
+ const truncate = () => {
158476
+ const entry = index.get(compositeKey);
158477
+ if (!entry || entry.occurrences.length === 0)
158478
+ return "absent";
158479
+ if (!entry.hasResult)
158480
+ return "incomplete";
158481
+ for (const occurrence of entry.occurrences) {
158482
+ truncateToolPart(occurrence.part);
158483
+ }
158484
+ clearThinkingParts(thinkingParts);
158485
+ return "truncated";
158486
+ };
158487
+ return {
158488
+ setContent: (content) => {
158489
+ if (isDropContent(content)) {
158490
+ drop();
158491
+ return true;
158492
+ }
158493
+ const entry = index.get(compositeKey);
158494
+ if (!entry)
158495
+ return false;
158496
+ let changed = false;
158497
+ for (const occurrence of entry.occurrences) {
158498
+ if (occurrence.kind !== "result")
158499
+ continue;
158500
+ const prevContent = getToolContent(occurrence.part);
158501
+ if (prevContent !== content) {
158502
+ setToolContent(occurrence.part, content);
158503
+ changed = true;
158504
+ }
158505
+ }
158506
+ return changed;
158507
+ },
158508
+ drop,
158509
+ truncate
158510
+ };
158511
+ }
158512
+ var DROP_PREFIX = "[dropped", IGNORE_PART_TYPES, TRUNCATION_SENTINEL = "...[truncated]";
158513
+ var init_tool_drop_target = __esm(() => {
158514
+ IGNORE_PART_TYPES = new Set([
158515
+ "thinking",
158516
+ "reasoning",
158517
+ "redacted_thinking",
158518
+ "meta",
158519
+ "step-start",
158520
+ "step-finish"
158521
+ ]);
158522
+ });
158523
+
158524
+ // src/hooks/magic-context/read-session-chunk.ts
158525
+ function cleanUserText(text) {
158526
+ return removeSystemReminders(text).replace(OMO_INTERNAL_INITIATOR_MARKER, "").trim();
158527
+ }
158528
+ function withRawSessionMessageCache(fn) {
158529
+ const outerCache = activeRawMessageCache;
158530
+ if (!outerCache) {
158531
+ activeRawMessageCache = new Map;
158532
+ }
158533
+ try {
158534
+ return fn();
158535
+ } finally {
158536
+ if (!outerCache) {
158537
+ activeRawMessageCache = null;
158538
+ }
158539
+ }
158540
+ }
158541
+ function readRawSessionMessages(sessionId) {
158542
+ if (activeRawMessageCache) {
158543
+ const cached2 = activeRawMessageCache.get(sessionId);
158544
+ if (cached2) {
158545
+ return cached2;
158546
+ }
158547
+ const messages = readRawSessionMessagesFromSource(sessionId);
158548
+ activeRawMessageCache.set(sessionId, messages);
158549
+ return messages;
158550
+ }
158551
+ return readRawSessionMessagesFromSource(sessionId);
158552
+ }
158553
+ function readRawSessionMessageById(sessionId, messageId) {
158554
+ const provider2 = sessionProviders.get(sessionId);
158555
+ if (provider2?.readMessageById) {
158556
+ return provider2.readMessageById(messageId);
158557
+ }
158558
+ if (provider2) {
158559
+ return provider2.readMessages().find((message) => message.id === messageId) ?? null;
158560
+ }
158561
+ return withReadOnlySessionDb((db) => readRawSessionMessageByIdFromDb(db, sessionId, messageId));
158562
+ }
158563
+ function readRawSessionMessagesFromSource(sessionId) {
158564
+ const provider2 = sessionProviders.get(sessionId);
158565
+ if (provider2)
158566
+ return provider2.readMessages();
158567
+ return withReadOnlySessionDb((db) => readRawSessionMessagesFromDb(db, sessionId));
158568
+ }
158569
+ function getRawSessionMessageCount(sessionId) {
158570
+ const provider2 = sessionProviders.get(sessionId);
158571
+ if (provider2) {
158572
+ if (provider2.getMessageCount)
158573
+ return provider2.getMessageCount();
158574
+ return provider2.readMessages().length;
158575
+ }
158576
+ return withReadOnlySessionDb((db) => getRawSessionMessageCountFromDb(db, sessionId));
158577
+ }
158578
+ function getRawSessionTagKeysThrough(sessionId, upToMessageIndex) {
158579
+ const messages = readRawSessionMessages(sessionId);
158580
+ const messageFileKeys = new Set;
158581
+ const toolObservations = new Map;
158582
+ const unpairedInvocations = new Map;
158583
+ for (const message of messages) {
158584
+ if (message.ordinal > upToMessageIndex)
158585
+ break;
158586
+ for (const [partIndex, part] of message.parts.entries()) {
158587
+ if (isTextPart(part)) {
158588
+ messageFileKeys.add(`${message.id}:p${partIndex}`);
158589
+ continue;
158590
+ }
158591
+ if (isFilePart(part)) {
158592
+ messageFileKeys.add(`${message.id}:file${partIndex}`);
158593
+ continue;
158594
+ }
158595
+ const obs = extractToolCallObservation(part);
158596
+ if (!obs)
158597
+ continue;
158598
+ let ownerMsgId;
158599
+ if (obs.kind === "invocation") {
158600
+ ownerMsgId = message.id;
158601
+ const queue2 = unpairedInvocations.get(obs.callId) ?? [];
158602
+ queue2.push(message.id);
158603
+ unpairedInvocations.set(obs.callId, queue2);
158604
+ } else {
158605
+ const queue2 = unpairedInvocations.get(obs.callId);
158606
+ if (queue2 && queue2.length > 0) {
158607
+ const popped = queue2.shift();
158608
+ if (queue2.length === 0)
158609
+ unpairedInvocations.delete(obs.callId);
158610
+ ownerMsgId = popped ?? message.id;
158611
+ } else {
158612
+ ownerMsgId = message.id;
158613
+ }
158614
+ }
158615
+ const owners = toolObservations.get(obs.callId) ?? new Set;
158616
+ owners.add(ownerMsgId);
158617
+ toolObservations.set(obs.callId, owners);
158618
+ }
158619
+ }
158620
+ return { messageFileKeys, toolObservations };
158621
+ }
158622
+ function getProtectedTailStartOrdinal(sessionId) {
158623
+ const messages = readRawSessionMessages(sessionId);
158624
+ const userOrdinals = messages.filter((m) => m.role === "user" && hasMeaningfulUserText(m.parts)).map((m) => m.ordinal);
158625
+ if (userOrdinals.length < PROTECTED_TAIL_USER_TURNS) {
158626
+ return 1;
158627
+ }
158628
+ return userOrdinals[userOrdinals.length - PROTECTED_TAIL_USER_TURNS];
158629
+ }
158630
+ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal) {
158631
+ const messages = readRawSessionMessages(sessionId);
158632
+ const startOrdinal = Math.max(1, offset);
158633
+ const lines = [];
158634
+ const lineMeta = [];
158635
+ const flushedToolOnlyBlocks = [];
158636
+ let totalTokens = 0;
158637
+ let messagesProcessed = 0;
158638
+ let lastOrdinal = startOrdinal - 1;
158639
+ let lastMessageId = "";
158640
+ let firstMessageId = "";
158641
+ let currentBlock = null;
158642
+ let pendingNoiseMeta = [];
158643
+ let commitClusters = 0;
158644
+ let lastFlushedRole = "";
158645
+ function flushCurrentBlock() {
158646
+ if (!currentBlock)
158647
+ return true;
158648
+ const blockText = formatBlock(currentBlock);
158649
+ const blockTokens = estimateTokens(blockText);
158650
+ if (totalTokens + blockTokens > tokenBudget && totalTokens > 0) {
158651
+ return false;
158652
+ }
158653
+ if (currentBlock.role === "A" && currentBlock.commitHashes.length > 0 && lastFlushedRole !== "A") {
158654
+ commitClusters++;
158655
+ }
158656
+ lastFlushedRole = currentBlock.role;
158657
+ if (!firstMessageId)
158658
+ firstMessageId = currentBlock.meta[0]?.messageId ?? "";
158659
+ lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
158660
+ lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
158661
+ messagesProcessed += currentBlock.meta.length;
158662
+ lines.push(blockText);
158663
+ lineMeta.push(...currentBlock.meta);
158664
+ totalTokens += blockTokens;
158665
+ if (currentBlock.isToolOnly) {
158666
+ flushedToolOnlyBlocks.push({
158667
+ start: currentBlock.startOrdinal,
158668
+ end: currentBlock.endOrdinal
158669
+ });
158670
+ }
158671
+ currentBlock = null;
158672
+ return true;
158673
+ }
158674
+ for (const msg of messages) {
158675
+ if (eligibleEndOrdinal !== undefined && msg.ordinal >= eligibleEndOrdinal)
158676
+ break;
158677
+ if (msg.ordinal < startOrdinal)
158678
+ continue;
158325
158679
  const meta3 = { ordinal: msg.ordinal, messageId: msg.id };
158326
158680
  if (msg.role === "user" && !hasMeaningfulUserText(msg.parts)) {
158327
158681
  const tcSummaries = extractToolCallSummaries(msg.parts);
@@ -158414,6 +158768,7 @@ var activeRawMessageCache = null, sessionProviders, PROTECTED_TAIL_USER_TURNS =
158414
158768
  var init_read_session_chunk = __esm(async () => {
158415
158769
  init_read_session_formatting();
158416
158770
  init_tag_part_guards();
158771
+ init_tool_drop_target();
158417
158772
  init_read_session_formatting();
158418
158773
  await init_read_session_db();
158419
158774
  sessionProviders = new Map;
@@ -158471,10 +158826,26 @@ function getCountIndexedMessageStatement(db) {
158471
158826
  }
158472
158827
  return stmt;
158473
158828
  }
158829
+ function getIndexedMessageIdStatement(db) {
158830
+ let stmt = indexedMessageIdStatements.get(db);
158831
+ if (!stmt) {
158832
+ stmt = db.prepare("SELECT message_id AS messageId FROM message_history_fts WHERE session_id = ?");
158833
+ indexedMessageIdStatements.set(db, stmt);
158834
+ }
158835
+ return stmt;
158836
+ }
158474
158837
  function getLastIndexedOrdinal(db, sessionId) {
158475
158838
  const row = getLastIndexedStatement(db).get(sessionId);
158476
158839
  return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
158477
158840
  }
158841
+ function isMessageAlreadyIndexed(db, sessionId, messageId) {
158842
+ const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
158843
+ return (typeof row?.count === "number" ? row.count : 0) > 0;
158844
+ }
158845
+ function advanceIndexWatermark(db, sessionId, ordinal, now) {
158846
+ const current = getLastIndexedOrdinal(db, sessionId);
158847
+ getUpsertIndexStatement(db).run(sessionId, Math.max(current, ordinal), now, getHarness());
158848
+ }
158478
158849
  function deleteIndexedMessage(db, sessionId, messageId) {
158479
158850
  const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
158480
158851
  const count = typeof row?.count === "number" ? row.count : 0;
@@ -158500,36 +158871,53 @@ function getIndexableContent(role, parts) {
158500
158871
  }
158501
158872
  return "";
158502
158873
  }
158503
- function ensureMessagesIndexed(db, sessionId, readMessages) {
158504
- const messages = readMessages(sessionId);
158505
- if (messages.length === 0) {
158506
- db.transaction(() => clearIndexedMessages(db, sessionId))();
158507
- return;
158874
+ function indexSingleMessageInTransaction(db, sessionId, message, now) {
158875
+ if (message.role !== "user" && message.role !== "assistant") {
158876
+ advanceIndexWatermark(db, sessionId, message.ordinal, now);
158877
+ return false;
158508
158878
  }
158509
- let lastIndexedOrdinal = getLastIndexedOrdinal(db, sessionId);
158510
- if (lastIndexedOrdinal > messages.length) {
158511
- db.transaction(() => clearIndexedMessages(db, sessionId))();
158512
- lastIndexedOrdinal = 0;
158879
+ const content = getIndexableContent(message.role, message.parts);
158880
+ if (content.length === 0) {
158881
+ advanceIndexWatermark(db, sessionId, message.ordinal, now);
158882
+ return false;
158513
158883
  }
158514
- if (lastIndexedOrdinal >= messages.length) {
158515
- return;
158884
+ if (isMessageAlreadyIndexed(db, sessionId, message.id)) {
158885
+ advanceIndexWatermark(db, sessionId, message.ordinal, now);
158886
+ return false;
158516
158887
  }
158517
- const messagesToInsert = messages.filter((message) => message.ordinal > lastIndexedOrdinal).filter((message) => message.role === "user" || message.role === "assistant").map((message) => ({
158518
- ordinal: message.ordinal,
158519
- id: message.id,
158520
- role: message.role,
158521
- content: getIndexableContent(message.role, message.parts)
158522
- })).filter((message) => message.content.length > 0);
158888
+ getInsertMessageStatement(db).run(sessionId, message.ordinal, message.id, message.role, content);
158889
+ advanceIndexWatermark(db, sessionId, message.ordinal, now);
158890
+ return true;
158891
+ }
158892
+ function indexSingleMessage(db, sessionId, message) {
158893
+ return db.transaction(() => indexSingleMessageInTransaction(db, sessionId, message, Date.now()))();
158894
+ }
158895
+ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal, finalWatermark = messages.length) {
158523
158896
  const now = Date.now();
158897
+ let inserted = 0;
158524
158898
  db.transaction(() => {
158899
+ const existingMessageIds = new Set(getIndexedMessageIdStatement(db).all(sessionId).map((row) => row.messageId).filter((messageId) => typeof messageId === "string"));
158525
158900
  const insertMessage = getInsertMessageStatement(db);
158526
- for (const message of messagesToInsert) {
158527
- insertMessage.run(sessionId, message.ordinal, message.id, message.role, message.content);
158901
+ for (const message of messages) {
158902
+ if (message.ordinal <= lastIndexedOrdinal) {
158903
+ continue;
158904
+ }
158905
+ if (message.role !== "user" && message.role !== "assistant") {
158906
+ continue;
158907
+ }
158908
+ const content = getIndexableContent(message.role, message.parts);
158909
+ if (content.length === 0 || existingMessageIds.has(message.id)) {
158910
+ continue;
158911
+ }
158912
+ insertMessage.run(sessionId, message.ordinal, message.id, message.role, content);
158913
+ existingMessageIds.add(message.id);
158914
+ inserted++;
158528
158915
  }
158529
- getUpsertIndexStatement(db).run(sessionId, messages.length, now, getHarness());
158916
+ getUpsertIndexStatement(db).run(sessionId, finalWatermark, now, getHarness());
158530
158917
  })();
158918
+ return inserted;
158531
158919
  }
158532
- var lastIndexedStatements, insertMessageStatements, upsertIndexStatements, deleteFtsStatements, deleteIndexStatements, countIndexedMessageStatements;
158920
+ var lastIndexedStatements, insertMessageStatements, upsertIndexStatements, deleteFtsStatements, deleteIndexStatements, countIndexedMessageStatements, indexedMessageIdStatements;
158533
158921
  var init_message_index = __esm(async () => {
158534
158922
  init_compression_depth_storage();
158535
158923
  await init_read_session_chunk();
@@ -158539,6 +158927,7 @@ var init_message_index = __esm(async () => {
158539
158927
  deleteFtsStatements = new WeakMap;
158540
158928
  deleteIndexStatements = new WeakMap;
158541
158929
  countIndexedMessageStatements = new WeakMap;
158930
+ indexedMessageIdStatements = new WeakMap;
158542
158931
  });
158543
158932
 
158544
158933
  // src/features/magic-context/migrations.ts
@@ -158555,24 +158944,53 @@ function getCurrentVersion(db) {
158555
158944
  const row = db.prepare("SELECT MAX(version) as version FROM schema_migrations").get();
158556
158945
  return row?.version ?? 0;
158557
158946
  }
158947
+ function isSiblingMigrationConflict(error48, version2) {
158948
+ if (!(error48 instanceof Error))
158949
+ return false;
158950
+ const code = error48.code;
158951
+ if (code !== "SQLITE_CONSTRAINT_PRIMARYKEY" && code !== "SQLITE_CONSTRAINT_UNIQUE") {
158952
+ return false;
158953
+ }
158954
+ const msg = error48.message;
158955
+ if (!msg.includes("schema_migrations"))
158956
+ return false;
158957
+ if (!msg.toLowerCase().includes("version"))
158958
+ return false;
158959
+ return true;
158960
+ }
158558
158961
  function runMigrations(db) {
158559
158962
  ensureMigrationsTable(db);
158560
- const currentVersion = getCurrentVersion(db);
158561
- const pendingMigrations = MIGRATIONS.filter((m) => m.version > currentVersion);
158963
+ let currentVersion = getCurrentVersion(db);
158964
+ let pendingMigrations = MIGRATIONS.filter((m) => m.version > currentVersion);
158562
158965
  if (pendingMigrations.length === 0) {
158563
158966
  return;
158564
158967
  }
158565
158968
  log(`[migrations] current schema version: ${currentVersion}, applying ${pendingMigrations.length} migration(s)`);
158566
- for (const migration of pendingMigrations) {
158969
+ let migrationIndex = 0;
158970
+ while (migrationIndex < pendingMigrations.length) {
158971
+ const migration = pendingMigrations[migrationIndex];
158567
158972
  try {
158568
158973
  db.transaction(() => {
158569
158974
  migration.up(db);
158570
158975
  db.prepare("INSERT INTO schema_migrations (version, description, applied_at) VALUES (?, ?, ?)").run(migration.version, migration.description, Date.now());
158571
158976
  })();
158572
158977
  log(`[migrations] applied v${migration.version}: ${migration.description}`);
158978
+ migrationIndex += 1;
158573
158979
  } catch (error48) {
158574
- log(`[migrations] FAILED v${migration.version}: ${migration.description} — ${error48 instanceof Error ? error48.message : String(error48)}`);
158575
- throw new Error(`Migration v${migration.version} failed: ${error48 instanceof Error ? error48.message : String(error48)}. Database may need manual repair.`);
158980
+ if (isSiblingMigrationConflict(error48, migration.version)) {
158981
+ log(`[migrations] v${migration.version} already applied by sibling instance resuming with re-read version`);
158982
+ const reReadVersion = getCurrentVersion(db);
158983
+ if (reReadVersion <= currentVersion) {
158984
+ log(`[migrations] FAILED v${migration.version}: sibling-conflict shape but version not advanced (${reReadVersion} <= ${currentVersion}) — failing closed`);
158985
+ throw new Error(`Migration v${migration.version} failed: sibling conflict reported but version did not advance. Database may need manual repair.`);
158986
+ }
158987
+ currentVersion = reReadVersion;
158988
+ pendingMigrations = MIGRATIONS.filter((m) => m.version > currentVersion);
158989
+ migrationIndex = 0;
158990
+ continue;
158991
+ }
158992
+ log(`[migrations] FAILED v${migration.version}: ${migration.description} — ${error48 instanceof Error ? error48.message : String(error48)}`);
158993
+ throw new Error(`Migration v${migration.version} failed: ${error48 instanceof Error ? error48.message : String(error48)}. Database may need manual repair.`);
158576
158994
  }
158577
158995
  }
158578
158996
  log(`[migrations] schema version now: ${MIGRATIONS[MIGRATIONS.length - 1].version}`);
@@ -158782,30 +159200,330 @@ var init_migrations = __esm(async () => {
158782
159200
  db.exec("ALTER TABLE notes ADD COLUMN harness TEXT NOT NULL DEFAULT 'opencode'");
158783
159201
  }
158784
159202
  }
159203
+ },
159204
+ {
159205
+ version: 8,
159206
+ description: "Add partial indexes on tags(session_id, tag_number) for active and dropped",
159207
+ up: (db) => {
159208
+ db.exec(`
159209
+ CREATE INDEX IF NOT EXISTS idx_tags_active_session_tag_number
159210
+ ON tags(session_id, tag_number)
159211
+ WHERE status = 'active';
159212
+
159213
+ CREATE INDEX IF NOT EXISTS idx_tags_dropped_session_tag_number
159214
+ ON tags(session_id, tag_number)
159215
+ WHERE status = 'dropped';
159216
+ `);
159217
+ db.exec("ANALYZE tags;");
159218
+ }
159219
+ },
159220
+ {
159221
+ version: 9,
159222
+ description: "Persist tool_definition_measurements across plugin restarts",
159223
+ up: (db) => {
159224
+ db.exec(`
159225
+ CREATE TABLE IF NOT EXISTS tool_definition_measurements (
159226
+ provider_id TEXT NOT NULL,
159227
+ model_id TEXT NOT NULL,
159228
+ agent_name TEXT NOT NULL,
159229
+ tool_id TEXT NOT NULL,
159230
+ token_count INTEGER NOT NULL,
159231
+ recorded_at INTEGER NOT NULL,
159232
+ PRIMARY KEY (provider_id, model_id, agent_name, tool_id)
159233
+ );
159234
+ `);
159235
+ }
159236
+ },
159237
+ {
159238
+ version: 10,
159239
+ description: "Add tool_owner_message_id column to tags + composite identity indexes",
159240
+ up: (db) => {
159241
+ const cols = db.prepare("PRAGMA table_info(tags)").all();
159242
+ if (!cols.some((c) => c.name === "tool_owner_message_id")) {
159243
+ db.exec("ALTER TABLE tags ADD COLUMN tool_owner_message_id TEXT DEFAULT NULL");
159244
+ }
159245
+ db.exec(`
159246
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_tool_composite
159247
+ ON tags(session_id, message_id, tool_owner_message_id)
159248
+ WHERE type = 'tool' AND tool_owner_message_id IS NOT NULL;
159249
+
159250
+ CREATE INDEX IF NOT EXISTS idx_tags_tool_null_owner
159251
+ ON tags(session_id, message_id)
159252
+ WHERE type = 'tool' AND tool_owner_message_id IS NULL;
159253
+ `);
159254
+ }
158785
159255
  }
158786
159256
  ];
158787
159257
  });
158788
159258
 
158789
- // src/features/magic-context/storage-db.ts
158790
- import { copyFileSync, cpSync, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "node:fs";
159259
+ // src/features/magic-context/tool-owner-backfill.ts
159260
+ import { existsSync as existsSync9 } from "node:fs";
158791
159261
  import { join as join14 } from "node:path";
159262
+ function resolveOpencodeDbPath() {
159263
+ return join14(getDataDir(), "opencode", "opencode.db");
159264
+ }
159265
+ function ensureBackfillStateTable(db) {
159266
+ db.exec(`
159267
+ CREATE TABLE IF NOT EXISTS tool_owner_backfill_state (
159268
+ session_id TEXT PRIMARY KEY,
159269
+ status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'completed', 'skipped')),
159270
+ started_at INTEGER,
159271
+ lease_expires_at INTEGER,
159272
+ completed_at INTEGER,
159273
+ last_error TEXT
159274
+ );
159275
+ CREATE INDEX IF NOT EXISTS idx_tool_owner_backfill_state_status
159276
+ ON tool_owner_backfill_state(status);
159277
+ `);
159278
+ }
159279
+ function runToolOwnerBackfill(db) {
159280
+ const startedAt = performance.now();
159281
+ ensureBackfillStateTable(db);
159282
+ const result = {
159283
+ sessionsProcessed: 0,
159284
+ sessionsSkippedNoOcDb: 0,
159285
+ sessionsSkippedNoMatches: 0,
159286
+ sessionsCompleted: 0,
159287
+ sessionsBlockedByLease: 0,
159288
+ sessionsErrored: 0,
159289
+ rowsUpdated: 0,
159290
+ rowsLeftNull: 0,
159291
+ durationMs: 0
159292
+ };
159293
+ if (!isToolOwnerBackfillNeeded(db)) {
159294
+ result.durationMs = performance.now() - startedAt;
159295
+ return result;
159296
+ }
159297
+ const opencodeDbPath = resolveOpencodeDbPath();
159298
+ if (!existsSync9(opencodeDbPath)) {
159299
+ log(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
159300
+ markAllUnbackfilledSessionsSkipped(db);
159301
+ result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
159302
+ result.durationMs = performance.now() - startedAt;
159303
+ return result;
159304
+ }
159305
+ db.exec(`ATTACH '${opencodeDbPath}' AS oc_backfill`);
159306
+ try {
159307
+ backfillToolOwnersInChunks(db, result);
159308
+ } finally {
159309
+ try {
159310
+ db.exec("DETACH DATABASE oc_backfill");
159311
+ } catch (error48) {
159312
+ log(`[backfill] failed to detach oc_backfill database: ${error48 instanceof Error ? error48.message : String(error48)}`);
159313
+ }
159314
+ }
159315
+ result.durationMs = performance.now() - startedAt;
159316
+ log(`[backfill] sessions=${result.sessionsProcessed} completed=${result.sessionsCompleted} skipped_no_oc=${result.sessionsSkippedNoOcDb} skipped_no_matches=${result.sessionsSkippedNoMatches} blocked_by_lease=${result.sessionsBlockedByLease} errored=${result.sessionsErrored} rows_updated=${result.rowsUpdated} rows_left_null=${result.rowsLeftNull} duration_ms=${Math.round(result.durationMs)}`);
159317
+ return result;
159318
+ }
159319
+ function isToolOwnerBackfillNeeded(db) {
159320
+ ensureBackfillStateTable(db);
159321
+ const row = db.prepare(`SELECT 1 AS hit
159322
+ FROM tags
159323
+ WHERE type = 'tool' AND tool_owner_message_id IS NULL
159324
+ AND NOT EXISTS (
159325
+ SELECT 1 FROM tool_owner_backfill_state s
159326
+ WHERE s.session_id = tags.session_id
159327
+ AND s.status IN ('completed', 'skipped')
159328
+ )
159329
+ LIMIT 1`).get();
159330
+ return row !== null && row !== undefined;
159331
+ }
159332
+ function markAllUnbackfilledSessionsSkipped(db) {
159333
+ const now = Date.now();
159334
+ db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, started_at, completed_at, last_error)
159335
+ SELECT DISTINCT session_id, 'skipped', NULL, ?, NULL
159336
+ FROM tags
159337
+ WHERE type = 'tool' AND tool_owner_message_id IS NULL
159338
+ ON CONFLICT(session_id) DO UPDATE SET
159339
+ status = 'skipped',
159340
+ completed_at = excluded.completed_at,
159341
+ last_error = NULL
159342
+ WHERE tool_owner_backfill_state.status NOT IN ('completed', 'running')`).run(now);
159343
+ }
159344
+ function countSessionsByStatus(db, status) {
159345
+ const row = db.prepare("SELECT COUNT(*) AS c FROM tool_owner_backfill_state WHERE status = ?").get(status);
159346
+ return row.c;
159347
+ }
159348
+ function acquireSessionLease(db, sessionId, now) {
159349
+ const expiresAt = now + LEASE_DURATION_MS2;
159350
+ const result = db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, started_at, lease_expires_at)
159351
+ VALUES (?, 'running', ?, ?)
159352
+ ON CONFLICT(session_id) DO UPDATE SET
159353
+ status = 'running',
159354
+ started_at = excluded.started_at,
159355
+ lease_expires_at = excluded.lease_expires_at,
159356
+ last_error = NULL
159357
+ WHERE tool_owner_backfill_state.status IN ('pending', 'skipped')
159358
+ OR (tool_owner_backfill_state.status = 'running'
159359
+ AND tool_owner_backfill_state.lease_expires_at < ?)`).run(sessionId, now, expiresAt, now);
159360
+ return (result.changes ?? 0) === 1;
159361
+ }
159362
+ function renewSessionLease(db, sessionId, now) {
159363
+ const expiresAt = now + LEASE_DURATION_MS2;
159364
+ db.prepare(`UPDATE tool_owner_backfill_state
159365
+ SET lease_expires_at = ?
159366
+ WHERE session_id = ? AND status = 'running'`).run(expiresAt, sessionId);
159367
+ }
159368
+ function markSessionCompleted(db, sessionId, now) {
159369
+ db.prepare(`UPDATE tool_owner_backfill_state
159370
+ SET status = 'completed', completed_at = ?, lease_expires_at = NULL, last_error = NULL
159371
+ WHERE session_id = ?`).run(now, sessionId);
159372
+ }
159373
+ function markSessionSkipped(db, sessionId, now, reason) {
159374
+ db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, completed_at, last_error)
159375
+ VALUES (?, 'skipped', ?, ?)
159376
+ ON CONFLICT(session_id) DO UPDATE SET
159377
+ status = 'skipped',
159378
+ completed_at = excluded.completed_at,
159379
+ last_error = excluded.last_error,
159380
+ lease_expires_at = NULL`).run(sessionId, now, reason);
159381
+ }
159382
+ function markSessionErrored(db, sessionId, error48) {
159383
+ const message = error48 instanceof Error ? error48.message : String(error48);
159384
+ db.prepare(`UPDATE tool_owner_backfill_state
159385
+ SET last_error = ?, lease_expires_at = NULL
159386
+ WHERE session_id = ?`).run(message, sessionId);
159387
+ }
159388
+ function getSessionsNeedingBackfill(db) {
159389
+ const rows = db.prepare(`SELECT DISTINCT t.session_id
159390
+ FROM tags t
159391
+ LEFT JOIN tool_owner_backfill_state s ON s.session_id = t.session_id
159392
+ WHERE t.type = 'tool' AND t.tool_owner_message_id IS NULL
159393
+ AND (s.status IS NULL OR s.status NOT IN ('completed', 'skipped'))
159394
+ ORDER BY t.session_id ASC`).all();
159395
+ return rows.map((r) => r.session_id);
159396
+ }
159397
+ function buildSessionOwnerMap(db, sessionId) {
159398
+ const rows = db.prepare(`SELECT
159399
+ COALESCE(
159400
+ CASE WHEN json_extract(p.data, '$.type') = 'tool_use'
159401
+ THEN json_extract(p.data, '$.id')
159402
+ END,
159403
+ json_extract(p.data, '$.callID')
159404
+ ) AS callid,
159405
+ m.id AS owner_id,
159406
+ m.time_created AS owner_t_created,
159407
+ p.id AS part_id,
159408
+ p.time_created AS part_t_created
159409
+ FROM oc_backfill.message m
159410
+ INNER JOIN oc_backfill.part p ON p.message_id = m.id
159411
+ WHERE m.session_id = ?
159412
+ AND json_extract(m.data, '$.role') = 'assistant'
159413
+ AND (
159414
+ (json_extract(p.data, '$.type') IN ('tool', 'tool-invocation')
159415
+ AND json_extract(p.data, '$.callID') IS NOT NULL)
159416
+ OR (json_extract(p.data, '$.type') = 'tool_use'
159417
+ AND json_extract(p.data, '$.id') IS NOT NULL)
159418
+ )
159419
+ ORDER BY
159420
+ m.time_created ASC,
159421
+ m.id ASC,
159422
+ p.time_created ASC,
159423
+ p.id ASC`).all(sessionId);
159424
+ const oldestByCallId = new Map;
159425
+ for (const r of rows) {
159426
+ if (typeof r.callid !== "string" || r.callid.length === 0)
159427
+ continue;
159428
+ if (!oldestByCallId.has(r.callid)) {
159429
+ oldestByCallId.set(r.callid, r.owner_id);
159430
+ }
159431
+ }
159432
+ return oldestByCallId;
159433
+ }
159434
+ function applyOwnersForSession(db, sessionId, ownersByCallId) {
159435
+ if (ownersByCallId.size === 0) {
159436
+ const leftNull = db.prepare(`SELECT COUNT(*) AS c FROM tags
159437
+ WHERE session_id = ? AND type = 'tool'
159438
+ AND tool_owner_message_id IS NULL`).get(sessionId).c;
159439
+ return { rowsUpdated: 0, rowsLeftNull: leftNull };
159440
+ }
159441
+ const findOrphanStmt = db.prepare(`SELECT id FROM tags
159442
+ WHERE session_id = ? AND message_id = ? AND type = 'tool'
159443
+ AND tool_owner_message_id IS NULL
159444
+ ORDER BY tag_number ASC
159445
+ LIMIT 1`);
159446
+ const updateRowStmt = db.prepare(`UPDATE tags
159447
+ SET tool_owner_message_id = ?
159448
+ WHERE id = ? AND tool_owner_message_id IS NULL`);
159449
+ let rowsUpdated = 0;
159450
+ db.transaction(() => {
159451
+ for (const [callId, ownerId] of ownersByCallId) {
159452
+ const orphan = findOrphanStmt.get(sessionId, callId);
159453
+ if (!orphan)
159454
+ continue;
159455
+ const result = updateRowStmt.run(ownerId, orphan.id);
159456
+ rowsUpdated += result.changes ?? 0;
159457
+ }
159458
+ })();
159459
+ const rowsLeftNull = db.prepare(`SELECT COUNT(*) AS c FROM tags
159460
+ WHERE session_id = ? AND type = 'tool'
159461
+ AND tool_owner_message_id IS NULL`).get(sessionId).c;
159462
+ return { rowsUpdated, rowsLeftNull };
159463
+ }
159464
+ function backfillToolOwnersInChunks(db, result) {
159465
+ const sessionIds = getSessionsNeedingBackfill(db);
159466
+ let lastRenewedAt = Date.now();
159467
+ for (const sessionId of sessionIds) {
159468
+ const now = Date.now();
159469
+ result.sessionsProcessed += 1;
159470
+ const acquired = acquireSessionLease(db, sessionId, now);
159471
+ if (!acquired) {
159472
+ result.sessionsBlockedByLease += 1;
159473
+ continue;
159474
+ }
159475
+ try {
159476
+ const owners = buildSessionOwnerMap(db, sessionId);
159477
+ const { rowsUpdated, rowsLeftNull } = applyOwnersForSession(db, sessionId, owners);
159478
+ result.rowsUpdated += rowsUpdated;
159479
+ result.rowsLeftNull += rowsLeftNull;
159480
+ if (owners.size === 0) {
159481
+ markSessionSkipped(db, sessionId, Date.now(), "no_oc_matches");
159482
+ result.sessionsSkippedNoMatches += 1;
159483
+ } else {
159484
+ markSessionCompleted(db, sessionId, Date.now());
159485
+ result.sessionsCompleted += 1;
159486
+ }
159487
+ } catch (error48) {
159488
+ log(`[backfill] session=${sessionId} errored: ${error48 instanceof Error ? error48.message : String(error48)}`);
159489
+ markSessionErrored(db, sessionId, error48);
159490
+ result.sessionsErrored += 1;
159491
+ }
159492
+ const sinceRenew = Date.now() - lastRenewedAt;
159493
+ if (sinceRenew > LEASE_RENEWAL_MS) {
159494
+ renewSessionLease(db, sessionId, Date.now());
159495
+ lastRenewedAt = Date.now();
159496
+ }
159497
+ }
159498
+ }
159499
+ var LEASE_DURATION_MS2, LEASE_RENEWAL_MS;
159500
+ var init_tool_owner_backfill = __esm(() => {
159501
+ init_data_path();
159502
+ init_logger();
159503
+ LEASE_DURATION_MS2 = 5 * 60 * 1000;
159504
+ LEASE_RENEWAL_MS = 60 * 1000;
159505
+ });
159506
+
159507
+ // src/features/magic-context/storage-db.ts
159508
+ import { copyFileSync, cpSync, existsSync as existsSync10, mkdirSync as mkdirSync3 } from "node:fs";
159509
+ import { join as join15 } from "node:path";
158792
159510
  function resolveDatabasePath() {
158793
159511
  const dbDir = getMagicContextStorageDir();
158794
- return { dbDir, dbPath: join14(dbDir, "context.db") };
159512
+ return { dbDir, dbPath: join15(dbDir, "context.db") };
158795
159513
  }
158796
159514
  function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
158797
- if (existsSync9(targetDbPath))
159515
+ if (existsSync10(targetDbPath))
158798
159516
  return;
158799
159517
  const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
158800
- const legacyDbPath = join14(legacyDir, "context.db");
158801
- if (!existsSync9(legacyDbPath))
159518
+ const legacyDbPath = join15(legacyDir, "context.db");
159519
+ if (!existsSync10(legacyDbPath))
158802
159520
  return;
158803
159521
  log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
158804
159522
  mkdirSync3(targetDbDir, { recursive: true });
158805
159523
  for (const suffix of ["", "-wal", "-shm"]) {
158806
159524
  const src = `${legacyDbPath}${suffix}`;
158807
- const dst = join14(targetDbDir, `context.db${suffix}`);
158808
- if (existsSync9(src)) {
159525
+ const dst = join15(targetDbDir, `context.db${suffix}`);
159526
+ if (existsSync10(src)) {
158809
159527
  try {
158810
159528
  copyFileSync(src, dst);
158811
159529
  } catch (error48) {
@@ -158813,9 +159531,9 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
158813
159531
  }
158814
159532
  }
158815
159533
  }
158816
- const legacyModelsDir = join14(legacyDir, "models");
158817
- const targetModelsDir = join14(targetDbDir, "models");
158818
- if (existsSync9(legacyModelsDir) && !existsSync9(targetModelsDir)) {
159534
+ const legacyModelsDir = join15(legacyDir, "models");
159535
+ const targetModelsDir = join15(targetDbDir, "models");
159536
+ if (existsSync10(legacyModelsDir) && !existsSync10(targetModelsDir)) {
158819
159537
  try {
158820
159538
  cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
158821
159539
  } catch (error48) {
@@ -159101,6 +159819,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
159101
159819
  ensureColumn(db, "tags", "tool_name", "TEXT");
159102
159820
  ensureColumn(db, "tags", "input_byte_size", "INTEGER DEFAULT 0");
159103
159821
  ensureColumn(db, "tags", "caveman_depth", "INTEGER DEFAULT 0");
159822
+ ensureColumn(db, "tags", "tool_owner_message_id", "TEXT DEFAULT NULL");
159104
159823
  ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
159105
159824
  ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
159106
159825
  ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
@@ -159199,6 +159918,13 @@ function openDatabase() {
159199
159918
  const db = new Database(dbPath);
159200
159919
  initializeDatabase(db);
159201
159920
  runMigrations(db);
159921
+ try {
159922
+ runToolOwnerBackfill(db);
159923
+ } catch (error48) {
159924
+ log(`[magic-context] tool-owner backfill failed (continuing with lazy adoption fallback): ${getErrorMessage(error48)}`);
159925
+ }
159926
+ setDatabase(db);
159927
+ loadToolDefinitionMeasurements(db);
159202
159928
  databases.set(dbPath, db);
159203
159929
  persistenceByDatabase.set(db, true);
159204
159930
  persistenceErrorByDatabase.delete(db);
@@ -159219,6 +159945,8 @@ var databases, persistenceByDatabase, persistenceErrorByDatabase;
159219
159945
  var init_storage_db = __esm(async () => {
159220
159946
  init_data_path();
159221
159947
  init_logger();
159948
+ init_tool_definition_tokens();
159949
+ init_tool_owner_backfill();
159222
159950
  await __promiseAll([
159223
159951
  init_sqlite(),
159224
159952
  init_migrations()
@@ -159789,7 +160517,7 @@ var init_storage_source = () => {};
159789
160517
  function getInsertTagStatement(db) {
159790
160518
  let stmt = insertTagStatements.get(db);
159791
160519
  if (!stmt) {
159792
- stmt = db.prepare("INSERT INTO tags (session_id, message_id, type, byte_size, reasoning_byte_size, tag_number, tool_name, input_byte_size, harness) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
160520
+ stmt = db.prepare("INSERT INTO tags (session_id, message_id, type, byte_size, reasoning_byte_size, tag_number, tool_name, input_byte_size, harness, tool_owner_message_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
159793
160521
  insertTagStatements.set(db, stmt);
159794
160522
  }
159795
160523
  return stmt;
@@ -159870,7 +160598,8 @@ function toTagEntry(row) {
159870
160598
  byteSize: row.byte_size,
159871
160599
  reasoningByteSize: row.reasoning_byte_size ?? 0,
159872
160600
  sessionId: row.session_id,
159873
- cavemanDepth: typeof row.caveman_depth === "number" && Number.isFinite(row.caveman_depth) ? row.caveman_depth : 0
160601
+ cavemanDepth: typeof row.caveman_depth === "number" && Number.isFinite(row.caveman_depth) ? row.caveman_depth : 0,
160602
+ toolOwnerMessageId: typeof row.tool_owner_message_id === "string" ? row.tool_owner_message_id : null
159874
160603
  };
159875
160604
  }
159876
160605
  function isTagNumberRow(row) {
@@ -159888,8 +160617,8 @@ function isMaxTagNumberRow(row) {
159888
160617
  function escapeLikePattern(value) {
159889
160618
  return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
159890
160619
  }
159891
- function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber, reasoningByteSize = 0, toolName = null, inputByteSize = 0) {
159892
- getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, reasoningByteSize, tagNumber, toolName, inputByteSize, getHarness());
160620
+ function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber, reasoningByteSize = 0, toolName = null, inputByteSize = 0, toolOwnerMessageId = null) {
160621
+ getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, reasoningByteSize, tagNumber, toolName, inputByteSize, getHarness(), toolOwnerMessageId);
159893
160622
  return tagNumber;
159894
160623
  }
159895
160624
  function updateTagStatus(db, sessionId, tagId, status) {
@@ -159908,12 +160637,27 @@ function deleteTagsByMessageId(db, sessionId, messageId) {
159908
160637
  const escapedMessageId = escapeLikePattern(messageId);
159909
160638
  const textPartPattern = `${escapedMessageId}:p%`;
159910
160639
  const filePartPattern = `${escapedMessageId}:file%`;
159911
- const tagNumbers = getTagNumbersByMessageIdStatement(db).all(sessionId, messageId, textPartPattern, filePartPattern).filter(isTagNumberRow).map((row) => row.tag_number);
159912
- if (tagNumbers.length === 0) {
160640
+ const messageScopedTags = getTagNumbersByMessageIdStatement(db).all(sessionId, messageId, textPartPattern, filePartPattern).filter(isTagNumberRow).map((row) => row.tag_number);
160641
+ const ownerScopedTagNumbers = getOwnerScopedToolTagNumbers(db, sessionId, messageId);
160642
+ if (messageScopedTags.length === 0 && ownerScopedTagNumbers.length === 0) {
159913
160643
  return [];
159914
160644
  }
159915
- getDeleteTagsByMessageIdStatement(db).run(sessionId, messageId, textPartPattern, filePartPattern);
159916
- return tagNumbers;
160645
+ if (messageScopedTags.length > 0) {
160646
+ getDeleteTagsByMessageIdStatement(db).run(sessionId, messageId, textPartPattern, filePartPattern);
160647
+ }
160648
+ if (ownerScopedTagNumbers.length > 0) {
160649
+ deleteToolTagsByOwner(db, sessionId, messageId);
160650
+ }
160651
+ const merged = new Set([...messageScopedTags, ...ownerScopedTagNumbers]);
160652
+ return Array.from(merged).sort((a, b) => a - b);
160653
+ }
160654
+ function getOwnerScopedToolTagNumbers(db, sessionId, ownerMsgId) {
160655
+ let stmt = getOwnerScopedToolTagNumbersStatements.get(db);
160656
+ if (!stmt) {
160657
+ stmt = db.prepare("SELECT tag_number FROM tags WHERE session_id = ? AND type = 'tool' AND tool_owner_message_id = ? ORDER BY tag_number ASC");
160658
+ getOwnerScopedToolTagNumbersStatements.set(db, stmt);
160659
+ }
160660
+ return stmt.all(sessionId, ownerMsgId).filter(isTagNumberRow).map((row) => row.tag_number);
159917
160661
  }
159918
160662
  function getMaxTagNumberBySession(db, sessionId) {
159919
160663
  const row = getMaxTagNumberBySessionStatement(db).get(sessionId);
@@ -159924,17 +160668,151 @@ function getTagNumberByMessageId(db, sessionId, messageId) {
159924
160668
  return isTagNumberRow(row) ? row.tag_number : null;
159925
160669
  }
159926
160670
  function getTagsBySession(db, sessionId) {
159927
- const rows = db.prepare("SELECT id, message_id, type, status, drop_mode, tool_name, input_byte_size, byte_size, reasoning_byte_size, session_id, tag_number, caveman_depth FROM tags WHERE session_id = ? ORDER BY tag_number ASC, id ASC").all(sessionId).filter(isTagRow);
160671
+ const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? ORDER BY tag_number ASC, id ASC`).all(sessionId).filter(isTagRow);
160672
+ return rows.map(toTagEntry);
160673
+ }
160674
+ function getActiveTagsBySessionStatement(db) {
160675
+ let stmt = getActiveTagsBySessionStatements.get(db);
160676
+ if (!stmt) {
160677
+ stmt = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND status = 'active' ORDER BY tag_number ASC, id ASC`);
160678
+ getActiveTagsBySessionStatements.set(db, stmt);
160679
+ }
160680
+ return stmt;
160681
+ }
160682
+ function getMaxDroppedTagNumberStatement(db) {
160683
+ let stmt = getMaxDroppedTagNumberStatements.get(db);
160684
+ if (!stmt) {
160685
+ stmt = db.prepare("SELECT COALESCE(MAX(tag_number), 0) AS max_tag_number FROM tags WHERE session_id = ? AND status = 'dropped'");
160686
+ getMaxDroppedTagNumberStatements.set(db, stmt);
160687
+ }
160688
+ return stmt;
160689
+ }
160690
+ function getActiveTagsBySession(db, sessionId) {
160691
+ const rows = getActiveTagsBySessionStatement(db).all(sessionId).filter(isTagRow);
160692
+ return rows.map(toTagEntry);
160693
+ }
160694
+ function getTagsByNumbers(db, sessionId, tagNumbers) {
160695
+ if (tagNumbers.length === 0)
160696
+ return [];
160697
+ if (tagNumbers.length > 900) {
160698
+ const all = [];
160699
+ for (let i = 0;i < tagNumbers.length; i += 900) {
160700
+ all.push(...getTagsByNumbers(db, sessionId, tagNumbers.slice(i, i + 900)));
160701
+ }
160702
+ return all;
160703
+ }
160704
+ const placeholders = tagNumbers.map(() => "?").join(",");
160705
+ const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
159928
160706
  return rows.map(toTagEntry);
159929
160707
  }
160708
+ function getMaxDroppedTagNumber(db, sessionId) {
160709
+ const row = getMaxDroppedTagNumberStatement(db).get(sessionId);
160710
+ return isMaxTagNumberRow(row) ? row.max_tag_number : 0;
160711
+ }
159930
160712
  function getTopNBySize(db, sessionId, n) {
159931
160713
  if (n <= 0) {
159932
160714
  return [];
159933
160715
  }
159934
- const rows = db.prepare("SELECT id, message_id, type, status, drop_mode, tool_name, input_byte_size, byte_size, reasoning_byte_size, session_id, tag_number, caveman_depth FROM tags WHERE session_id = ? AND status = 'active' ORDER BY byte_size DESC, tag_number ASC LIMIT ?").all(sessionId, n).filter(isTagRow);
160716
+ const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND status = 'active' ORDER BY byte_size DESC, tag_number ASC LIMIT ?`).all(sessionId, n).filter(isTagRow);
159935
160717
  return rows.map(toTagEntry);
159936
160718
  }
159937
- var insertTagStatements, updateTagStatusStatements, updateTagDropModeStatements, updateTagMessageIdStatements, getTagNumbersByMessageIdStatements, deleteTagsByMessageIdStatements, getMaxTagNumberBySessionStatements, getTagNumberByMessageIdStatements, updateTagByteSizeStatements, updateTagInputByteSizeStatements;
160719
+ function getGetToolTagNumberByOwnerStatement(db) {
160720
+ let stmt = getToolTagNumberByOwnerStatements.get(db);
160721
+ if (!stmt) {
160722
+ stmt = db.prepare(`SELECT tag_number FROM tags
160723
+ WHERE session_id = ? AND message_id = ?
160724
+ AND type = 'tool' AND tool_owner_message_id = ?
160725
+ LIMIT 1`);
160726
+ getToolTagNumberByOwnerStatements.set(db, stmt);
160727
+ }
160728
+ return stmt;
160729
+ }
160730
+ function getToolTagNumberByOwner(db, sessionId, callId, ownerMsgId) {
160731
+ const row = getGetToolTagNumberByOwnerStatement(db).get(sessionId, callId, ownerMsgId);
160732
+ return isTagNumberRow(row) ? row.tag_number : null;
160733
+ }
160734
+ function isNullOwnerToolTagRow(row) {
160735
+ if (row === null || typeof row !== "object")
160736
+ return false;
160737
+ const r = row;
160738
+ return typeof r.id === "number" && typeof r.tag_number === "number";
160739
+ }
160740
+ function getGetNullOwnerToolTagStatement(db) {
160741
+ let stmt = getNullOwnerToolTagStatements.get(db);
160742
+ if (!stmt) {
160743
+ stmt = db.prepare(`SELECT id, tag_number FROM tags
160744
+ WHERE session_id = ? AND message_id = ?
160745
+ AND type = 'tool' AND tool_owner_message_id IS NULL
160746
+ ORDER BY tag_number ASC
160747
+ LIMIT 1`);
160748
+ getNullOwnerToolTagStatements.set(db, stmt);
160749
+ }
160750
+ return stmt;
160751
+ }
160752
+ function getNullOwnerToolTag(db, sessionId, callId) {
160753
+ const row = getGetNullOwnerToolTagStatement(db).get(sessionId, callId);
160754
+ if (!isNullOwnerToolTagRow(row))
160755
+ return null;
160756
+ return { id: row.id, tagNumber: row.tag_number };
160757
+ }
160758
+ function getAdoptNullOwnerToolTagStatement(db) {
160759
+ let stmt = adoptNullOwnerToolTagStatements.get(db);
160760
+ if (!stmt) {
160761
+ stmt = db.prepare(`UPDATE tags
160762
+ SET tool_owner_message_id = ?
160763
+ WHERE id = ? AND tool_owner_message_id IS NULL`);
160764
+ adoptNullOwnerToolTagStatements.set(db, stmt);
160765
+ }
160766
+ return stmt;
160767
+ }
160768
+ function adoptNullOwnerToolTag(db, rowId, ownerMsgId) {
160769
+ const result = getAdoptNullOwnerToolTagStatement(db).run(ownerMsgId, rowId);
160770
+ return (result.changes ?? 0) === 1;
160771
+ }
160772
+ function getCandidateToolOwners(db, sessionId, callId) {
160773
+ const rows = db.prepare(`SELECT DISTINCT tool_owner_message_id
160774
+ FROM tags
160775
+ WHERE session_id = ?
160776
+ AND message_id = ?
160777
+ AND type = 'tool'
160778
+ AND tool_owner_message_id IS NOT NULL`).all(sessionId, callId);
160779
+ return rows.map((r) => r.tool_owner_message_id);
160780
+ }
160781
+ function pickNearestPriorOwner(candidates, currentMessageId, times) {
160782
+ const currentTime = times.get(currentMessageId);
160783
+ if (typeof currentTime !== "number")
160784
+ return null;
160785
+ let best = null;
160786
+ for (const id of candidates) {
160787
+ const t = times.get(id);
160788
+ if (typeof t !== "number")
160789
+ continue;
160790
+ if (t > currentTime)
160791
+ continue;
160792
+ if (t === currentTime && id >= currentMessageId)
160793
+ continue;
160794
+ if (best === null || t > best.time || t === best.time && id > best.id) {
160795
+ best = { id, time: t };
160796
+ }
160797
+ }
160798
+ return best?.id ?? null;
160799
+ }
160800
+ function getDeleteToolTagsByOwnerStatement(db) {
160801
+ let stmt = deleteToolTagsByOwnerStatements.get(db);
160802
+ if (!stmt) {
160803
+ stmt = db.prepare(`DELETE FROM tags
160804
+ WHERE session_id = ?
160805
+ AND type = 'tool'
160806
+ AND tool_owner_message_id = ?`);
160807
+ deleteToolTagsByOwnerStatements.set(db, stmt);
160808
+ }
160809
+ return stmt;
160810
+ }
160811
+ function deleteToolTagsByOwner(db, sessionId, ownerMsgId) {
160812
+ const result = getDeleteToolTagsByOwnerStatement(db).run(sessionId, ownerMsgId);
160813
+ return result.changes ?? 0;
160814
+ }
160815
+ var insertTagStatements, updateTagStatusStatements, updateTagDropModeStatements, updateTagMessageIdStatements, getTagNumbersByMessageIdStatements, deleteTagsByMessageIdStatements, getMaxTagNumberBySessionStatements, getTagNumberByMessageIdStatements, updateTagByteSizeStatements, updateTagInputByteSizeStatements, getOwnerScopedToolTagNumbersStatements, TAG_SELECT_COLUMNS = "id, message_id, type, status, drop_mode, tool_name, input_byte_size, byte_size, reasoning_byte_size, session_id, tag_number, caveman_depth, tool_owner_message_id", getActiveTagsBySessionStatements, getMaxDroppedTagNumberStatements, getToolTagNumberByOwnerStatements, getNullOwnerToolTagStatements, adoptNullOwnerToolTagStatements, deleteToolTagsByOwnerStatements;
159938
160816
  var init_storage_tags = __esm(() => {
159939
160817
  insertTagStatements = new WeakMap;
159940
160818
  updateTagStatusStatements = new WeakMap;
@@ -159946,6 +160824,13 @@ var init_storage_tags = __esm(() => {
159946
160824
  getTagNumberByMessageIdStatements = new WeakMap;
159947
160825
  updateTagByteSizeStatements = new WeakMap;
159948
160826
  updateTagInputByteSizeStatements = new WeakMap;
160827
+ getOwnerScopedToolTagNumbersStatements = new WeakMap;
160828
+ getActiveTagsBySessionStatements = new WeakMap;
160829
+ getMaxDroppedTagNumberStatements = new WeakMap;
160830
+ getToolTagNumberByOwnerStatements = new WeakMap;
160831
+ getNullOwnerToolTagStatements = new WeakMap;
160832
+ adoptNullOwnerToolTagStatements = new WeakMap;
160833
+ deleteToolTagsByOwnerStatements = new WeakMap;
159949
160834
  });
159950
160835
 
159951
160836
  // src/features/magic-context/storage.ts
@@ -159965,9 +160850,9 @@ var init_storage = __esm(async () => {
159965
160850
 
159966
160851
  // src/shared/models-dev-cache.ts
159967
160852
  import { createHash as createHash3 } from "node:crypto";
159968
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
160853
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
159969
160854
  import { homedir as homedir8, platform as platform3 } from "node:os";
159970
- import { join as join15 } from "node:path";
160855
+ import { join as join16 } from "node:path";
159971
160856
  function hashFast(input) {
159972
160857
  return createHash3("sha1").update(input).digest("hex");
159973
160858
  }
@@ -159978,16 +160863,16 @@ function getModelsJsonPath() {
159978
160863
  const cacheBase = getCacheDir();
159979
160864
  const source = process.env.OPENCODE_MODELS_URL?.trim();
159980
160865
  const filename = source && source !== "https://models.dev" ? `models-${hashFast(source)}.json` : "models.json";
159981
- return join15(cacheBase, "opencode", filename);
160866
+ return join16(cacheBase, "opencode", filename);
159982
160867
  }
159983
160868
  function getOpencodeConfigPath() {
159984
160869
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
159985
- const configDir = envDir ? envDir : platform3() === "win32" ? join15(homedir8(), ".config", "opencode") : join15(process.env.XDG_CONFIG_HOME || join15(homedir8(), ".config"), "opencode");
159986
- const jsonc = join15(configDir, "opencode.jsonc");
159987
- if (existsSync10(jsonc))
160870
+ const configDir = envDir ? envDir : platform3() === "win32" ? join16(homedir8(), ".config", "opencode") : join16(process.env.XDG_CONFIG_HOME || join16(homedir8(), ".config"), "opencode");
160871
+ const jsonc = join16(configDir, "opencode.jsonc");
160872
+ if (existsSync11(jsonc))
159988
160873
  return jsonc;
159989
- const json2 = join15(configDir, "opencode.json");
159990
- if (existsSync10(json2))
160874
+ const json2 = join16(configDir, "opencode.json");
160875
+ if (existsSync11(json2))
159991
160876
  return json2;
159992
160877
  return null;
159993
160878
  }
@@ -160000,23 +160885,12 @@ function resolveLimit(limit) {
160000
160885
  return limit.context;
160001
160886
  return;
160002
160887
  }
160003
- function resolveInterleavedField(interleaved) {
160004
- if (interleaved && typeof interleaved === "object" && typeof interleaved.field === "string" && interleaved.field.length > 0) {
160005
- return interleaved.field;
160006
- }
160007
- return;
160008
- }
160009
160888
  function setCachedModelMetadata(cache, key, model) {
160010
160889
  const limit = resolveLimit(model?.limit);
160011
- const interleavedField = resolveInterleavedField(model?.capabilities?.interleaved) ?? resolveInterleavedField(model?.interleaved);
160012
- if (limit === undefined && interleavedField === undefined) {
160890
+ if (limit === undefined) {
160013
160891
  return;
160014
160892
  }
160015
- const value = {};
160016
- if (limit !== undefined)
160017
- value.limit = limit;
160018
- if (interleavedField !== undefined)
160019
- value.interleavedField = interleavedField;
160893
+ const value = { limit };
160020
160894
  cache.set(key, value);
160021
160895
  const modes = model?.experimental?.modes;
160022
160896
  if (modes && typeof modes === "object") {
@@ -160030,7 +160904,7 @@ function loadModelsDevMetadataFromFile() {
160030
160904
  const modelsJsonPath = getModelsJsonPath();
160031
160905
  let fileFound = false;
160032
160906
  try {
160033
- if (existsSync10(modelsJsonPath)) {
160907
+ if (existsSync11(modelsJsonPath)) {
160034
160908
  fileFound = true;
160035
160909
  const raw = readFileSync7(modelsJsonPath, "utf-8");
160036
160910
  const data = JSON.parse(raw);
@@ -160047,7 +160921,7 @@ function loadModelsDevMetadataFromFile() {
160047
160921
  }
160048
160922
  try {
160049
160923
  const configPath = getOpencodeConfigPath();
160050
- if (configPath && existsSync10(configPath)) {
160924
+ if (configPath && existsSync11(configPath)) {
160051
160925
  let raw = readFileSync7(configPath, "utf-8");
160052
160926
  raw = raw.replace(/"(?:[^"\\]|\\.)*"|\/\/.*$/gm, (match) => match.startsWith('"') ? match : "");
160053
160927
  const config2 = JSON.parse(raw);
@@ -160088,8 +160962,18 @@ async function refreshModelLimitsFromApi(client) {
160088
160962
  const previousSize = apiCache?.size ?? null;
160089
160963
  apiCache = map2;
160090
160964
  apiLoadedAt = Date.now();
160091
- if (previousSize === null || previousSize !== map2.size) {
160092
- sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model metadata entries${previousSize !== null ? ` (was ${previousSize})` : ""}`);
160965
+ if (previousSize === null) {
160966
+ recentlySeenApiSizes.add(map2.size);
160967
+ sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model metadata entries`);
160968
+ } else if (previousSize !== map2.size) {
160969
+ const sizeAlreadySeen = recentlySeenApiSizes.has(map2.size);
160970
+ recentlySeenApiSizes.add(map2.size);
160971
+ if (!sizeAlreadySeen) {
160972
+ sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model metadata entries (was ${previousSize})`);
160973
+ } else if (!oscillationLogged) {
160974
+ oscillationLogged = true;
160975
+ sessionLog("global", `models-dev-cache: API count oscillating between ${[...recentlySeenApiSizes].sort((a, b) => a - b).join(" ↔ ")} — likely upstream provider plugin returning slightly different model sets between calls (e.g. github-copilot's /models endpoint toggling model_picker_enabled). Suppressing further size-change logs.`);
160976
+ }
160093
160977
  }
160094
160978
  } catch (error48) {
160095
160979
  sessionLog("global", "models-dev-cache: API refresh failed:", error48 instanceof Error ? error48.message : String(error48));
@@ -160109,27 +160993,12 @@ function getModelsDevContextLimit(providerID, modelID) {
160109
160993
  }
160110
160994
  return fileCache.get(key)?.limit;
160111
160995
  }
160112
- function getModelsDevInterleavedField(providerID, modelID) {
160113
- const key = `${providerID}/${modelID}`;
160114
- if (apiCache) {
160115
- const fromApi = apiCache.get(key)?.interleavedField;
160116
- if (typeof fromApi === "string" && fromApi.length > 0) {
160117
- return fromApi;
160118
- }
160119
- }
160120
- const now = Date.now();
160121
- if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
160122
- fileLastAttempt = now;
160123
- fileCache = loadModelsDevMetadataFromFile();
160124
- }
160125
- const fromFile = fileCache.get(key)?.interleavedField;
160126
- return typeof fromFile === "string" && fromFile.length > 0 ? fromFile : undefined;
160127
- }
160128
- var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, fileCache = null, fileLastAttempt = 0;
160996
+ var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, recentlySeenApiSizes, oscillationLogged = false, fileCache = null, fileLastAttempt = 0;
160129
160997
  var init_models_dev_cache = __esm(() => {
160130
160998
  init_data_path();
160131
160999
  init_logger();
160132
161000
  RELOAD_INTERVAL_MS = 5 * 60 * 1000;
161001
+ recentlySeenApiSizes = new Set;
160133
161002
  });
160134
161003
 
160135
161004
  // src/shared/rpc-notifications.ts
@@ -160381,7 +161250,7 @@ var init_compartment_runner_validation = __esm(async () => {
160381
161250
  // src/hooks/magic-context/compartment-runner-historian.ts
160382
161251
  import { mkdirSync as mkdirSync4, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
160383
161252
  import { tmpdir as tmpdir2 } from "node:os";
160384
- import { join as join16 } from "node:path";
161253
+ import { join as join17 } from "node:path";
160385
161254
  async function runValidatedHistorianPass(args) {
160386
161255
  const firstRun = await runHistorianPrompt({
160387
161256
  ...args,
@@ -160624,7 +161493,7 @@ function dumpHistorianResponse(sessionId, label, text) {
160624
161493
  mkdirSync4(HISTORIAN_RESPONSE_DUMP_DIR, { recursive: true });
160625
161494
  const safeSessionId = sanitizeDumpName(sessionId);
160626
161495
  const safeLabel = sanitizeDumpName(label);
160627
- const dumpPath = join16(HISTORIAN_RESPONSE_DUMP_DIR, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
161496
+ const dumpPath = join17(HISTORIAN_RESPONSE_DUMP_DIR, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
160628
161497
  writeFileSync4(dumpPath, text, "utf8");
160629
161498
  sessionLog(sessionId, "compartment agent: historian response dumped", {
160630
161499
  label,
@@ -160649,7 +161518,7 @@ var init_compartment_runner_historian = __esm(async () => {
160649
161518
  init_assistant_message_extractor();
160650
161519
  init_compartment_prompt();
160651
161520
  await init_compartment_runner_validation();
160652
- HISTORIAN_RESPONSE_DUMP_DIR = join16(tmpdir2(), "magic-context-historian");
161521
+ HISTORIAN_RESPONSE_DUMP_DIR = join17(tmpdir2(), "magic-context-historian");
160653
161522
  });
160654
161523
 
160655
161524
  // src/hooks/magic-context/compartment-runner-state-xml.ts
@@ -161628,7 +162497,7 @@ var init_derive_budgets = __esm(() => {
161628
162497
  });
161629
162498
 
161630
162499
  // src/features/magic-context/compaction-marker.ts
161631
- import { join as join17 } from "node:path";
162500
+ import { join as join18 } from "node:path";
161632
162501
  function randomBase62(length) {
161633
162502
  const chars = [];
161634
162503
  for (let i = 0;i < length; i++) {
@@ -161648,7 +162517,7 @@ function generatePartId(timestampMs, counter = 0n) {
161648
162517
  return generateId("prt", timestampMs, counter);
161649
162518
  }
161650
162519
  function getOpenCodeDbPath3() {
161651
- return join17(getDataDir(), "opencode", "opencode.db");
162520
+ return join18(getDataDir(), "opencode", "opencode.db");
161652
162521
  }
161653
162522
  function isOpenCodeSchemaCompatible(db, dbPath) {
161654
162523
  if (cachedSchemaCompatible?.path === dbPath) {
@@ -161785,7 +162654,7 @@ var init_compaction_marker = __esm(async () => {
161785
162654
  });
161786
162655
 
161787
162656
  // src/hooks/magic-context/compaction-marker-manager.ts
161788
- import { join as join18 } from "node:path";
162657
+ import { join as join19 } from "node:path";
161789
162658
  function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, directory) {
161790
162659
  const existing = getPersistedCompactionMarkerState(db, sessionId);
161791
162660
  if (existing) {
@@ -161828,7 +162697,7 @@ function removeCompactionMarkerForSession(db, sessionId) {
161828
162697
  }
161829
162698
  }
161830
162699
  function checkCompactionMarkerConsistency(db) {
161831
- const opencodeDbPath = join18(getDataDir(), "opencode", "opencode.db");
162700
+ const opencodeDbPath = join19(getDataDir(), "opencode", "opencode.db");
161832
162701
  let opencodeDb;
161833
162702
  try {
161834
162703
  opencodeDb = new Database(opencodeDbPath, { readonly: true });
@@ -162217,13 +163086,13 @@ var init_caveman = __esm(() => {
162217
163086
  // src/hooks/magic-context/historian-state-file.ts
162218
163087
  import { mkdirSync as mkdirSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
162219
163088
  import { tmpdir as tmpdir3 } from "node:os";
162220
- import { join as join19 } from "node:path";
163089
+ import { join as join20 } from "node:path";
162221
163090
  function maybeWriteHistorianStateFile(sessionId, existingState) {
162222
163091
  if (existingState.length <= HISTORIAN_STATE_INLINE_THRESHOLD)
162223
163092
  return;
162224
163093
  try {
162225
163094
  mkdirSync5(HISTORIAN_STATE_DIR, { recursive: true });
162226
- const path5 = join19(HISTORIAN_STATE_DIR, `state-${sessionId}-${Date.now()}.xml`);
163095
+ const path5 = join20(HISTORIAN_STATE_DIR, `state-${sessionId}-${Date.now()}.xml`);
162227
163096
  writeFileSync5(path5, existingState, "utf8");
162228
163097
  return path5;
162229
163098
  } catch {
@@ -162239,7 +163108,7 @@ function cleanupHistorianStateFile(path5) {
162239
163108
  }
162240
163109
  var HISTORIAN_STATE_INLINE_THRESHOLD = 30000, HISTORIAN_STATE_DIR;
162241
163110
  var init_historian_state_file = __esm(() => {
162242
- HISTORIAN_STATE_DIR = join19(tmpdir3(), "magic-context-historian");
163111
+ HISTORIAN_STATE_DIR = join20(tmpdir3(), "magic-context-historian");
162243
163112
  });
162244
163113
 
162245
163114
  // src/features/magic-context/memory/embedding-backfill.ts
@@ -162744,12 +163613,24 @@ var init_compartment_runner_compressor = __esm(async () => {
162744
163613
  // src/hooks/magic-context/compartment-runner-drop-queue.ts
162745
163614
  function queueDropsForCompartmentalizedMessages(db, sessionId, upToMessageIndex) {
162746
163615
  const tags = getTagsBySession(db, sessionId);
162747
- const rawTagKeys = new Set(getRawSessionTagKeysThrough(sessionId, upToMessageIndex));
163616
+ const { messageFileKeys, toolObservations } = getRawSessionTagKeysThrough(sessionId, upToMessageIndex);
162748
163617
  let dropsQueued = 0;
162749
163618
  for (const tag of tags) {
162750
163619
  if (tag.status !== "active")
162751
163620
  continue;
162752
- if (rawTagKeys.has(tag.messageId)) {
163621
+ if (tag.type === "tool") {
163622
+ const observedOwners = toolObservations.get(tag.messageId);
163623
+ if (!observedOwners)
163624
+ continue;
163625
+ if (tag.toolOwnerMessageId !== null) {
163626
+ if (!observedOwners.has(tag.toolOwnerMessageId))
163627
+ continue;
163628
+ }
163629
+ queuePendingOp(db, sessionId, tag.tagNumber, "drop");
163630
+ dropsQueued += 1;
163631
+ continue;
163632
+ }
163633
+ if (messageFileKeys.has(tag.messageId)) {
162753
163634
  queuePendingOp(db, sessionId, tag.tagNumber, "drop");
162754
163635
  dropsQueued += 1;
162755
163636
  }
@@ -163309,8 +164190,8 @@ var exports_tui_config = {};
163309
164190
  __export(exports_tui_config, {
163310
164191
  ensureTuiPluginEntry: () => ensureTuiPluginEntry
163311
164192
  });
163312
- import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
163313
- import { dirname as dirname6, join as join22 } from "node:path";
164193
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
164194
+ import { dirname as dirname6, join as join23 } from "node:path";
163314
164195
  function isMagicContextEntry(entry) {
163315
164196
  if (!entry)
163316
164197
  return false;
@@ -163324,11 +164205,11 @@ function isMagicContextEntry(entry) {
163324
164205
  }
163325
164206
  function resolveTuiConfigPath() {
163326
164207
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
163327
- const jsoncPath = join22(configDir, "tui.jsonc");
163328
- const jsonPath = join22(configDir, "tui.json");
163329
- if (existsSync12(jsoncPath))
164208
+ const jsoncPath = join23(configDir, "tui.jsonc");
164209
+ const jsonPath = join23(configDir, "tui.json");
164210
+ if (existsSync13(jsoncPath))
163330
164211
  return jsoncPath;
163331
- if (existsSync12(jsonPath))
164212
+ if (existsSync13(jsonPath))
163332
164213
  return jsonPath;
163333
164214
  return jsonPath;
163334
164215
  }
@@ -163336,7 +164217,7 @@ function ensureTuiPluginEntry() {
163336
164217
  try {
163337
164218
  const configPath = resolveTuiConfigPath();
163338
164219
  let config2 = {};
163339
- if (existsSync12(configPath)) {
164220
+ if (existsSync13(configPath)) {
163340
164221
  const raw = readFileSync9(configPath, "utf-8");
163341
164222
  config2 = import_comment_json3.parse(raw) ?? {};
163342
164223
  }
@@ -164133,42 +165014,8 @@ async function runSidekick(deps) {
164133
165014
  }
164134
165015
  }
164135
165016
 
164136
- // src/features/magic-context/tool-definition-tokens.ts
164137
- init_read_session_formatting();
164138
- var measurements = new Map;
164139
- function keyFor(providerID, modelID, agentName) {
164140
- const agent = agentName && agentName.length > 0 ? agentName : "default";
164141
- return `${providerID}/${modelID}/${agent}`;
164142
- }
164143
- function recordToolDefinition(providerID, modelID, agentName, toolID, description, parameters) {
164144
- if (!providerID || !modelID || !toolID)
164145
- return;
164146
- const key = keyFor(providerID, modelID, agentName);
164147
- let paramsText = "";
164148
- try {
164149
- paramsText = parameters === undefined ? "" : JSON.stringify(parameters);
164150
- } catch {
164151
- paramsText = "";
164152
- }
164153
- const tokens = estimateTokens(description ?? "") + estimateTokens(paramsText);
164154
- let inner = measurements.get(key);
164155
- if (!inner) {
164156
- inner = new Map;
164157
- measurements.set(key, inner);
164158
- }
164159
- inner.set(toolID, tokens);
164160
- }
164161
- function getMeasuredToolDefinitionTokens(providerID, modelID, agentName) {
164162
- if (!providerID || !modelID)
164163
- return;
164164
- const inner = measurements.get(keyFor(providerID, modelID, agentName));
164165
- if (!inner || inner.size === 0)
164166
- return;
164167
- let total = 0;
164168
- for (const tokens of inner.values())
164169
- total += tokens;
164170
- return total;
164171
- }
165017
+ // src/index.ts
165018
+ init_tool_definition_tokens();
164172
165019
 
164173
165020
  // src/hooks/auto-update-checker/index.ts
164174
165021
  init_logger();
@@ -166859,14 +167706,46 @@ function createScheduler(config2) {
166859
167706
 
166860
167707
  // src/features/magic-context/tagger.ts
166861
167708
  init_storage_tags();
167709
+ var TOOL_COMPOSITE_KEY_SEP = "\x00";
167710
+ function makeToolCompositeKey(ownerMsgId, callId) {
167711
+ return `${ownerMsgId}${TOOL_COMPOSITE_KEY_SEP}${callId}`;
167712
+ }
166862
167713
  var GET_COUNTER_SQL = `SELECT counter FROM session_meta WHERE session_id = ?`;
166863
- var GET_ASSIGNMENTS_SQL = "SELECT message_id, tag_number FROM tags WHERE session_id = ? ORDER BY tag_number ASC";
167714
+ var GET_ASSIGNMENTS_SQL = "SELECT message_id, tag_number, type, tool_owner_message_id FROM tags WHERE session_id = ? ORDER BY tag_number ASC";
167715
+ var PROBE_DATA_VERSION_SQL = "PRAGMA main.data_version";
167716
+ var PROBE_TOTAL_CHANGES_SQL = "SELECT total_changes() AS tc";
167717
+ var probeDataVersionStatements = new WeakMap;
167718
+ var probeTotalChangesStatements = new WeakMap;
167719
+ function getProbeDataVersionStatement(db) {
167720
+ let stmt = probeDataVersionStatements.get(db);
167721
+ if (!stmt) {
167722
+ stmt = db.prepare(PROBE_DATA_VERSION_SQL);
167723
+ probeDataVersionStatements.set(db, stmt);
167724
+ }
167725
+ return stmt;
167726
+ }
167727
+ function getProbeTotalChangesStatement(db) {
167728
+ let stmt = probeTotalChangesStatements.get(db);
167729
+ if (!stmt) {
167730
+ stmt = db.prepare(PROBE_TOTAL_CHANGES_SQL);
167731
+ probeTotalChangesStatements.set(db, stmt);
167732
+ }
167733
+ return stmt;
167734
+ }
166864
167735
  function isAssignmentRow(row) {
166865
167736
  if (row === null || typeof row !== "object") {
166866
167737
  return false;
166867
167738
  }
166868
167739
  const candidate = row;
166869
- return typeof candidate.message_id === "string" && typeof candidate.tag_number === "number";
167740
+ if (typeof candidate.message_id !== "string")
167741
+ return false;
167742
+ if (typeof candidate.tag_number !== "number")
167743
+ return false;
167744
+ if (candidate.type !== "message" && candidate.type !== "tool" && candidate.type !== "file")
167745
+ return false;
167746
+ if (candidate.tool_owner_message_id !== null && typeof candidate.tool_owner_message_id !== "string")
167747
+ return false;
167748
+ return true;
166870
167749
  }
166871
167750
  var UPSERT_COUNTER_SQL = `
166872
167751
  INSERT INTO session_meta (session_id, counter, harness)
@@ -166900,6 +167779,7 @@ var MAX_TAG_ALLOC_RETRIES = 5;
166900
167779
  function createTagger() {
166901
167780
  const counters = new Map;
166902
167781
  const assignments = new Map;
167782
+ const loadSignatures = new Map;
166903
167783
  function getSessionAssignments(sessionId) {
166904
167784
  let map2 = assignments.get(sessionId);
166905
167785
  if (!map2) {
@@ -166918,15 +167798,15 @@ function createTagger() {
166918
167798
  counters.set(sessionId, next);
166919
167799
  getUpsertCounterStatement(db).run(sessionId, next, getHarness());
166920
167800
  }
166921
- function assignTag(sessionId, messageId, type, byteSize2, db, reasoningByteSize = 0, toolName = null, inputByteSize = 0) {
167801
+ function allocateTag(sessionId, messageId, type, byteSize2, db, reasoningByteSize, toolName, inputByteSize, toolOwnerMessageId, mapKey, dbExistingLookup) {
166922
167802
  const sessionAssignments = getSessionAssignments(sessionId);
166923
- const existing = sessionAssignments.get(messageId);
167803
+ const existing = sessionAssignments.get(mapKey);
166924
167804
  if (existing !== undefined) {
166925
167805
  return existing;
166926
167806
  }
166927
- const dbExisting = getTagNumberByMessageId(db, sessionId, messageId);
167807
+ const dbExisting = dbExistingLookup();
166928
167808
  if (dbExisting !== null) {
166929
- sessionAssignments.set(messageId, dbExisting);
167809
+ sessionAssignments.set(mapKey, dbExisting);
166930
167810
  syncCounterAtLeast(sessionId, db, dbExisting);
166931
167811
  return dbExisting;
166932
167812
  }
@@ -166936,16 +167816,16 @@ function createTagger() {
166936
167816
  const next = Math.max(memCounter, dbMax) + 1;
166937
167817
  try {
166938
167818
  db.transaction(() => {
166939
- insertTag(db, sessionId, messageId, type, byteSize2, next, reasoningByteSize, toolName, inputByteSize);
167819
+ insertTag(db, sessionId, messageId, type, byteSize2, next, reasoningByteSize, toolName, inputByteSize, toolOwnerMessageId);
166940
167820
  getUpsertCounterStatement(db).run(sessionId, next, getHarness());
166941
167821
  })();
166942
167822
  } catch (error48) {
166943
167823
  if (!isUniqueConstraintError(error48)) {
166944
167824
  throw error48;
166945
167825
  }
166946
- const racedRow = getTagNumberByMessageId(db, sessionId, messageId);
167826
+ const racedRow = dbExistingLookup();
166947
167827
  if (racedRow !== null) {
166948
- sessionAssignments.set(messageId, racedRow);
167828
+ sessionAssignments.set(mapKey, racedRow);
166949
167829
  syncCounterAtLeast(sessionId, db, racedRow);
166950
167830
  return racedRow;
166951
167831
  }
@@ -166954,51 +167834,124 @@ function createTagger() {
166954
167834
  continue;
166955
167835
  }
166956
167836
  counters.set(sessionId, next);
166957
- sessionAssignments.set(messageId, next);
167837
+ sessionAssignments.set(mapKey, next);
166958
167838
  return next;
166959
167839
  }
166960
- throw new Error(`tagger.assignTag: failed to allocate tag for session=${sessionId} message=${messageId} after ${MAX_TAG_ALLOC_RETRIES} retries`);
167840
+ throw new Error(`tagger.allocateTag: failed to allocate tag for session=${sessionId} key=${mapKey} after ${MAX_TAG_ALLOC_RETRIES} retries`);
167841
+ }
167842
+ function assignTag(sessionId, messageId, type, byteSize2, db, reasoningByteSize = 0, toolName = null, inputByteSize = 0) {
167843
+ if (type === "tool") {
167844
+ throw new Error("tagger.assignTag: type='tool' is forbidden — use assignToolTag(sessionId, callId, ownerMsgId, ...)");
167845
+ }
167846
+ return allocateTag(sessionId, messageId, type, byteSize2, db, reasoningByteSize, toolName, inputByteSize, null, messageId, () => getTagNumberByMessageId(db, sessionId, messageId));
167847
+ }
167848
+ function assignToolTag(sessionId, callId, ownerMsgId, byteSize2, db, reasoningByteSize = 0, toolName = null, inputByteSize = 0) {
167849
+ const compositeKey = makeToolCompositeKey(ownerMsgId, callId);
167850
+ const sessionAssignments = getSessionAssignments(sessionId);
167851
+ const existing = sessionAssignments.get(compositeKey);
167852
+ if (existing !== undefined) {
167853
+ return existing;
167854
+ }
167855
+ const dbHit = getToolTagNumberByOwner(db, sessionId, callId, ownerMsgId);
167856
+ if (dbHit !== null) {
167857
+ sessionAssignments.set(compositeKey, dbHit);
167858
+ syncCounterAtLeast(sessionId, db, dbHit);
167859
+ return dbHit;
167860
+ }
167861
+ for (let attempt = 0;attempt < MAX_TAG_ALLOC_RETRIES; attempt += 1) {
167862
+ const orphan = getNullOwnerToolTag(db, sessionId, callId);
167863
+ if (orphan === null)
167864
+ break;
167865
+ const claimed = adoptNullOwnerToolTag(db, orphan.id, ownerMsgId);
167866
+ if (claimed) {
167867
+ sessionAssignments.set(compositeKey, orphan.tagNumber);
167868
+ syncCounterAtLeast(sessionId, db, orphan.tagNumber);
167869
+ return orphan.tagNumber;
167870
+ }
167871
+ const recheck = getToolTagNumberByOwner(db, sessionId, callId, ownerMsgId);
167872
+ if (recheck !== null) {
167873
+ sessionAssignments.set(compositeKey, recheck);
167874
+ syncCounterAtLeast(sessionId, db, recheck);
167875
+ return recheck;
167876
+ }
167877
+ }
167878
+ return allocateTag(sessionId, callId, "tool", byteSize2, db, reasoningByteSize, toolName, inputByteSize, ownerMsgId, compositeKey, () => getToolTagNumberByOwner(db, sessionId, callId, ownerMsgId));
166961
167879
  }
166962
- function getTag(sessionId, messageId) {
167880
+ function getTag(sessionId, messageId, _type) {
166963
167881
  return assignments.get(sessionId)?.get(messageId);
166964
167882
  }
167883
+ function getToolTag(sessionId, callId, ownerMsgId) {
167884
+ return assignments.get(sessionId)?.get(makeToolCompositeKey(ownerMsgId, callId));
167885
+ }
166965
167886
  function bindTag(sessionId, messageId, tagNumber) {
166966
167887
  getSessionAssignments(sessionId).set(messageId, tagNumber);
166967
167888
  }
167889
+ function bindToolTag(sessionId, callId, ownerMsgId, tagNumber) {
167890
+ getSessionAssignments(sessionId).set(makeToolCompositeKey(ownerMsgId, callId), tagNumber);
167891
+ }
166968
167892
  function getAssignments(sessionId) {
166969
167893
  return getSessionAssignments(sessionId);
166970
167894
  }
166971
167895
  function resetCounter(sessionId, db) {
166972
167896
  counters.set(sessionId, 0);
166973
167897
  assignments.delete(sessionId);
167898
+ loadSignatures.delete(sessionId);
166974
167899
  getResetCounterStatement(db).run(sessionId, getHarness());
166975
167900
  }
166976
167901
  function getCounter(sessionId) {
166977
167902
  return counters.get(sessionId) ?? 0;
166978
167903
  }
167904
+ function probeSignature(db) {
167905
+ const dvRow = getProbeDataVersionStatement(db).get();
167906
+ const tcRow = getProbeTotalChangesStatement(db).get();
167907
+ return {
167908
+ dataVersion: dvRow?.data_version ?? 0,
167909
+ totalChanges: tcRow?.tc ?? 0
167910
+ };
167911
+ }
166979
167912
  function initFromDb(sessionId, db) {
167913
+ const probe = probeSignature(db);
167914
+ const cached2 = loadSignatures.get(sessionId);
167915
+ if (cached2 !== undefined && cached2.db === db && cached2.dataVersion === probe.dataVersion && cached2.totalChanges === probe.totalChanges) {
167916
+ return;
167917
+ }
166980
167918
  const row = db.prepare(GET_COUNTER_SQL).get(sessionId);
166981
167919
  const assignmentRows = db.prepare(GET_ASSIGNMENTS_SQL).all(sessionId).filter(isAssignmentRow);
166982
167920
  const sessionAssignments = getSessionAssignments(sessionId);
166983
167921
  sessionAssignments.clear();
166984
167922
  let maxTagNumber = 0;
166985
167923
  for (const assignment of assignmentRows) {
166986
- sessionAssignments.set(assignment.message_id, assignment.tag_number);
167924
+ if (assignment.type === "tool") {
167925
+ if (assignment.tool_owner_message_id !== null) {
167926
+ sessionAssignments.set(makeToolCompositeKey(assignment.tool_owner_message_id, assignment.message_id), assignment.tag_number);
167927
+ }
167928
+ } else {
167929
+ sessionAssignments.set(assignment.message_id, assignment.tag_number);
167930
+ }
166987
167931
  if (assignment.tag_number > maxTagNumber) {
166988
167932
  maxTagNumber = assignment.tag_number;
166989
167933
  }
166990
167934
  }
166991
167935
  const counter = Math.max(row?.counter ?? 0, maxTagNumber, counters.get(sessionId) ?? 0);
166992
167936
  counters.set(sessionId, counter);
167937
+ loadSignatures.set(sessionId, {
167938
+ db,
167939
+ dataVersion: probe.dataVersion,
167940
+ totalChanges: probe.totalChanges
167941
+ });
166993
167942
  }
166994
167943
  function cleanup(sessionId) {
166995
167944
  counters.delete(sessionId);
166996
167945
  assignments.delete(sessionId);
167946
+ loadSignatures.delete(sessionId);
166997
167947
  }
166998
167948
  return {
166999
167949
  assignTag,
167950
+ assignToolTag,
167000
167951
  getTag,
167952
+ getToolTag,
167001
167953
  bindTag,
167954
+ bindToolTag,
167002
167955
  getAssignments,
167003
167956
  resetCounter,
167004
167957
  getCounter,
@@ -167642,24 +168595,149 @@ ${snap.error}`;
167642
168595
  // src/hooks/magic-context/hook.ts
167643
168596
  init_derive_budgets();
167644
168597
 
167645
- // src/features/magic-context/overflow-detection.ts
167646
- var OVERFLOW_PATTERNS = [
167647
- /prompt is too long/i,
167648
- /input is too long for requested model/i,
167649
- /exceeds the context window/i,
167650
- /input token count.*exceeds the maximum/i,
167651
- /maximum prompt length is \d+/i,
167652
- /reduce the length of the messages/i,
167653
- /maximum context length is \d+ tokens/i,
167654
- /exceeds the limit of \d+/i,
167655
- /exceeds the available context size/i,
167656
- /greater than the context length/i,
167657
- /context window exceeds limit/i,
167658
- /exceeded model token limit/i,
167659
- /context[_ ]length[_ ]exceeded/i,
167660
- /request entity too large/i,
167661
- /context length is only \d+ tokens/i,
167662
- /input length.*exceeds.*context length/i,
168598
+ // src/features/magic-context/message-index-async.ts
168599
+ init_logger();
168600
+ await init_message_index();
168601
+ var INCREMENTAL_DEBOUNCE_MS = 100;
168602
+ var reconciledSessions = new Set;
168603
+ var reconciliationScheduledSessions = new Set;
168604
+ var sessionLocks = new Map;
168605
+ var incrementalTimers = new Map;
168606
+ var pendingIncrementalKeys = new Set;
168607
+ function defer(fn) {
168608
+ const immediate = globalThis.setImmediate;
168609
+ if (typeof immediate === "function") {
168610
+ immediate(fn);
168611
+ return;
168612
+ }
168613
+ setTimeout(fn, 0);
168614
+ }
168615
+ function runWithSessionLock(sessionId, operation) {
168616
+ const previous = sessionLocks.get(sessionId) ?? Promise.resolve();
168617
+ const run = previous.catch(() => {
168618
+ return;
168619
+ }).then(async () => {
168620
+ await operation();
168621
+ });
168622
+ sessionLocks.set(sessionId, run);
168623
+ run.finally(() => {
168624
+ if (sessionLocks.get(sessionId) === run) {
168625
+ sessionLocks.delete(sessionId);
168626
+ }
168627
+ }).catch(() => {
168628
+ return;
168629
+ });
168630
+ return run;
168631
+ }
168632
+ function logIndexingError(sessionId, action, error48) {
168633
+ sessionLog(sessionId, `message FTS async ${action} failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
168634
+ log(`[message-index-async] ${action} failed for ${sessionId}:`, error48);
168635
+ }
168636
+ async function reconcileSessionIndex(db, sessionId, readMessages) {
168637
+ await runWithSessionLock(sessionId, () => {
168638
+ if (reconciledSessions.has(sessionId)) {
168639
+ return;
168640
+ }
168641
+ const messages = readMessages(sessionId);
168642
+ if (messages.length === 0) {
168643
+ clearIndexedMessages(db, sessionId);
168644
+ reconciledSessions.add(sessionId);
168645
+ return;
168646
+ }
168647
+ let lastIndexedOrdinal = getLastIndexedOrdinal(db, sessionId);
168648
+ if (lastIndexedOrdinal > messages.length) {
168649
+ clearIndexedMessages(db, sessionId);
168650
+ lastIndexedOrdinal = 0;
168651
+ }
168652
+ const watermark = Math.min(lastIndexedOrdinal, messages.length);
168653
+ indexMessagesAfterOrdinal(db, sessionId, messages, watermark, messages.length);
168654
+ reconciledSessions.add(sessionId);
168655
+ });
168656
+ }
168657
+ function scheduleReconciliation(db, sessionId, readMessages) {
168658
+ if (reconciledSessions.has(sessionId) || reconciliationScheduledSessions.has(sessionId)) {
168659
+ return;
168660
+ }
168661
+ reconciliationScheduledSessions.add(sessionId);
168662
+ defer(() => {
168663
+ reconcileSessionIndex(db, sessionId, readMessages).catch((error48) => {
168664
+ reconciliationScheduledSessions.delete(sessionId);
168665
+ logIndexingError(sessionId, "reconciliation", error48);
168666
+ });
168667
+ });
168668
+ }
168669
+ function scheduleIncrementalIndex(db, sessionId, messageId, readSingleMessage) {
168670
+ const key = `${sessionId}\x00${messageId}`;
168671
+ if (incrementalTimers.has(key) || pendingIncrementalKeys.has(key)) {
168672
+ return;
168673
+ }
168674
+ const timer = setTimeout(() => {
168675
+ incrementalTimers.delete(key);
168676
+ pendingIncrementalKeys.add(key);
168677
+ runWithSessionLock(sessionId, () => {
168678
+ const message = readSingleMessage(sessionId, messageId);
168679
+ if (!message) {
168680
+ return;
168681
+ }
168682
+ indexSingleMessage(db, sessionId, message);
168683
+ }).catch((error48) => {
168684
+ logIndexingError(sessionId, `incremental index for ${messageId}`, error48);
168685
+ }).finally(() => {
168686
+ pendingIncrementalKeys.delete(key);
168687
+ });
168688
+ }, INCREMENTAL_DEBOUNCE_MS);
168689
+ incrementalTimers.set(key, timer);
168690
+ }
168691
+ function scheduleClearAndReindex(db, sessionId, readMessages) {
168692
+ reconciledSessions.delete(sessionId);
168693
+ reconciliationScheduledSessions.delete(sessionId);
168694
+ defer(() => {
168695
+ runWithSessionLock(sessionId, () => {
168696
+ clearIndexedMessages(db, sessionId);
168697
+ const messages = readMessages(sessionId);
168698
+ indexMessagesAfterOrdinal(db, sessionId, messages, 0, messages.length);
168699
+ reconciledSessions.add(sessionId);
168700
+ }).catch((error48) => {
168701
+ logIndexingError(sessionId, "clear and reindex", error48);
168702
+ });
168703
+ });
168704
+ }
168705
+ function clearSessionTracking(sessionId) {
168706
+ reconciledSessions.delete(sessionId);
168707
+ reconciliationScheduledSessions.delete(sessionId);
168708
+ sessionLocks.delete(sessionId);
168709
+ const prefix = `${sessionId}\x00`;
168710
+ for (const [key, timer] of incrementalTimers) {
168711
+ if (key.startsWith(prefix)) {
168712
+ clearTimeout(timer);
168713
+ incrementalTimers.delete(key);
168714
+ }
168715
+ }
168716
+ for (const key of pendingIncrementalKeys) {
168717
+ if (key.startsWith(prefix)) {
168718
+ pendingIncrementalKeys.delete(key);
168719
+ }
168720
+ }
168721
+ }
168722
+
168723
+ // src/features/magic-context/overflow-detection.ts
168724
+ var OVERFLOW_PATTERNS = [
168725
+ /prompt is too long/i,
168726
+ /input is too long for requested model/i,
168727
+ /exceeds the context window/i,
168728
+ /input token count.*exceeds the maximum/i,
168729
+ /maximum prompt length is \d+/i,
168730
+ /reduce the length of the messages/i,
168731
+ /maximum context length is \d+ tokens/i,
168732
+ /exceeds the limit of \d+/i,
168733
+ /exceeds the available context size/i,
168734
+ /greater than the context length/i,
168735
+ /context window exceeds limit/i,
168736
+ /exceeded model token limit/i,
168737
+ /context[_ ]length[_ ]exceeded/i,
168738
+ /request entity too large/i,
168739
+ /context length is only \d+ tokens/i,
168740
+ /input length.*exceeds.*context length/i,
167663
168741
  /prompt too long; exceeded (?:max )?context length/i,
167664
168742
  /too large for model with \d+ maximum context length/i,
167665
168743
  /model_context_window_exceeded/i,
@@ -167812,6 +168890,24 @@ function getMessageUpdatedAssistantInfo(properties) {
167812
168890
  error: info.error !== undefined ? info.error : undefined
167813
168891
  };
167814
168892
  }
168893
+ function getMessageUpdatedInfo(properties) {
168894
+ const eventProps = getSessionProperties(properties);
168895
+ if (!eventProps || !isRecord(eventProps.info)) {
168896
+ return null;
168897
+ }
168898
+ const info = eventProps.info;
168899
+ if (typeof info.role !== "string" || typeof info.sessionID !== "string") {
168900
+ return null;
168901
+ }
168902
+ const time3 = isRecord(info.time) ? info.time : undefined;
168903
+ return {
168904
+ role: info.role,
168905
+ sessionID: info.sessionID,
168906
+ messageID: typeof info.id === "string" ? info.id : undefined,
168907
+ finish: typeof info.finish === "string" ? info.finish : undefined,
168908
+ completedAt: typeof time3?.completed === "number" ? time3.completed : undefined
168909
+ };
168910
+ }
167815
168911
  function getSessionErrorInfo(properties) {
167816
168912
  if (!isRecord(properties))
167817
168913
  return null;
@@ -167835,6 +168931,7 @@ function getMessageRemovedInfo(properties) {
167835
168931
 
167836
168932
  // src/hooks/magic-context/event-handler.ts
167837
168933
  init_note_nudger();
168934
+ await init_read_session_chunk();
167838
168935
 
167839
168936
  // src/hooks/magic-context/transform.ts
167840
168937
  init_compartment_storage();
@@ -168110,23 +169207,11 @@ function readUint32BE(b, offset) {
168110
169207
  // src/hooks/magic-context/transform.ts
168111
169208
  init_note_nudger();
168112
169209
  init_read_session_formatting();
169210
+ init_send_session_notification();
168113
169211
  await __promiseAll([
168114
169212
  init_inject_compartments(),
168115
169213
  init_read_session_chunk()
168116
169214
  ]);
168117
-
168118
- // src/hooks/magic-context/reasoning-capability.ts
168119
- init_models_dev_cache();
168120
- function modelRequiresInterleavedReasoning(model) {
168121
- if (!model?.providerID || !model?.modelID) {
168122
- return false;
168123
- }
168124
- const field = getModelsDevInterleavedField(model.providerID, model.modelID);
168125
- return typeof field === "string" && field.length > 0;
168126
- }
168127
-
168128
- // src/hooks/magic-context/transform.ts
168129
- init_send_session_notification();
168130
169215
  // src/hooks/magic-context/sentinel.ts
168131
169216
  function makeSentinel(originalPart) {
168132
169217
  const sentinel = {
@@ -168303,9 +169388,7 @@ function stripDroppedPlaceholderMessages(messages) {
168303
169388
  }
168304
169389
  return { stripped, sentineledIds };
168305
169390
  }
168306
- function replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedWatermark, skipTypedReasoningCleanup = false) {
168307
- if (skipTypedReasoningCleanup)
168308
- return 0;
169391
+ function replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedWatermark) {
168309
169392
  if (persistedWatermark <= 0)
168310
169393
  return 0;
168311
169394
  let cleared = 0;
@@ -168351,9 +169434,7 @@ function replayStrippedInlineThinking(messages, messageTagNumbers, persistedWate
168351
169434
  }
168352
169435
  return stripped;
168353
169436
  }
168354
- function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers, clearReasoningAge, skipTypedReasoningCleanup = false) {
168355
- if (skipTypedReasoningCleanup)
168356
- return 0;
169437
+ function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers, clearReasoningAge) {
168357
169438
  const maxTag = findMaxTag(messageTagNumbers);
168358
169439
  if (maxTag === 0)
168359
169440
  return 0;
@@ -168388,9 +169469,7 @@ function findMaxTag(messageTagNumbers) {
168388
169469
  return max;
168389
169470
  }
168390
169471
  var CLEARED_REASONING_TYPES = new Set(["thinking", "reasoning"]);
168391
- function stripClearedReasoning(messages, skipTypedReasoningCleanup = false) {
168392
- if (skipTypedReasoningCleanup)
168393
- return 0;
169472
+ function stripClearedReasoning(messages) {
168394
169473
  let stripped = 0;
168395
169474
  for (const message of messages) {
168396
169475
  if (message.info.role !== "assistant")
@@ -168909,377 +169988,192 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
168909
169988
  // src/hooks/magic-context/strip-structural-noise.ts
168910
169989
  var STRUCTURAL_PART_TYPES = new Set(["meta", "step-start", "step-finish", "reasoning"]);
168911
169990
  function isStructuralNoisePart(part) {
168912
- if (!isRecord(part) || typeof part.type !== "string") {
168913
- return false;
168914
- }
168915
- if (!STRUCTURAL_PART_TYPES.has(part.type)) {
168916
- return false;
168917
- }
168918
- if (part.type === "reasoning" && typeof part.text === "string" && part.text !== "[cleared]") {
168919
- return false;
168920
- }
168921
- return true;
168922
- }
168923
- function stripStructuralNoise(messages) {
168924
- let strippedParts = 0;
168925
- for (const message of messages) {
168926
- if (!Array.isArray(message.parts)) {
168927
- continue;
168928
- }
168929
- for (let i = 0;i < message.parts.length; i++) {
168930
- const part = message.parts[i];
168931
- if (isSentinel(part))
168932
- continue;
168933
- if (!isStructuralNoisePart(part))
168934
- continue;
168935
- message.parts[i] = makeSentinel(part);
168936
- strippedParts++;
168937
- }
168938
- }
168939
- return strippedParts;
168940
- }
168941
- // src/hooks/magic-context/tag-messages.ts
168942
- await init_storage();
168943
- // src/hooks/magic-context/drop-stale-reduce-calls.ts
168944
- var STALE_TOOL_NAMES = new Set(["ctx_reduce"]);
168945
- function isReduceToolPart(part) {
168946
- if (!isRecord(part))
168947
- return false;
168948
- if (part.type === "tool" && typeof part.tool === "string" && STALE_TOOL_NAMES.has(part.tool))
168949
- return true;
168950
- if (part.type === "tool-invocation" && typeof part.toolName === "string" && STALE_TOOL_NAMES.has(part.toolName))
168951
- return true;
168952
- if (part.type === "tool_use" && typeof part.name === "string" && STALE_TOOL_NAMES.has(part.name))
168953
- return true;
168954
- return false;
168955
- }
168956
- function hasAnyMeaningfulPart(parts) {
168957
- for (const part of parts) {
168958
- if (!isRecord(part))
168959
- continue;
168960
- if (part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0)
168961
- return true;
168962
- if (part.type === "thinking" || part.type === "reasoning" || part.type === "redacted_thinking")
168963
- continue;
168964
- if (part.type === "meta" || part.type === "step-start" || part.type === "step-finish")
168965
- continue;
168966
- if (part.type !== "tool" || !isReduceToolPart(part))
168967
- return true;
168968
- }
168969
- return false;
168970
- }
168971
- function dropStaleReduceCalls(messages, protectedCount = 0) {
168972
- let didDrop = false;
168973
- const protectedStart = messages.length - protectedCount;
168974
- for (let i = 0;i < messages.length; i++) {
168975
- if (i >= protectedStart)
168976
- break;
168977
- const message = messages[i];
168978
- let touched = false;
168979
- for (let j = 0;j < message.parts.length; j++) {
168980
- const part = message.parts[j];
168981
- if (isSentinel(part))
168982
- continue;
168983
- if (isReduceToolPart(part)) {
168984
- message.parts[j] = makeSentinel(part);
168985
- touched = true;
168986
- }
168987
- }
168988
- if (touched) {
168989
- didDrop = true;
168990
- if (!hasAnyMeaningfulPart(message.parts)) {
168991
- message.parts.length = 0;
168992
- message.parts.push(makeSentinel(undefined));
168993
- }
168994
- }
168995
- }
168996
- return didDrop;
168997
- }
168998
-
168999
- // src/hooks/magic-context/tag-messages.ts
169000
- init_tag_content_primitives();
169001
-
169002
- // src/hooks/magic-context/tag-id-fallback.ts
169003
- await init_storage();
169004
- function parseScopedContentId(contentId) {
169005
- const match = /^(.*):(p|file)(\d+)$/.exec(contentId);
169006
- if (!match)
169007
- return null;
169008
- return {
169009
- messageId: match[1],
169010
- type: match[2] === "file" ? "file" : "message",
169011
- partIndex: Number.parseInt(match[3], 10)
169012
- };
169013
- }
169014
- function createScopedAssignments(assignments) {
169015
- const scoped = new Map;
169016
- for (const [contentId, tagNumber] of assignments) {
169017
- const parsed = parseScopedContentId(contentId);
169018
- if (!parsed)
169019
- continue;
169020
- const entry = scoped.get(parsed.messageId) ?? { message: [], file: [] };
169021
- entry[parsed.type].push({ tagNumber, contentId, partIndex: parsed.partIndex });
169022
- scoped.set(parsed.messageId, entry);
169023
- }
169024
- for (const entry of scoped.values()) {
169025
- entry.message.sort((left, right) => left.partIndex - right.partIndex);
169026
- entry.file.sort((left, right) => left.partIndex - right.partIndex);
169027
- }
169028
- return scoped;
169029
- }
169030
- function createExistingTagResolver(sessionId, tagger, db) {
169031
- const assignments = tagger.getAssignments(sessionId);
169032
- let cachedAssignmentSize = -1;
169033
- let cachedScopedAssignments = null;
169034
- const usedTagNumbers = new Set;
169035
- function getScopedAssignments() {
169036
- if (!cachedScopedAssignments || cachedAssignmentSize !== assignments.size) {
169037
- cachedScopedAssignments = createScopedAssignments(assignments);
169038
- cachedAssignmentSize = assignments.size;
169039
- }
169040
- return cachedScopedAssignments;
169041
- }
169042
- return {
169043
- resolve(messageId, type, currentContentId, ordinal) {
169044
- const exactTagId = assignments.get(currentContentId);
169045
- if (exactTagId !== undefined) {
169046
- usedTagNumbers.add(exactTagId);
169047
- return exactTagId;
169048
- }
169049
- const fallback = getScopedAssignments().get(messageId)?.[type][ordinal];
169050
- if (!fallback || usedTagNumbers.has(fallback.tagNumber)) {
169051
- return;
169052
- }
169053
- updateTagMessageId(db, sessionId, fallback.tagNumber, currentContentId);
169054
- tagger.bindTag(sessionId, currentContentId, fallback.tagNumber);
169055
- usedTagNumbers.add(fallback.tagNumber);
169056
- return fallback.tagNumber;
169057
- }
169058
- };
169059
- }
169060
-
169061
- // src/hooks/magic-context/tag-messages.ts
169062
- init_tag_part_guards();
169063
-
169064
- // src/hooks/magic-context/tool-drop-target.ts
169065
- var DROP_PREFIX = "[dropped";
169066
- var IGNORE_PART_TYPES = new Set([
169067
- "thinking",
169068
- "reasoning",
169069
- "redacted_thinking",
169070
- "meta",
169071
- "step-start",
169072
- "step-finish"
169073
- ]);
169074
- function isToolCallId(value) {
169075
- return typeof value === "string" && value.length > 0;
169076
- }
169077
- function getToolContent(part) {
169078
- if (!isRecord(part))
169079
- return;
169080
- if (part.type === "tool" && isRecord(part.state)) {
169081
- return typeof part.state.output === "string" ? part.state.output : undefined;
169082
- }
169083
- if (part.type === "tool_result") {
169084
- return typeof part.content === "string" ? part.content : undefined;
169085
- }
169086
- return;
169087
- }
169088
- function setToolContent(part, content) {
169089
- if (!isRecord(part))
169090
- return;
169091
- if (part.type === "tool" && isRecord(part.state)) {
169092
- part.state.output = content;
169093
- return;
169094
- }
169095
- if (part.type === "tool_result") {
169096
- part.content = content;
169097
- }
169098
- }
169099
- function truncateToolPart(part) {
169100
- if (!isRecord(part))
169101
- return;
169102
- if (part.type === "tool" && isRecord(part.state)) {
169103
- const state = part.state;
169104
- state.output = "[truncated]";
169105
- if (isRecord(state.input)) {
169106
- const inputSize = estimateInputSize(state.input);
169107
- if (inputSize > 500) {
169108
- truncateInputValues(state.input);
169109
- }
169110
- }
169111
- return;
169112
- }
169113
- if (part.type === "tool_result") {
169114
- part.content = "[truncated]";
169115
- return;
169116
- }
169117
- if (part.type === "tool-invocation" && isRecord(part.args)) {
169118
- const inputSize = estimateInputSize(part.args);
169119
- if (inputSize > 500) {
169120
- truncateInputValues(part.args);
169121
- }
169122
- return;
169123
- }
169124
- if (part.type === "tool_use" && isRecord(part.input)) {
169125
- const inputSize = estimateInputSize(part.input);
169126
- if (inputSize > 500) {
169127
- truncateInputValues(part.input);
169128
- }
169129
- }
169130
- }
169131
- function estimateInputSize(input) {
169132
- try {
169133
- return JSON.stringify(input).length;
169134
- } catch {
169135
- return 0;
169136
- }
169137
- }
169138
- var TRUNCATION_SENTINEL = "...[truncated]";
169139
- function safeSlice(str, maxLen) {
169140
- if (str.length <= maxLen)
169141
- return str;
169142
- const lastCharCode = str.charCodeAt(maxLen - 1);
169143
- if (lastCharCode >= 55296 && lastCharCode <= 56319) {
169144
- return str.slice(0, maxLen - 1);
169145
- }
169146
- return str.slice(0, maxLen);
169147
- }
169148
- function truncateInputValues(input) {
169149
- for (const key of Object.keys(input)) {
169150
- const value = input[key];
169151
- if (typeof value === "string") {
169152
- if (value.endsWith(TRUNCATION_SENTINEL) || value === "[object]" || /^\[\d+ items\]$/.test(value))
169153
- continue;
169154
- input[key] = value.length > 5 ? `${safeSlice(value, 5)}${TRUNCATION_SENTINEL}` : value;
169155
- } else if (Array.isArray(value)) {
169156
- input[key] = `[${value.length} items]`;
169157
- } else if (value !== null && typeof value === "object") {
169158
- input[key] = "[object]";
169159
- }
169160
- }
169161
- }
169162
- function hasMeaningfulPart(part) {
169163
- if (!isRecord(part))
169164
- return false;
169165
- const type = part.type;
169166
- if (type === "text") {
169167
- return typeof part.text === "string" && part.text.trim().length > 0;
169168
- }
169169
- if (typeof type !== "string")
169170
- return false;
169171
- if (IGNORE_PART_TYPES.has(type))
169172
- return false;
169173
- return true;
169174
- }
169175
- function clearThinkingParts(thinkingParts) {
169176
- for (const part of thinkingParts) {
169177
- if (part.thinking !== undefined)
169178
- part.thinking = "[cleared]";
169179
- if (part.text !== undefined)
169180
- part.text = "[cleared]";
169181
- }
169182
- }
169183
- function extractToolCallObservation(part) {
169184
- if (!isRecord(part))
169185
- return null;
169186
- if (part.type === "tool" && isToolCallId(part.callID)) {
169187
- return { callId: part.callID, kind: "result" };
169991
+ if (!isRecord(part) || typeof part.type !== "string") {
169992
+ return false;
169188
169993
  }
169189
- if (part.type === "tool-invocation" && isToolCallId(part.callID)) {
169190
- return { callId: part.callID, kind: "invocation" };
169994
+ if (!STRUCTURAL_PART_TYPES.has(part.type)) {
169995
+ return false;
169191
169996
  }
169192
- if (part.type === "tool_use" && isToolCallId(part.id)) {
169193
- return { callId: part.id, kind: "invocation" };
169997
+ if (part.type === "reasoning" && typeof part.text === "string" && part.text !== "[cleared]") {
169998
+ return false;
169194
169999
  }
169195
- if (part.type === "tool_result" && isToolCallId(part.tool_use_id)) {
169196
- return { callId: part.tool_use_id, kind: "result" };
170000
+ return true;
170001
+ }
170002
+ function stripStructuralNoise(messages) {
170003
+ let strippedParts = 0;
170004
+ for (const message of messages) {
170005
+ if (!Array.isArray(message.parts)) {
170006
+ continue;
170007
+ }
170008
+ for (let i = 0;i < message.parts.length; i++) {
170009
+ const part = message.parts[i];
170010
+ if (isSentinel(part))
170011
+ continue;
170012
+ if (!isStructuralNoisePart(part))
170013
+ continue;
170014
+ message.parts[i] = makeSentinel(part);
170015
+ strippedParts++;
170016
+ }
169197
170017
  }
169198
- return null;
170018
+ return strippedParts;
169199
170019
  }
169200
- function isDropContent(content) {
169201
- return content.startsWith(DROP_PREFIX);
170020
+ // src/hooks/magic-context/tag-messages.ts
170021
+ init_storage_tags();
170022
+ await init_storage();
170023
+ // src/hooks/magic-context/drop-stale-reduce-calls.ts
170024
+ var STALE_TOOL_NAMES = new Set(["ctx_reduce"]);
170025
+ function isReduceToolPart(part) {
170026
+ if (!isRecord(part))
170027
+ return false;
170028
+ if (part.type === "tool" && typeof part.tool === "string" && STALE_TOOL_NAMES.has(part.tool))
170029
+ return true;
170030
+ if (part.type === "tool-invocation" && typeof part.toolName === "string" && STALE_TOOL_NAMES.has(part.toolName))
170031
+ return true;
170032
+ if (part.type === "tool_use" && typeof part.name === "string" && STALE_TOOL_NAMES.has(part.name))
170033
+ return true;
170034
+ return false;
169202
170035
  }
169203
-
169204
- class ToolMutationBatch {
169205
- partsToRemove = new Set;
169206
- affectedMessages = new Set;
169207
- messages;
169208
- constructor(messages) {
169209
- this.messages = messages;
169210
- }
169211
- markForRemoval(occurrence) {
169212
- this.partsToRemove.add(occurrence.part);
169213
- this.affectedMessages.add(occurrence.message);
170036
+ function hasAnyMeaningfulPart(parts) {
170037
+ for (const part of parts) {
170038
+ if (!isRecord(part))
170039
+ continue;
170040
+ if (part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0)
170041
+ return true;
170042
+ if (part.type === "thinking" || part.type === "reasoning" || part.type === "redacted_thinking")
170043
+ continue;
170044
+ if (part.type === "meta" || part.type === "step-start" || part.type === "step-finish")
170045
+ continue;
170046
+ if (part.type !== "tool" || !isReduceToolPart(part))
170047
+ return true;
169214
170048
  }
169215
- finalize() {
169216
- if (this.partsToRemove.size === 0)
169217
- return;
169218
- for (const message of this.affectedMessages) {
169219
- message.parts = message.parts.filter((p) => !this.partsToRemove.has(p));
170049
+ return false;
170050
+ }
170051
+ function dropStaleReduceCalls(messages, protectedCount = 0) {
170052
+ let didDrop = false;
170053
+ const protectedStart = messages.length - protectedCount;
170054
+ for (let i = 0;i < messages.length; i++) {
170055
+ if (i >= protectedStart)
170056
+ break;
170057
+ const message = messages[i];
170058
+ let touched = false;
170059
+ for (let j = 0;j < message.parts.length; j++) {
170060
+ const part = message.parts[j];
170061
+ if (isSentinel(part))
170062
+ continue;
170063
+ if (isReduceToolPart(part)) {
170064
+ message.parts[j] = makeSentinel(part);
170065
+ touched = true;
170066
+ }
169220
170067
  }
169221
- for (let i = this.messages.length - 1;i >= 0; i -= 1) {
169222
- if (!this.messages[i].parts.some(hasMeaningfulPart)) {
169223
- this.messages.splice(i, 1);
170068
+ if (touched) {
170069
+ didDrop = true;
170070
+ if (!hasAnyMeaningfulPart(message.parts)) {
170071
+ message.parts.length = 0;
170072
+ message.parts.push(makeSentinel(undefined));
169224
170073
  }
169225
170074
  }
169226
- this.partsToRemove.clear();
169227
- this.affectedMessages.clear();
169228
170075
  }
170076
+ return didDrop;
169229
170077
  }
169230
- function createToolDropTarget(callId, thinkingParts, index, batch) {
169231
- const drop = () => {
169232
- const entry = index.get(callId);
169233
- if (!entry || entry.occurrences.length === 0)
169234
- return "absent";
169235
- if (!entry.hasResult)
169236
- return "incomplete";
169237
- for (const occurrence of entry.occurrences) {
169238
- batch.markForRemoval(occurrence);
169239
- }
169240
- clearThinkingParts(thinkingParts);
169241
- index.delete(callId);
169242
- return "removed";
170078
+
170079
+ // src/hooks/magic-context/tag-messages.ts
170080
+ init_tag_content_primitives();
170081
+ await init_read_session_db();
170082
+
170083
+ // src/hooks/magic-context/tag-id-fallback.ts
170084
+ await init_storage();
170085
+ function parseScopedContentId(contentId) {
170086
+ const match = /^(.*):(p|file)(\d+)$/.exec(contentId);
170087
+ if (!match)
170088
+ return null;
170089
+ return {
170090
+ messageId: match[1],
170091
+ type: match[2] === "file" ? "file" : "message",
170092
+ partIndex: Number.parseInt(match[3], 10)
169243
170093
  };
169244
- const truncate = () => {
169245
- const entry = index.get(callId);
169246
- if (!entry || entry.occurrences.length === 0)
169247
- return "absent";
169248
- if (!entry.hasResult)
169249
- return "incomplete";
169250
- for (const occurrence of entry.occurrences) {
169251
- truncateToolPart(occurrence.part);
170094
+ }
170095
+ function createScopedAssignments(assignments) {
170096
+ const scoped = new Map;
170097
+ for (const [contentId, tagNumber] of assignments) {
170098
+ const parsed = parseScopedContentId(contentId);
170099
+ if (!parsed)
170100
+ continue;
170101
+ const entry = scoped.get(parsed.messageId) ?? { message: [], file: [] };
170102
+ entry[parsed.type].push({ tagNumber, contentId, partIndex: parsed.partIndex });
170103
+ scoped.set(parsed.messageId, entry);
170104
+ }
170105
+ for (const entry of scoped.values()) {
170106
+ entry.message.sort((left, right) => left.partIndex - right.partIndex);
170107
+ entry.file.sort((left, right) => left.partIndex - right.partIndex);
170108
+ }
170109
+ return scoped;
170110
+ }
170111
+ function createExistingTagResolver(sessionId, tagger, db) {
170112
+ const assignments = tagger.getAssignments(sessionId);
170113
+ let cachedAssignmentSize = -1;
170114
+ let cachedScopedAssignments = null;
170115
+ const usedTagNumbers = new Set;
170116
+ function getScopedAssignments() {
170117
+ if (!cachedScopedAssignments || cachedAssignmentSize !== assignments.size) {
170118
+ cachedScopedAssignments = createScopedAssignments(assignments);
170119
+ cachedAssignmentSize = assignments.size;
169252
170120
  }
169253
- clearThinkingParts(thinkingParts);
169254
- return "truncated";
169255
- };
170121
+ return cachedScopedAssignments;
170122
+ }
169256
170123
  return {
169257
- setContent: (content) => {
169258
- if (isDropContent(content)) {
169259
- drop();
169260
- return true;
170124
+ resolve(messageId, type, currentContentId, ordinal) {
170125
+ const exactTagId = assignments.get(currentContentId);
170126
+ if (exactTagId !== undefined) {
170127
+ usedTagNumbers.add(exactTagId);
170128
+ return exactTagId;
169261
170129
  }
169262
- const entry = index.get(callId);
169263
- if (!entry)
169264
- return false;
169265
- let changed = false;
169266
- for (const occurrence of entry.occurrences) {
169267
- if (occurrence.kind !== "result")
169268
- continue;
169269
- const prevContent = getToolContent(occurrence.part);
169270
- if (prevContent !== content) {
169271
- setToolContent(occurrence.part, content);
169272
- changed = true;
169273
- }
170130
+ const fallback = getScopedAssignments().get(messageId)?.[type][ordinal];
170131
+ if (!fallback || usedTagNumbers.has(fallback.tagNumber)) {
170132
+ return;
169274
170133
  }
169275
- return changed;
169276
- },
169277
- drop,
169278
- truncate
170134
+ updateTagMessageId(db, sessionId, fallback.tagNumber, currentContentId);
170135
+ tagger.bindTag(sessionId, currentContentId, fallback.tagNumber);
170136
+ usedTagNumbers.add(fallback.tagNumber);
170137
+ return fallback.tagNumber;
170138
+ }
169279
170139
  };
169280
170140
  }
169281
170141
 
169282
170142
  // src/hooks/magic-context/tag-messages.ts
170143
+ init_tag_part_guards();
170144
+ init_tool_drop_target();
170145
+ function deriveToolOwnerMessageId(sessionId, db, message, obs, unpaired) {
170146
+ const messageId = typeof message.info.id === "string" ? message.info.id : "";
170147
+ if (obs.kind === "invocation") {
170148
+ if (messageId) {
170149
+ const queue4 = unpaired.get(obs.callId) ?? [];
170150
+ queue4.push(messageId);
170151
+ unpaired.set(obs.callId, queue4);
170152
+ return messageId;
170153
+ }
170154
+ return obs.callId;
170155
+ }
170156
+ const queue3 = unpaired.get(obs.callId);
170157
+ if (queue3 && queue3.length > 0) {
170158
+ const popped = queue3.shift();
170159
+ if (queue3.length === 0)
170160
+ unpaired.delete(obs.callId);
170161
+ if (popped !== undefined)
170162
+ return popped;
170163
+ }
170164
+ if (messageId) {
170165
+ const candidates = getCandidateToolOwners(db, sessionId, obs.callId);
170166
+ if (candidates.length > 0) {
170167
+ const ids = [...candidates, messageId];
170168
+ const times = getMessageTimesFromOpenCodeDb(sessionId, ids);
170169
+ const persisted = pickNearestPriorOwner(candidates, messageId, times);
170170
+ if (persisted !== null)
170171
+ return persisted;
170172
+ }
170173
+ return messageId;
170174
+ }
170175
+ return obs.callId;
170176
+ }
169283
170177
  function collectRelevantSourceTagIds(messages, assignments) {
169284
170178
  const currentMessageIds = new Set(messages.flatMap((message) => typeof message.info.id === "string" ? [message.info.id] : []));
169285
170179
  const relevantTagIds = new Set;
@@ -169330,6 +170224,8 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
169330
170224
  const toolTagByCallId = new Map;
169331
170225
  const toolThinkingByCallId = new Map;
169332
170226
  const toolCallIndex = new Map;
170227
+ const unpairedInvocations = new Map;
170228
+ const ownerByPartKey = new Map;
169333
170229
  const batch = new ToolMutationBatch(messages);
169334
170230
  const assignments = tagger.getAssignments(sessionId);
169335
170231
  const resolver = createExistingTagResolver(sessionId, tagger, db);
@@ -169360,22 +170256,37 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
169360
170256
  }
169361
170257
  const toolObservation = extractToolCallObservation(part);
169362
170258
  if (toolObservation) {
169363
- const entry = toolCallIndex.get(toolObservation.callId) ?? {
170259
+ const ownerMsgId = deriveToolOwnerMessageId(sessionId, db, message, toolObservation, unpairedInvocations);
170260
+ const compositeKey = makeToolCompositeKey(ownerMsgId, toolObservation.callId);
170261
+ const entry = toolCallIndex.get(compositeKey) ?? {
169364
170262
  occurrences: [],
169365
170263
  hasResult: false
169366
170264
  };
169367
170265
  entry.occurrences.push({ message, part, kind: toolObservation.kind });
169368
170266
  if (toolObservation.kind === "result")
169369
170267
  entry.hasResult = true;
169370
- toolCallIndex.set(toolObservation.callId, entry);
169371
- const existingTagId = tagger.getTag(sessionId, toolObservation.callId);
170268
+ toolCallIndex.set(compositeKey, entry);
170269
+ let existingTagId = tagger.getToolTag(sessionId, toolObservation.callId, ownerMsgId);
170270
+ if (existingTagId === undefined) {
170271
+ const orphan = getNullOwnerToolTag(db, sessionId, toolObservation.callId);
170272
+ if (orphan !== null) {
170273
+ const claimed = adoptNullOwnerToolTag(db, orphan.id, ownerMsgId);
170274
+ if (claimed) {
170275
+ tagger.bindToolTag(sessionId, toolObservation.callId, ownerMsgId, orphan.tagNumber);
170276
+ existingTagId = orphan.tagNumber;
170277
+ } else {
170278
+ existingTagId = tagger.getToolTag(sessionId, toolObservation.callId, ownerMsgId);
170279
+ }
170280
+ }
170281
+ }
169372
170282
  if (existingTagId !== undefined) {
169373
- toolTagByCallId.set(toolObservation.callId, existingTagId);
170283
+ toolTagByCallId.set(compositeKey, existingTagId);
169374
170284
  messageTagNumbers.set(message, Math.max(messageTagNumbers.get(message) ?? 0, existingTagId));
169375
- if (message.info.role === "tool" && precedingThinkingParts.length > 0 && !toolThinkingByCallId.has(toolObservation.callId)) {
169376
- toolThinkingByCallId.set(toolObservation.callId, precedingThinkingParts);
170285
+ if (message.info.role === "tool" && precedingThinkingParts.length > 0 && !toolThinkingByCallId.has(compositeKey)) {
170286
+ toolThinkingByCallId.set(compositeKey, precedingThinkingParts);
169377
170287
  }
169378
170288
  }
170289
+ ownerByPartKey.set(part, { ownerMsgId, callId: toolObservation.callId });
169379
170290
  }
169380
170291
  if (messageId && isTextPart(part)) {
169381
170292
  const textPart = part;
@@ -169421,14 +170332,17 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
169421
170332
  const thinkingParts = precedingThinkingParts;
169422
170333
  const reasoningBytes = getReasoningByteSize(thinkingParts);
169423
170334
  const { toolName, inputByteSize } = extractToolTagMetadata(toolPart);
169424
- const tagId = tagger.assignTag(sessionId, toolPart.callID, "tool", byteSize(toolPart.state.output), db, reasoningBytes, toolName, inputByteSize);
170335
+ const memo = ownerByPartKey.get(part);
170336
+ const ownerMsgId = memo?.ownerMsgId ?? messageId ?? toolPart.callID;
170337
+ const compositeKey = makeToolCompositeKey(ownerMsgId, toolPart.callID);
170338
+ const tagId = tagger.assignToolTag(sessionId, toolPart.callID, ownerMsgId, byteSize(toolPart.state.output), db, reasoningBytes, toolName, inputByteSize);
169425
170339
  messageTagNumbers.set(message, Math.max(messageTagNumbers.get(message) ?? 0, tagId));
169426
170340
  if (!skipPrefixInjection) {
169427
170341
  toolPart.state.output = prependTag(tagId, toolPart.state.output);
169428
170342
  }
169429
- toolTagByCallId.set(toolPart.callID, tagId);
169430
- if (thinkingParts.length > 0 && !toolThinkingByCallId.has(toolPart.callID)) {
169431
- toolThinkingByCallId.set(toolPart.callID, thinkingParts);
170343
+ toolTagByCallId.set(compositeKey, tagId);
170344
+ if (thinkingParts.length > 0 && !toolThinkingByCallId.has(compositeKey)) {
170345
+ toolThinkingByCallId.set(compositeKey, thinkingParts);
169432
170346
  }
169433
170347
  }
169434
170348
  if (messageId && isFilePart(part)) {
@@ -169476,9 +170390,9 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
169476
170390
  }
169477
170391
  }
169478
170392
  }
169479
- for (const [callId, tagId] of toolTagByCallId) {
169480
- const thinkingParts = toolThinkingByCallId.get(callId) ?? [];
169481
- targets.set(tagId, createToolDropTarget(callId, thinkingParts, toolCallIndex, batch));
170393
+ for (const [compositeKey, tagId] of toolTagByCallId) {
170394
+ const thinkingParts = toolThinkingByCallId.get(compositeKey) ?? [];
170395
+ targets.set(tagId, createToolDropTarget(compositeKey, thinkingParts, toolCallIndex, batch));
169482
170396
  }
169483
170397
  const hasRecentReduceCall = lastReduceMessageIndex >= 0 && messages.length - lastReduceMessageIndex <= RECENT_REDUCE_LOOKBACK;
169484
170398
  return {
@@ -169639,11 +170553,9 @@ function applyContextNudge(messages, nudge, nudgePlacements, sessionId) {
169639
170553
 
169640
170554
  // src/features/magic-context/search.ts
169641
170555
  init_logger();
169642
- await init_read_session_chunk();
169643
170556
  init_memory();
169644
170557
  init_embedding();
169645
170558
  init_storage_memory_fts();
169646
- await init_message_index();
169647
170559
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
169648
170560
  var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
169649
170561
  var SEMANTIC_WEIGHT = 0.7;
@@ -169693,11 +170605,7 @@ function getMessageOrdinal(value) {
169693
170605
  }
169694
170606
  async function getSemanticScores(args) {
169695
170607
  const semanticScores = new Map;
169696
- if (!args.embeddingEnabled || !args.isEmbeddingRuntimeEnabled() || args.memories.length === 0) {
169697
- return semanticScores;
169698
- }
169699
- const queryEmbedding = await args.embedQuery(args.query, args.signal);
169700
- if (!queryEmbedding) {
170608
+ if (!args.queryEmbedding || args.memories.length === 0) {
169701
170609
  return semanticScores;
169702
170610
  }
169703
170611
  const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
@@ -169711,7 +170619,7 @@ async function getSemanticScores(args) {
169711
170619
  if (!memoryEmbedding) {
169712
170620
  continue;
169713
170621
  }
169714
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(queryEmbedding, memoryEmbedding)));
170622
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
169715
170623
  }
169716
170624
  return semanticScores;
169717
170625
  }
@@ -169807,12 +170715,8 @@ async function searchMemories(args) {
169807
170715
  const semanticScores = await getSemanticScores({
169808
170716
  db: args.db,
169809
170717
  projectPath: args.projectPath,
169810
- query: args.query,
169811
170718
  memories: semanticCandidates,
169812
- embeddingEnabled: args.embeddingEnabled,
169813
- embedQuery: args.embedQuery,
169814
- isEmbeddingRuntimeEnabled: args.isEmbeddingRuntimeEnabled,
169815
- signal: args.signal
170719
+ queryEmbedding: args.queryEmbedding
169816
170720
  });
169817
170721
  return mergeMemoryResults({
169818
170722
  memories,
@@ -169828,7 +170732,6 @@ function linearDecayScore(rank, total) {
169828
170732
  return Math.max(0, 1 - rank / total);
169829
170733
  }
169830
170734
  function searchMessages(args) {
169831
- ensureMessagesIndexed(args.db, args.sessionId, args.readMessages);
169832
170735
  const sanitizedQuery = sanitizeFtsQuery(args.query.trim());
169833
170736
  if (sanitizedQuery.length === 0) {
169834
170737
  return [];
@@ -169899,20 +170802,12 @@ function toGitCommitResult(hit) {
169899
170802
  matchType: hit.matchType
169900
170803
  };
169901
170804
  }
169902
- async function searchGitCommitsAsync(args) {
170805
+ function searchGitCommits(args) {
169903
170806
  if (args.limit <= 0)
169904
170807
  return [];
169905
- let queryEmbedding = null;
169906
- if (args.embeddingEnabled && args.isEmbeddingRuntimeEnabled()) {
169907
- try {
169908
- queryEmbedding = await args.embedQuery(args.query, args.signal);
169909
- } catch (error48) {
169910
- log(`[search] git commit query embedding failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
169911
- }
169912
- }
169913
170808
  const hits = searchGitCommitsSync(args.db, args.projectPath, args.query, {
169914
170809
  limit: args.limit,
169915
- queryEmbedding
170810
+ queryEmbedding: args.queryEmbedding
169916
170811
  });
169917
170812
  return hits.map(toGitCommitResult);
169918
170813
  }
@@ -169943,37 +170838,37 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
169943
170838
  const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
169944
170839
  const runMessages = activeSources.has("message");
169945
170840
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
169946
- const [memoryResults, messageResults, gitCommitResults] = await Promise.all([
170841
+ const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
170842
+ const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error48) => {
170843
+ log(`[search] query embedding failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
170844
+ return null;
170845
+ }) : Promise.resolve(null);
170846
+ await Promise.resolve();
170847
+ const messageResults = runMessages ? searchMessages({
170848
+ db,
170849
+ sessionId,
170850
+ query: trimmedQuery,
170851
+ limit: tierLimit,
170852
+ maxOrdinal: options.maxMessageOrdinal
170853
+ }) : [];
170854
+ const queryEmbedding = await queryEmbeddingPromise;
170855
+ const [memoryResults, gitCommitResults] = await Promise.all([
169947
170856
  runMemory ? searchMemories({
169948
170857
  db,
169949
170858
  projectPath,
169950
170859
  query: trimmedQuery,
169951
170860
  limit: tierLimit,
169952
170861
  memoryEnabled: true,
169953
- embeddingEnabled,
169954
- embedQuery,
169955
- isEmbeddingRuntimeEnabled,
169956
- visibleMemoryIds: options.visibleMemoryIds,
169957
- signal: options.signal
170862
+ queryEmbedding,
170863
+ visibleMemoryIds: options.visibleMemoryIds
169958
170864
  }) : Promise.resolve([]),
169959
- runMessages ? Promise.resolve(searchMessages({
169960
- db,
169961
- sessionId,
169962
- query: trimmedQuery,
169963
- limit: tierLimit,
169964
- readMessages: options.readMessages ?? readRawSessionMessages,
169965
- maxOrdinal: options.maxMessageOrdinal
169966
- })) : Promise.resolve([]),
169967
- runGitCommits ? searchGitCommitsAsync({
170865
+ runGitCommits ? Promise.resolve(searchGitCommits({
169968
170866
  db,
169969
170867
  projectPath,
169970
170868
  query: trimmedQuery,
169971
170869
  limit: tierLimit,
169972
- embeddingEnabled,
169973
- embedQuery,
169974
- isEmbeddingRuntimeEnabled,
169975
- signal: options.signal
169976
- }) : Promise.resolve([])
170870
+ queryEmbedding
170871
+ })) : Promise.resolve([])
169977
170872
  ]);
169978
170873
  const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
169979
170874
  const countRetrievals = options.countRetrievals ?? true;
@@ -170111,8 +171006,31 @@ function collectUserPromptParts(message) {
170111
171006
  function hasStackedAugmentation(rawText) {
170112
171007
  return rawText.includes("<sidekick-augmentation>") || rawText.includes("<ctx-search-hint>") || rawText.includes("<ctx-search-auto>");
170113
171008
  }
171009
+ function stripNestedSystemReminders(text) {
171010
+ const OPEN = "<system-reminder>";
171011
+ const CLOSE = "</system-reminder>";
171012
+ let result = "";
171013
+ let depth = 0;
171014
+ let i = 0;
171015
+ while (i < text.length) {
171016
+ if (text.startsWith(OPEN, i)) {
171017
+ depth += 1;
171018
+ i += OPEN.length;
171019
+ } else if (text.startsWith(CLOSE, i)) {
171020
+ if (depth > 0)
171021
+ depth -= 1;
171022
+ i += CLOSE.length;
171023
+ } else if (depth === 0) {
171024
+ result += text[i];
171025
+ i += 1;
171026
+ } else {
171027
+ i += 1;
171028
+ }
171029
+ }
171030
+ return result;
171031
+ }
170114
171032
  function extractUserPromptText(message) {
170115
- return collectUserPromptParts(message).replace(/§\d+§\s*/g, "").replace(/<!--\s*\+[\d\s.hmdw]+\s*-->/g, "").replace(/<!--\s*OMO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").replace(/<ctx-search-hint>[\s\S]*?<\/ctx-search-hint>/g, "").replace(/<ctx-search-auto>[\s\S]*?<\/ctx-search-auto>/g, "").replace(/<instruction[^>]*>[\s\S]*?<\/instruction>/g, "").replace(/<sidekick-augmentation>[\s\S]*?<\/sidekick-augmentation>/g, "").replace(/[ \t]+\n/g, `
171033
+ return stripNestedSystemReminders(collectUserPromptParts(message)).replace(/§\d+§\s*/g, "").replace(/<!--\s*\+[\d\s.hmdw]+\s*-->/g, "").replace(/<!--\s*OMO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<!--\s*ALFONSO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<ctx-search-hint>[\s\S]*?<\/ctx-search-hint>/g, "").replace(/<ctx-search-auto>[\s\S]*?<\/ctx-search-auto>/g, "").replace(/<instruction[^>]*>[\s\S]*?<\/instruction>/g, "").replace(/<sidekick-augmentation>[\s\S]*?<\/sidekick-augmentation>/g, "").replace(/[ \t]+\n/g, `
170116
171034
  `).replace(/\n{3,}/g, `
170117
171035
 
170118
171036
  `).trim();
@@ -170268,8 +171186,8 @@ var DEDUP_SAFE_TOOLS = new Set([
170268
171186
  "mcp_lsp_prepare_rename"
170269
171187
  ]);
170270
171188
  function applyHeuristicCleanup(sessionId, db, targets, messageTagNumbers, config2, preloadedTags) {
170271
- const tags = preloadedTags ?? getTagsBySession(db, sessionId);
170272
- const maxTag = tags.reduce((max, t) => Math.max(max, t.tagNumber), 0);
171189
+ const tags = preloadedTags ?? getActiveTagsBySession(db, sessionId);
171190
+ const maxTag = getMaxTagNumberBySession(db, sessionId);
170273
171191
  const toolAgeCutoff = maxTag - config2.autoDropToolAge;
170274
171192
  const protectedCutoff = maxTag - config2.protectedTags;
170275
171193
  let droppedTools = 0;
@@ -170334,15 +171252,16 @@ function applyHeuristicCleanup(sessionId, db, targets, messageTagNumbers, config
170334
171252
  const allMessages = Array.from(messageTagNumbers.keys());
170335
171253
  const toolFingerprints = buildToolFingerprints(allMessages);
170336
171254
  if (toolFingerprints.size > 0) {
170337
- const tagsByMessageId = new Map;
171255
+ const tagsByCompositeKey = new Map;
170338
171256
  for (const tag of tags) {
170339
171257
  if (tag.type === "tool" && tag.status === "active" && tag.messageId) {
170340
- tagsByMessageId.set(tag.messageId, tag);
171258
+ const key = tag.toolOwnerMessageId ? `${tag.toolOwnerMessageId}\x00${tag.messageId}` : tag.messageId;
171259
+ tagsByCompositeKey.set(key, tag);
170341
171260
  }
170342
171261
  }
170343
171262
  const fingerprintGroups = new Map;
170344
- for (const [messageId, fingerprint] of toolFingerprints) {
170345
- const tag = tagsByMessageId.get(messageId);
171263
+ for (const [compositeKey, fingerprint] of toolFingerprints) {
171264
+ const tag = tagsByCompositeKey.get(compositeKey);
170346
171265
  if (!tag || tag.tagNumber > protectedCutoff)
170347
171266
  continue;
170348
171267
  const group = fingerprintGroups.get(fingerprint) ?? [];
@@ -170399,6 +171318,9 @@ function buildToolFingerprints(messages) {
170399
171318
  for (const message of messages) {
170400
171319
  if (message.info.role !== "assistant")
170401
171320
  continue;
171321
+ const ownerMsgId = typeof message.info.id === "string" ? message.info.id : null;
171322
+ if (!ownerMsgId)
171323
+ continue;
170402
171324
  for (const part of message.parts) {
170403
171325
  const record2 = part;
170404
171326
  const info = extractToolInfo(record2);
@@ -170408,8 +171330,9 @@ function buildToolFingerprints(messages) {
170408
171330
  if (!callId)
170409
171331
  continue;
170410
171332
  try {
170411
- const fingerprint = `${info.toolName}:${JSON.stringify(info.args)}`;
170412
- fingerprints.set(callId, fingerprint);
171333
+ const fingerprint = `${ownerMsgId}:${info.toolName}:${JSON.stringify(info.args)}`;
171334
+ const compositeKey = `${ownerMsgId}\x00${callId}`;
171335
+ fingerprints.set(compositeKey, fingerprint);
170413
171336
  } catch {}
170414
171337
  }
170415
171338
  }
@@ -170515,7 +171438,7 @@ async function runPostTransformPhase(args) {
170515
171438
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
170516
171439
  const pendingCountBefore = pendingOps.length;
170517
171440
  const tApply = performance.now();
170518
- didMutateFromPendingOperations = applyPendingOperations(args.sessionId, args.db, args.targets, args.protectedTags, args.tags, pendingOps);
171441
+ didMutateFromPendingOperations = applyPendingOperations(args.sessionId, args.db, args.targets, args.protectedTags, undefined, pendingOps);
170519
171442
  const pendingCountAfter = getPendingOps(args.db, args.sessionId).length;
170520
171443
  if (pendingCountBefore > 0 && pendingCountAfter === 0) {
170521
171444
  clearPersistedStickyTurnReminder(args.db, args.sessionId);
@@ -170546,8 +171469,8 @@ async function runPostTransformPhase(args) {
170546
171469
  logTransformTiming(args.sessionId, "watermarkCleanup", t6);
170547
171470
  }
170548
171471
  const t7 = performance.now();
170549
- const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge, args.skipTypedReasoningCleanup);
170550
- stripClearedReasoning(args.messages, args.skipTypedReasoningCleanup);
171472
+ const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
171473
+ stripClearedReasoning(args.messages);
170551
171474
  const strippedInline = stripInlineThinking(args.messages, args.messageTagNumbers, args.clearReasoningAge);
170552
171475
  if (clearedReasoning > 0 || strippedInline > 0) {
170553
171476
  let maxTag = 0;
@@ -170817,6 +171740,9 @@ function createTransform(deps) {
170817
171740
  const resolvedSessionId = sessionId;
170818
171741
  logTransformTiming(sessionId, "findSessionId", startTime, `messages=${messages.length}`);
170819
171742
  const db = deps.db;
171743
+ if (deps.client !== undefined) {
171744
+ scheduleReconciliation(db, sessionId, readRawSessionMessages);
171745
+ }
170820
171746
  const tUserMsg = performance.now();
170821
171747
  const currentTurnId = findLastUserMessageId(messages);
170822
171748
  logTransformTiming(sessionId, "findLastUserMessageId", tUserMsg);
@@ -171038,13 +171964,17 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
171038
171964
  }
171039
171965
  }
171040
171966
  const t1 = performance.now();
171041
- const tags = getTagsBySession(db, sessionId);
171042
- logTransformTiming(sessionId, "getTagsBySession", t1, `count=${tags.length}`);
171967
+ const activeTags = getActiveTagsBySession(db, sessionId);
171968
+ logTransformTiming(sessionId, "getActiveTagsBySession", t1, `count=${activeTags.length}`);
171969
+ const t1b = performance.now();
171970
+ const targetTagNumbers = [...targets.keys()];
171971
+ const targetsSliceTags = getTagsByNumbers(db, sessionId, targetTagNumbers);
171972
+ logTransformTiming(sessionId, "getTagsByNumbers", t1b, `targets=${targetTagNumbers.length} fetched=${targetsSliceTags.length}`);
171043
171973
  let didMutateFromFlushedStatuses = false;
171044
171974
  if (taggingSucceeded) {
171045
171975
  try {
171046
171976
  const t2 = performance.now();
171047
- didMutateFromFlushedStatuses = applyFlushedStatuses(sessionId, db, targets, tags);
171977
+ didMutateFromFlushedStatuses = applyFlushedStatuses(sessionId, db, targets, targetsSliceTags);
171048
171978
  logTransformTiming(sessionId, "applyFlushedStatuses", t2);
171049
171979
  batch?.finalize();
171050
171980
  logTransformTiming(sessionId, "batchFinalize:flushed", t2);
@@ -171060,12 +171990,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
171060
171990
  const t3 = performance.now();
171061
171991
  const strippedStructuralNoise = stripStructuralNoise(messages);
171062
171992
  logTransformTiming(sessionId, "stripStructuralNoise", t3, `strippedParts=${strippedStructuralNoise}`);
171063
- const currentSessionModel = deps.liveModelBySession?.get(sessionId) ?? findLastAssistantModel(messages) ?? undefined;
171064
- const skipTypedReasoningCleanup = modelRequiresInterleavedReasoning(currentSessionModel);
171065
171993
  const persistedReasoningWatermark = sessionMeta?.clearedReasoningThroughTag ?? 0;
171066
171994
  if (persistedReasoningWatermark > 0) {
171067
171995
  const tReplay = performance.now();
171068
- const replayed = replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedReasoningWatermark, skipTypedReasoningCleanup);
171996
+ const replayed = replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedReasoningWatermark);
171069
171997
  const replayedInline = replayStrippedInlineThinking(messages, messageTagNumbers, persistedReasoningWatermark);
171070
171998
  if (replayed > 0 || replayedInline > 0) {
171071
171999
  sessionLog(sessionId, `reasoning replay: cleared=${replayed} inlineStripped=${replayedInline} (watermark=${persistedReasoningWatermark})`);
@@ -171074,27 +172002,22 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
171074
172002
  }
171075
172003
  if (!deps.ctxReduceEnabled && !reducedMode && deps.cavemanTextCompression?.enabled) {
171076
172004
  const tCavemanReplay = performance.now();
171077
- const replayedCaveman = replayCavemanCompression(sessionId, db, targets, tags);
172005
+ const replayedCaveman = replayCavemanCompression(sessionId, db, targets, targetsSliceTags);
171078
172006
  if (replayedCaveman > 0) {
171079
172007
  sessionLog(sessionId, `caveman replay: re-applied ${replayedCaveman} text tags`);
171080
172008
  }
171081
172009
  logTransformTiming(sessionId, "replayCavemanCompression", tCavemanReplay);
171082
172010
  }
171083
172011
  const t4 = performance.now();
171084
- const strippedClearedReasoning = stripClearedReasoning(messages, skipTypedReasoningCleanup);
172012
+ const strippedClearedReasoning = stripClearedReasoning(messages);
171085
172013
  logTransformTiming(sessionId, "stripClearedReasoning", t4, `strippedParts=${strippedClearedReasoning}`);
171086
172014
  const tMergeStrip = performance.now();
171087
- const strippedMergedReasoning = skipTypedReasoningCleanup ? 0 : stripReasoningFromMergedAssistants(messages);
172015
+ const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages);
171088
172016
  if (strippedMergedReasoning > 0) {
171089
172017
  sessionLog(sessionId, `stripped ${strippedMergedReasoning} reasoning parts from merged assistants (anthropic groupIntoBlocks workaround)`);
171090
172018
  }
171091
172019
  logTransformTiming(sessionId, "stripReasoningFromMergedAssistants", tMergeStrip, `strippedParts=${strippedMergedReasoning}`);
171092
- let watermark = 0;
171093
- for (const tag of tags) {
171094
- if (tag.status === "dropped" && tag.tagNumber > watermark) {
171095
- watermark = tag.tagNumber;
171096
- }
171097
- }
172020
+ const watermark = getMaxDroppedTagNumber(db, sessionId);
171098
172021
  const contextUsage = contextUsageEarly;
171099
172022
  const schedulerDecision = schedulerDecisionEarly;
171100
172023
  const rawGetNotifParams = deps.getNotificationParams;
@@ -171144,7 +172067,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
171144
172067
  sessionId,
171145
172068
  db,
171146
172069
  messages,
171147
- tags,
172070
+ tags: activeTags,
171148
172071
  targets,
171149
172072
  reasoningByMessage,
171150
172073
  messageTagNumbers,
@@ -171170,7 +172093,6 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
171170
172093
  watermark,
171171
172094
  forceMaterializationPercentage: FORCE_MATERIALIZE_PERCENTAGE,
171172
172095
  hasRecentReduceCall,
171173
- skipTypedReasoningCleanup,
171174
172096
  projectPath: deps.projectPath,
171175
172097
  autoSearch: deps.autoSearch,
171176
172098
  cavemanTextCompression: deps.ctxReduceEnabled === false && !reducedMode ? deps.cavemanTextCompression : undefined
@@ -171539,6 +172461,7 @@ function createEventHandler2(deps) {
171539
172461
  sessionLog(info.sessionID, `event message.removed: invalidating state for message ${info.messageID}`);
171540
172462
  try {
171541
172463
  const cleanup = cleanupRemovedMessageState(deps, info.sessionID, info.messageID);
172464
+ scheduleClearAndReindex(deps.db, info.sessionID, readRawSessionMessages);
171542
172465
  deps.tagger.cleanup(info.sessionID);
171543
172466
  sessionLog(info.sessionID, "event message.removed: invalidated tagger session cache");
171544
172467
  if (cleanup.clearedNudgePlacement) {
@@ -171657,7 +172580,7 @@ function createNudger(config2) {
171657
172580
  }
171658
172581
  const largest = formatLargestTags(topNFn(db, sessionId, 3));
171659
172582
  const protectedCount = config2.protected_tags;
171660
- const activeTags = (preloadedTags ?? getTagsBySession(db, sessionId)).filter((t) => t.status === "active");
172583
+ const activeTags = preloadedTags ? preloadedTags.filter((t) => t.status === "active") : getActiveTagsBySession(db, sessionId);
171661
172584
  const highestProtected = activeTags.map((t) => t.tagNumber).sort((a, b) => b - a).slice(0, protectedCount)[0];
171662
172585
  const protectedHint = highestProtected ? ` Tags §${highestProtected}§ and above are protected (last ${protectedCount}) — You MUST NOT try to reduce those.` : "";
171663
172586
  const oldToolHint = formatOldToolTags(activeTags, protectedCount, 5);
@@ -171748,7 +172671,7 @@ function estimateProjectedPercentage(db, sessionId, contextUsage, preloadedTags)
171748
172671
  if (pendingDrops.length === 0) {
171749
172672
  return null;
171750
172673
  }
171751
- const activeTags = (preloadedTags ?? getTagsBySession(db, sessionId)).filter((t) => t.status === "active");
172674
+ const activeTags = preloadedTags ? preloadedTags.filter((t) => t.status === "active") : getActiveTagsBySession(db, sessionId);
171752
172675
  const totalActiveBytes = activeTags.reduce((sum, t) => sum + t.byteSize, 0);
171753
172676
  if (totalActiveBytes === 0) {
171754
172677
  return null;
@@ -171821,6 +172744,7 @@ function clearSidebarSnapshotCache(sessionId) {
171821
172744
  // src/hooks/magic-context/hook-handlers.ts
171822
172745
  init_logger();
171823
172746
  init_note_nudger();
172747
+ await init_read_session_chunk();
171824
172748
  var TOOL_HEAVY_TURN_REMINDER_THRESHOLD = 5;
171825
172749
  var TOOL_HEAVY_TURN_REMINDER_TEXT = `
171826
172750
 
@@ -171873,6 +172797,14 @@ function createEventHook(args) {
171873
172797
  return async (input) => {
171874
172798
  await args.eventHandler(input);
171875
172799
  if (input.event.type === "message.updated") {
172800
+ const messageInfo = getMessageUpdatedInfo(input.event.properties);
172801
+ if (messageInfo?.messageID) {
172802
+ const isTerminalUser = messageInfo.role === "user";
172803
+ const isTerminalAssistant = messageInfo.role === "assistant" && (typeof messageInfo.completedAt === "number" || typeof messageInfo.finish === "string");
172804
+ if (isTerminalUser || isTerminalAssistant) {
172805
+ scheduleIncrementalIndex(args.db, messageInfo.sessionID, messageInfo.messageID, readRawSessionMessageById);
172806
+ }
172807
+ }
171876
172808
  const assistantInfo = getMessageUpdatedAssistantInfo(input.event.properties);
171877
172809
  if (assistantInfo?.providerID && assistantInfo?.modelID) {
171878
172810
  const previous = args.liveModelBySession.get(assistantInfo.sessionID);
@@ -171894,6 +172826,9 @@ function createEventHook(args) {
171894
172826
  const sessionId = resolveSessionId(properties);
171895
172827
  if (!sessionId)
171896
172828
  return;
172829
+ if (input.event.type !== "session.deleted") {
172830
+ scheduleReconciliation(args.db, sessionId, readRawSessionMessages);
172831
+ }
171897
172832
  if (input.event.type === "session.deleted") {
171898
172833
  args.liveModelBySession.delete(sessionId);
171899
172834
  args.variantBySession.delete(sessionId);
@@ -171909,6 +172844,7 @@ function createEventHook(args) {
171909
172844
  clearNoteNudgeState(args.db, sessionId);
171910
172845
  clearAutoSearchForSession(sessionId);
171911
172846
  clearSidebarSnapshotCache(sessionId);
172847
+ clearSessionTracking(sessionId);
171912
172848
  }
171913
172849
  };
171914
172850
  }
@@ -171956,8 +172892,8 @@ init_send_session_notification();
171956
172892
 
171957
172893
  // src/hooks/magic-context/system-prompt-hash.ts
171958
172894
  import { createHash as createHash4 } from "node:crypto";
171959
- import { existsSync as existsSync11, readFileSync as readFileSync8, realpathSync } from "node:fs";
171960
- import { join as join20, resolve as resolve4, sep } from "node:path";
172895
+ import { existsSync as existsSync12, readFileSync as readFileSync8, realpathSync } from "node:fs";
172896
+ import { join as join21, resolve as resolve4, sep } from "node:path";
171961
172897
 
171962
172898
  // src/agents/magic-context-prompt.ts
171963
172899
  function getToolHistoryGuidance(dropToolStructure) {
@@ -172070,9 +173006,9 @@ var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
172070
173006
  function readProjectDocs(directory) {
172071
173007
  const sections = [];
172072
173008
  for (const filename of DOC_FILES) {
172073
- const filePath = join20(directory, filename);
173009
+ const filePath = join21(directory, filename);
172074
173010
  try {
172075
- if (existsSync11(filePath)) {
173011
+ if (existsSync12(filePath)) {
172076
173012
  const content = readFileSync8(filePath, "utf-8").trim();
172077
173013
  if (content.length > 0) {
172078
173014
  sections.push(`<${filename}>
@@ -172185,7 +173121,7 @@ ${items}
172185
173121
  log(`[magic-context] key file path escapes project root, skipping: ${entry.filePath}`);
172186
173122
  continue;
172187
173123
  }
172188
- if (!existsSync11(absPath))
173124
+ if (!existsSync12(absPath))
172189
173125
  continue;
172190
173126
  let realPath;
172191
173127
  try {
@@ -172730,8 +173666,14 @@ function truncateError(name2, code, message, maxLen = 240) {
172730
173666
 
172731
173667
  // src/plugin/rpc-handlers.ts
172732
173668
  init_project_identity();
173669
+ init_storage_memory();
173670
+ init_tool_definition_tokens();
172733
173671
  await init_storage();
172734
173672
  init_read_session_formatting();
173673
+ await __promiseAll([
173674
+ init_inject_compartments(),
173675
+ init_read_session_db()
173676
+ ]);
172735
173677
 
172736
173678
  // src/hooks/magic-context/tokenizer-calibration.ts
172737
173679
  var CALIBRATION_TABLE = [
@@ -172938,7 +173880,7 @@ function resolveConfigValue(cfg, key, modelKey, defaultValue) {
172938
173880
  }
172939
173881
  return defaultValue;
172940
173882
  }
172941
- function buildSidebarSnapshot(db, sessionId, directory, liveSessionState) {
173883
+ function buildSidebarSnapshot(db, sessionId, directory, liveSessionState, injectionBudgetTokens) {
172942
173884
  const empty = {
172943
173885
  sessionId,
172944
173886
  usagePercentage: 0,
@@ -173023,6 +173965,20 @@ ${c.content}
173023
173965
  const cached2 = meta3.memory_block_cache;
173024
173966
  if (typeof cached2 === "string" && cached2.length > 0) {
173025
173967
  memoryTokens = estimateTokens(cached2);
173968
+ } else if (memoryBlockCount > 0 && projectIdentity) {
173969
+ try {
173970
+ let memories = getMemoriesByProject(db, projectIdentity, [
173971
+ "active",
173972
+ "permanent"
173973
+ ]);
173974
+ if (injectionBudgetTokens && memories.length > 0) {
173975
+ memories = trimMemoriesToBudget(sessionId, memories, injectionBudgetTokens);
173976
+ }
173977
+ const block = renderMemoryBlock(memories);
173978
+ memoryTokens = block ? estimateTokens(block) : 0;
173979
+ } catch {
173980
+ memoryTokens = 0;
173981
+ }
173026
173982
  }
173027
173983
  }
173028
173984
  let lastDreamerRunAt = null;
@@ -173041,8 +173997,24 @@ ${c.content}
173041
173997
  let activeProviderID;
173042
173998
  let activeModelID;
173043
173999
  if (liveSessionState) {
173044
- const model = liveSessionState.liveModelBySession.get(sessionId);
173045
- const agent = liveSessionState.agentBySession.get(sessionId);
174000
+ let model = liveSessionState.liveModelBySession.get(sessionId);
174001
+ let agent = liveSessionState.agentBySession.get(sessionId);
174002
+ if (!model || !agent) {
174003
+ const recovered = findLastAssistantModelFromOpenCodeDb(sessionId);
174004
+ if (recovered) {
174005
+ if (!model) {
174006
+ model = {
174007
+ providerID: recovered.providerID,
174008
+ modelID: recovered.modelID
174009
+ };
174010
+ liveSessionState.liveModelBySession.set(sessionId, model);
174011
+ }
174012
+ if (!agent && recovered.agent) {
174013
+ agent = recovered.agent;
174014
+ liveSessionState.agentBySession.set(sessionId, agent);
174015
+ }
174016
+ }
174017
+ }
173046
174018
  if (model) {
173047
174019
  activeProviderID = model.providerID;
173048
174020
  activeModelID = model.modelID;
@@ -173091,8 +174063,8 @@ ${c.content}
173091
174063
  return empty;
173092
174064
  }
173093
174065
  }
173094
- function buildStatusDetail(db, sessionId, directory, modelKey, config2, liveSessionState) {
173095
- const base = buildSidebarSnapshot(db, sessionId, directory, liveSessionState);
174066
+ function buildStatusDetail(db, sessionId, directory, modelKey, config2, liveSessionState, injectionBudgetTokens) {
174067
+ const base = buildSidebarSnapshot(db, sessionId, directory, liveSessionState, injectionBudgetTokens);
173096
174068
  const detail = {
173097
174069
  ...base,
173098
174070
  tagCounter: 0,
@@ -173208,13 +174180,14 @@ function registerRpcHandlers(rpcServer, args) {
173208
174180
  const { directory, config: config2, liveSessionState } = args;
173209
174181
  const rawConfig = config2;
173210
174182
  const getNotificationParams = (sessionId) => getLiveNotificationParams(sessionId, liveSessionState.liveModelBySession, liveSessionState.variantBySession, liveSessionState.agentBySession);
174183
+ const injectionBudgetTokens = config2.memory?.injection_budget_tokens;
173211
174184
  rpcServer.handle("sidebar-snapshot", async (params) => {
173212
174185
  const sessionId = String(params.sessionId ?? "");
173213
174186
  const dir = String(params.directory ?? directory);
173214
174187
  const db = getDb();
173215
174188
  if (!db || !sessionId)
173216
174189
  return { error: "unavailable" };
173217
- return buildSidebarSnapshot(db, sessionId, dir, liveSessionState);
174190
+ return buildSidebarSnapshot(db, sessionId, dir, liveSessionState, injectionBudgetTokens);
173218
174191
  });
173219
174192
  rpcServer.handle("status-detail", async (params) => {
173220
174193
  const sessionId = String(params.sessionId ?? "");
@@ -173223,7 +174196,7 @@ function registerRpcHandlers(rpcServer, args) {
173223
174196
  const db = getDb();
173224
174197
  if (!db || !sessionId)
173225
174198
  return { error: "unavailable" };
173226
- return buildStatusDetail(db, sessionId, dir, modelKey, rawConfig, liveSessionState);
174199
+ return buildStatusDetail(db, sessionId, dir, modelKey, rawConfig, liveSessionState, injectionBudgetTokens);
173227
174200
  });
173228
174201
  rpcServer.handle("compartment-count", async (params) => {
173229
174202
  const sessionId = String(params.sessionId ?? "");
@@ -174236,13 +175209,13 @@ import { dirname as dirname5 } from "node:path";
174236
175209
 
174237
175210
  // src/shared/rpc-utils.ts
174238
175211
  import { createHash as createHash5 } from "node:crypto";
174239
- import { join as join21 } from "node:path";
175212
+ import { join as join22 } from "node:path";
174240
175213
  function projectHash(directory) {
174241
175214
  const normalized = directory.replace(/\/+$/, "");
174242
175215
  return createHash5("sha256").update(normalized).digest("hex").slice(0, 16);
174243
175216
  }
174244
175217
  function rpcPortFilePath(storageDir, directory) {
174245
- return join21(storageDir, "rpc", projectHash(directory), "port");
175218
+ return join22(storageDir, "rpc", projectHash(directory), "port");
174246
175219
  }
174247
175220
 
174248
175221
  // src/shared/rpc-server.ts
@@ -174438,7 +175411,7 @@ var plugin = async (ctx) => {
174438
175411
  refreshModelLimitsFromApi(ctx.client);
174439
175412
  setInterval(() => {
174440
175413
  refreshModelLimitsFromApi(ctx.client);
174441
- }, 5 * 60 * 1000);
175414
+ }, 60 * 60 * 1000);
174442
175415
  }
174443
175416
  if (conflictResult?.hasConflict) {
174444
175417
  sendConflictWarning(ctx.client, ctx.directory, conflictResult);