@cortexkit/opencode-magic-context 0.24.1 → 0.25.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 (93) hide show
  1. package/README.md +4 -2
  2. package/dist/agents/magic-context-prompt.d.ts.map +1 -1
  3. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +8 -0
  4. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  5. package/dist/features/magic-context/memory/embedding-local.d.ts +4 -0
  6. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  7. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +6 -0
  8. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  9. package/dist/features/magic-context/project-embedding-registry.d.ts +38 -0
  10. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  11. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  12. package/dist/features/magic-context/storage-meta-session.d.ts +1 -0
  13. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  14. package/dist/features/magic-context/storage-meta-shared.d.ts +2 -1
  15. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  16. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  17. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  18. package/dist/features/magic-context/storage-tags.d.ts +10 -0
  19. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  20. package/dist/features/magic-context/storage.d.ts +2 -2
  21. package/dist/features/magic-context/storage.d.ts.map +1 -1
  22. package/dist/features/magic-context/types.d.ts +1 -0
  23. package/dist/features/magic-context/types.d.ts.map +1 -1
  24. package/dist/hooks/magic-context/apply-operations.d.ts +3 -25
  25. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  26. package/dist/hooks/magic-context/caveman-cleanup.d.ts +1 -0
  27. package/dist/hooks/magic-context/caveman-cleanup.d.ts.map +1 -1
  28. package/dist/hooks/magic-context/channel2-delivery.d.ts +2 -0
  29. package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/command-handler.d.ts +7 -5
  31. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +7 -2
  33. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/embed-session-state.d.ts +14 -0
  35. package/dist/hooks/magic-context/embed-session-state.d.ts.map +1 -0
  36. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/format-embed-status.d.ts +9 -0
  38. package/dist/hooks/magic-context/format-embed-status.d.ts.map +1 -0
  39. package/dist/hooks/magic-context/heuristic-cleanup.d.ts +1 -0
  40. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts +1 -1
  45. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/strip-content.d.ts +0 -1
  47. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/tag-content-primitives.d.ts +2 -0
  49. package/dist/hooks/magic-context/tag-content-primitives.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/tool-drop-target.d.ts +1 -1
  52. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/tool-reclaim.d.ts +12 -0
  54. package/dist/hooks/magic-context/tool-reclaim.d.ts.map +1 -0
  55. package/dist/hooks/magic-context/transform-operations.d.ts +1 -1
  56. package/dist/hooks/magic-context/transform-operations.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/transform.d.ts +2 -0
  59. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1103 -408
  62. package/dist/plugin/conflict-warning-hook.d.ts.map +1 -1
  63. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  64. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  65. package/dist/shared/announcement.d.ts +1 -1
  66. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  67. package/dist/shared/rpc-types.d.ts +20 -0
  68. package/dist/shared/rpc-types.d.ts.map +1 -1
  69. package/dist/shared/sqlite.d.ts +5 -1
  70. package/dist/shared/sqlite.d.ts.map +1 -1
  71. package/dist/tools/ctx-expand/constants.d.ts +1 -1
  72. package/dist/tools/ctx-expand/constants.d.ts.map +1 -1
  73. package/dist/tools/ctx-expand/render.d.ts +43 -0
  74. package/dist/tools/ctx-expand/render.d.ts.map +1 -0
  75. package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
  76. package/dist/tools/ctx-expand/types.d.ts +6 -2
  77. package/dist/tools/ctx-expand/types.d.ts.map +1 -1
  78. package/dist/tools/ctx-reduce/constants.d.ts +1 -1
  79. package/dist/tools/ctx-reduce/constants.d.ts.map +1 -1
  80. package/dist/tui/data/context-db.d.ts +4 -2
  81. package/dist/tui/data/context-db.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/shared/announcement.ts +6 -6
  84. package/src/shared/model-suggestion-retry.test.ts +61 -1
  85. package/src/shared/model-suggestion-retry.ts +22 -0
  86. package/src/shared/rpc-types.ts +11 -0
  87. package/src/shared/sqlite-bind-style.test.ts +82 -0
  88. package/src/shared/sqlite.ts +30 -1
  89. package/src/shared/tag-transcript.test.ts +3 -1
  90. package/src/shared/tag-transcript.ts +19 -17
  91. package/src/tui/data/context-db.ts +34 -2
  92. package/src/tui/index.tsx +58 -4
  93. package/src/tui/slots/sidebar-content.tsx +7 -2
package/dist/index.js CHANGED
@@ -15562,9 +15562,11 @@ async function promptWithTimeout(client, args, timeoutMs, signal) {
15562
15562
  });
15563
15563
  } catch (error51) {
15564
15564
  if (signal?.aborted) {
15565
+ await abortChildRun(client, args.path.id);
15565
15566
  throw new Error("prompt aborted by external signal");
15566
15567
  }
15567
15568
  if (controller.signal.aborted) {
15569
+ await abortChildRun(client, args.path.id);
15568
15570
  throw new Error(`prompt timed out after ${timeoutMs}ms`);
15569
15571
  }
15570
15572
  throw error51;
@@ -15573,6 +15575,13 @@ async function promptWithTimeout(client, args, timeoutMs, signal) {
15573
15575
  signal?.removeEventListener("abort", onExternalAbort);
15574
15576
  }
15575
15577
  }
15578
+ async function abortChildRun(client, sessionId) {
15579
+ try {
15580
+ await client.session.abort({ path: { id: sessionId } });
15581
+ } catch (error51) {
15582
+ log(`[model-retry] child session abort failed for ${sessionId}: ${String(error51)}`);
15583
+ }
15584
+ }
15576
15585
  function isNonRetryable(error51, externalSignal) {
15577
15586
  if (externalSignal?.aborted)
15578
15587
  return true;
@@ -15844,7 +15853,7 @@ function isSessionMetaRow(row) {
15844
15853
  if (row === null || typeof row !== "object")
15845
15854
  return false;
15846
15855
  const r = row;
15847
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isStringOrNull(r.cached_m0_workspace_fingerprint) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
15856
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isStringOrNull(r.cached_m0_workspace_fingerprint) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme) && isNumberOrNull(r.tool_reclaim_watermark);
15848
15857
  }
15849
15858
  function getDefaultSessionMeta(sessionId) {
15850
15859
  return {
@@ -15867,6 +15876,7 @@ function getDefaultSessionMeta(sessionId) {
15867
15876
  conversationTokens: 0,
15868
15877
  toolCallTokens: 0,
15869
15878
  clearedReasoningThroughTag: 0,
15879
+ toolReclaimWatermark: 0,
15870
15880
  lastTodoState: "",
15871
15881
  cachedM0Bytes: null,
15872
15882
  cachedM1Bytes: null,
@@ -15930,6 +15940,7 @@ function toSessionMeta(row) {
15930
15940
  conversationTokens: numOrZero(row.conversation_tokens),
15931
15941
  toolCallTokens: numOrZero(row.tool_call_tokens),
15932
15942
  clearedReasoningThroughTag: numOrZero(row.cleared_reasoning_through_tag),
15943
+ toolReclaimWatermark: numOrZero(row.tool_reclaim_watermark),
15933
15944
  lastTodoState: lastTodoStateRaw,
15934
15945
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
15935
15946
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
@@ -16039,6 +16050,7 @@ var init_storage_meta_shared = __esm(() => {
16039
16050
  "conversation_tokens",
16040
16051
  "tool_call_tokens",
16041
16052
  "cleared_reasoning_through_tag",
16053
+ "tool_reclaim_watermark",
16042
16054
  "last_todo_state",
16043
16055
  "cached_m0_bytes",
16044
16056
  "cached_m1_bytes",
@@ -16087,6 +16099,7 @@ var init_storage_meta_shared = __esm(() => {
16087
16099
  conversationTokens: "conversation_tokens",
16088
16100
  toolCallTokens: "tool_call_tokens",
16089
16101
  clearedReasoningThroughTag: "cleared_reasoning_through_tag",
16102
+ toolReclaimWatermark: "tool_reclaim_watermark",
16090
16103
  lastTodoState: "last_todo_state",
16091
16104
  cachedM0Bytes: "cached_m0_bytes",
16092
16105
  cachedM1Bytes: "cached_m1_bytes",
@@ -16363,6 +16376,14 @@ function buildNodeSqliteDatabaseClass(DatabaseSync) {
16363
16376
  }
16364
16377
  super(typeof filename === "string" ? filename : ":memory:", translated);
16365
16378
  }
16379
+ prepare(sql) {
16380
+ const stmt = super.prepare(sql);
16381
+ for (const method of ["run", "get", "all"]) {
16382
+ const original = stmt[method].bind(stmt);
16383
+ stmt[method] = (...args) => args.length === 1 && Array.isArray(args[0]) ? original(...args[0]) : original(...args);
16384
+ }
16385
+ return stmt;
16386
+ }
16366
16387
  transaction(fn) {
16367
16388
  const self = this;
16368
16389
  const wrapped = function(...args) {
@@ -149339,6 +149360,9 @@ function stripCompleteTagPairsGlobally(value) {
149339
149360
  function stripMalformedTagNotationGlobally(value) {
149340
149361
  return value.replace(MALFORMED_TAG_GLOBAL_REGEX, "");
149341
149362
  }
149363
+ function stripDanglingTagNotationGlobally(value) {
149364
+ return value.replace(DANGLING_TAG_GLOBAL_REGEX, "");
149365
+ }
149342
149366
  function stripTagSectionCharacters(value) {
149343
149367
  return value.replace(STRAY_SECTION_CHAR_REGEX, "");
149344
149368
  }
@@ -149346,6 +149370,7 @@ function stripPersistedAssistantText(value) {
149346
149370
  let text = stripWellFormedLeadingTagPrefix(value);
149347
149371
  text = stripCompleteTagPairsGlobally(text);
149348
149372
  text = stripMalformedTagNotationGlobally(text);
149373
+ text = stripDanglingTagNotationGlobally(text);
149349
149374
  text = stripTagSectionCharacters(text);
149350
149375
  return text.trim();
149351
149376
  }
@@ -149358,6 +149383,7 @@ function stripTagPrefix(value) {
149358
149383
  const prev = stripped;
149359
149384
  stripped = stripped.replace(MALFORMED_TAG_PREFIX_REGEX, "");
149360
149385
  stripped = stripped.replace(TAG_PREFIX_REGEX, "");
149386
+ stripped = stripped.replace(DANGLING_TAG_PREFIX_REGEX, "");
149361
149387
  if (stripped === prev)
149362
149388
  break;
149363
149389
  }
@@ -149379,11 +149405,13 @@ function isThinkingPart(part) {
149379
149405
  const candidate = part;
149380
149406
  return candidate.type === "thinking" || candidate.type === "reasoning";
149381
149407
  }
149382
- var encoder, TAG_PREFIX_REGEX, MALFORMED_TAG_PREFIX_REGEX, COMPLETE_TAG_PAIR_GLOBAL_REGEX, MALFORMED_TAG_GLOBAL_REGEX, STRAY_SECTION_CHAR_REGEX;
149408
+ var encoder, TAG_PREFIX_REGEX, MALFORMED_TAG_PREFIX_REGEX, DANGLING_TAG_GLOBAL_REGEX, DANGLING_TAG_PREFIX_REGEX, COMPLETE_TAG_PAIR_GLOBAL_REGEX, MALFORMED_TAG_GLOBAL_REGEX, STRAY_SECTION_CHAR_REGEX;
149383
149409
  var init_tag_content_primitives = __esm(() => {
149384
149410
  encoder = new TextEncoder;
149385
149411
  TAG_PREFIX_REGEX = /^(?:§\d+§\s*)+/;
149386
149412
  MALFORMED_TAG_PREFIX_REGEX = /^(?:§\d+">§(?:\d+§)?\s*)+/;
149413
+ DANGLING_TAG_GLOBAL_REGEX = /\u00a7\d+(?!\.\d)[^\s\u00a7\w.]?/g;
149414
+ DANGLING_TAG_PREFIX_REGEX = /^(?:\u00a7\d+(?!\.\d)[^\s\u00a7\w.]?\s*)+/;
149387
149415
  COMPLETE_TAG_PAIR_GLOBAL_REGEX = /\u00a7\d+\u00a7/g;
149388
149416
  MALFORMED_TAG_GLOBAL_REGEX = /\u00a7\d+">(?:\u00a7(?:\d+\u00a7)?)?/g;
149389
149417
  STRAY_SECTION_CHAR_REGEX = /\u00a7/g;
@@ -149447,12 +149475,13 @@ function setToolContent(part, content) {
149447
149475
  part.content = content;
149448
149476
  }
149449
149477
  }
149450
- function truncateToolPart(part) {
149478
+ function truncateToolPart(part, tagId) {
149451
149479
  if (!isRecord(part))
149452
149480
  return;
149481
+ const sentinel = `[dropped §${tagId}§]`;
149453
149482
  if (part.type === "tool" && isRecord(part.state)) {
149454
149483
  const state = part.state;
149455
- state.output = "[truncated]";
149484
+ state.output = sentinel;
149456
149485
  if (isRecord(state.input)) {
149457
149486
  const inputSize = estimateInputSize(state.input);
149458
149487
  if (inputSize > 500) {
@@ -149462,7 +149491,7 @@ function truncateToolPart(part) {
149462
149491
  return;
149463
149492
  }
149464
149493
  if (part.type === "tool_result") {
149465
- part.content = "[truncated]";
149494
+ part.content = sentinel;
149466
149495
  return;
149467
149496
  }
149468
149497
  if (part.type === "tool-invocation" && isRecord(part.args)) {
@@ -149579,7 +149608,7 @@ class ToolMutationBatch {
149579
149608
  this.affectedMessages.clear();
149580
149609
  }
149581
149610
  }
149582
- function createToolDropTarget(compositeKey, thinkingParts, index, batch) {
149611
+ function createToolDropTarget(compositeKey, thinkingParts, index, batch, tagId) {
149583
149612
  const drop = () => {
149584
149613
  const entry = index.get(compositeKey);
149585
149614
  if (!entry || entry.occurrences.length === 0)
@@ -149600,7 +149629,7 @@ function createToolDropTarget(compositeKey, thinkingParts, index, batch) {
149600
149629
  if (!entry.hasResult)
149601
149630
  return "incomplete";
149602
149631
  for (const occurrence of entry.occurrences) {
149603
- truncateToolPart(occurrence.part);
149632
+ truncateToolPart(occurrence.part, tagId);
149604
149633
  }
149605
149634
  clearThinkingParts(thinkingParts);
149606
149635
  return "truncated";
@@ -151334,6 +151363,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
151334
151363
  ensureColumn(db, "session_meta", "historian_last_failure_at", "INTEGER DEFAULT NULL");
151335
151364
  ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
151336
151365
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
151366
+ ensureColumn(db, "session_meta", "tool_reclaim_watermark", "INTEGER DEFAULT 0");
151337
151367
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
151338
151368
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
151339
151369
  ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
@@ -154123,7 +154153,8 @@ var exports_storage_meta_session = {};
154123
154153
  __export(exports_storage_meta_session, {
154124
154154
  updateSessionMeta: () => updateSessionMeta,
154125
154155
  getOrCreateSessionMeta: () => getOrCreateSessionMeta,
154126
- clearSession: () => clearSession
154156
+ clearSession: () => clearSession,
154157
+ advanceToolReclaimWatermark: () => advanceToolReclaimWatermark
154127
154158
  });
154128
154159
  import { Buffer as Buffer3 } from "node:buffer";
154129
154160
  function getSessionMetaSelectColumns(db) {
@@ -154184,6 +154215,14 @@ function updateSessionMeta(db, sessionId, updates) {
154184
154215
  db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")} WHERE session_id = ?`).run(...values, sessionId);
154185
154216
  })();
154186
154217
  }
154218
+ function advanceToolReclaimWatermark(db, sessionId, maxTagNumber) {
154219
+ if (maxTagNumber <= 0)
154220
+ return;
154221
+ db.transaction(() => {
154222
+ ensureSessionMetaRow(db, sessionId);
154223
+ db.prepare("UPDATE session_meta SET tool_reclaim_watermark = MAX(COALESCE(tool_reclaim_watermark, 0), ?) WHERE session_id = ?").run(maxTagNumber, sessionId);
154224
+ })();
154225
+ }
154187
154226
  function clearSession(db, sessionId) {
154188
154227
  db.transaction(() => {
154189
154228
  db.prepare("DELETE FROM pending_ops WHERE session_id = ?").run(sessionId);
@@ -154222,6 +154261,7 @@ var init_storage_meta_session = __esm(async () => {
154222
154261
  last_transform_error: "'' AS last_transform_error",
154223
154262
  system_prompt_hash: "'' AS system_prompt_hash",
154224
154263
  last_todo_state: "'' AS last_todo_state",
154264
+ tool_reclaim_watermark: "0 AS tool_reclaim_watermark",
154225
154265
  cached_m0_bytes: "NULL AS cached_m0_bytes",
154226
154266
  cached_m1_bytes: "NULL AS cached_m1_bytes",
154227
154267
  cached_m0_project_memory_epoch: "NULL AS cached_m0_project_memory_epoch",
@@ -154629,6 +154669,26 @@ function getActiveTagTokenAggregate(db, sessionId, protectedTags = 0) {
154629
154669
  nullCount: row?.null_count ?? 0
154630
154670
  };
154631
154671
  }
154672
+ function getOldestActiveUnprotectedToolTags(db, sessionId, protectedTags = 0, limit = 4) {
154673
+ if (limit <= 0)
154674
+ return [];
154675
+ const boundedLimit = Math.max(1, Math.min(10, Math.floor(limit)));
154676
+ const whereProtected = protectedTags > 0 ? `AND tag_number < (
154677
+ SELECT tag_number FROM tags
154678
+ WHERE session_id = ? AND status = 'active'
154679
+ ORDER BY tag_number DESC LIMIT 1 OFFSET ?
154680
+ )` : "";
154681
+ const params = protectedTags > 0 ? [sessionId, sessionId, protectedTags - 1, boundedLimit] : [sessionId, boundedLimit];
154682
+ const rows = db.prepare(`SELECT tag_number, tool_name
154683
+ FROM tags
154684
+ WHERE session_id = ? AND status = 'active' AND type = 'tool' ${whereProtected}
154685
+ ORDER BY tag_number ASC, id ASC
154686
+ LIMIT ?`).all(...params);
154687
+ return rows.filter((row) => typeof row.tag_number === "number").map((row) => ({
154688
+ tagNumber: row.tag_number,
154689
+ toolName: typeof row.tool_name === "string" ? row.tool_name : null
154690
+ }));
154691
+ }
154632
154692
  function getTriggerTagTokenUpperBound(db, sessionId) {
154633
154693
  const row = db.prepare(`SELECT
154634
154694
  COALESCE(SUM(COALESCE(token_count, 0) + COALESCE(input_token_count, 0) + COALESCE(reasoning_token_count, 0)), 0) AS bound,
@@ -163972,6 +164032,58 @@ var init_safe_notification_target = __esm(() => {
163972
164032
  DEFAULT_TITLE_RE = /^(New session - |Child session - )\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
163973
164033
  });
163974
164034
 
164035
+ // src/shared/rpc-notifications.ts
164036
+ var exports_rpc_notifications = {};
164037
+ __export(exports_rpc_notifications, {
164038
+ pushNotification: () => pushNotification,
164039
+ isTuiConnected: () => isTuiConnected,
164040
+ drainNotifications: () => drainNotifications
164041
+ });
164042
+ function pushNotification(type, payload, sessionId) {
164043
+ queue.push({ id: nextNotificationId++, type, payload, sessionId });
164044
+ if (queue.length > 100) {
164045
+ const newestPerSession = new Map;
164046
+ for (const n of queue) {
164047
+ const prev = newestPerSession.get(n.sessionId);
164048
+ if (prev === undefined || n.id > prev) {
164049
+ newestPerSession.set(n.sessionId, n.id);
164050
+ }
164051
+ }
164052
+ const mustKeep = new Set(newestPerSession.values());
164053
+ const byNewest = [...queue].sort((a, b) => b.id - a.id);
164054
+ const kept = [];
164055
+ for (const n of byNewest) {
164056
+ if (kept.length < 50 || mustKeep.has(n.id))
164057
+ kept.push(n);
164058
+ }
164059
+ queue = kept.sort((a, b) => a.id - b.id);
164060
+ }
164061
+ }
164062
+ function drainNotifications(lastReceivedId = 0, sessionId) {
164063
+ const now = Date.now();
164064
+ lastDrainAtAny = now;
164065
+ if (sessionId !== undefined)
164066
+ lastDrainAtBySession.set(sessionId, now);
164067
+ const matchesClient = (notification) => sessionId === undefined || notification.sessionId === undefined || notification.sessionId === sessionId;
164068
+ if (lastReceivedId > 0) {
164069
+ queue = queue.filter((notification) => !(notification.id <= lastReceivedId && matchesClient(notification)));
164070
+ }
164071
+ return queue.filter((notification) => notification.id > lastReceivedId && matchesClient(notification));
164072
+ }
164073
+ function isTuiConnected(sessionId) {
164074
+ const now = Date.now();
164075
+ if (sessionId !== undefined) {
164076
+ const at = lastDrainAtBySession.get(sessionId) ?? 0;
164077
+ return at > 0 && now - at < TUI_CONNECTED_WINDOW_MS;
164078
+ }
164079
+ return lastDrainAtAny > 0 && now - lastDrainAtAny < TUI_CONNECTED_WINDOW_MS;
164080
+ }
164081
+ var queue, nextNotificationId = 1, lastDrainAtBySession, lastDrainAtAny = 0, TUI_CONNECTED_WINDOW_MS = 3000;
164082
+ var init_rpc_notifications = __esm(() => {
164083
+ queue = [];
164084
+ lastDrainAtBySession = new Map;
164085
+ });
164086
+
163975
164087
  // src/plugin/conflict-warning-hook.ts
163976
164088
  var exports_conflict_warning_hook = {};
163977
164089
  __export(exports_conflict_warning_hook, {
@@ -164306,6 +164418,9 @@ async function sendStartupAnnouncement(client, directory, version2, features, fo
164306
164418
  if (!sessionId) {
164307
164419
  return;
164308
164420
  }
164421
+ const { isTuiConnected: isTuiConnected2 } = await Promise.resolve().then(() => (init_rpc_notifications(), exports_rpc_notifications));
164422
+ if (isTuiConnected2(sessionId) || isTuiConnected2())
164423
+ return;
164309
164424
  if (await waitForSafeNotificationTarget(client, sessionId) === "skip")
164310
164425
  return;
164311
164426
  const bullets = features.map((line) => ` • ${line}`).join(`
@@ -164528,6 +164643,20 @@ function getDistinctStoredModelIds(db, projectPath) {
164528
164643
  const rows = getDistinctStoredModelIdsStatement(db).all(projectPath);
164529
164644
  return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
164530
164645
  }
164646
+ function getMemoryEmbedCoverage(db, projectPath, modelId) {
164647
+ const row = db.prepare(`SELECT
164648
+ COUNT(*) AS total,
164649
+ SUM(CASE WHEN EXISTS (
164650
+ SELECT 1 FROM memory_embeddings e
164651
+ WHERE e.memory_id = m.id AND e.model_id = ?
164652
+ ) THEN 1 ELSE 0 END) AS embedded
164653
+ FROM memories m
164654
+ WHERE m.project_path = ? AND m.status = 'active'`).get(modelId, projectPath);
164655
+ return {
164656
+ total: typeof row?.total === "number" ? row.total : 0,
164657
+ embedded: typeof row?.embedded === "number" ? row.embedded : 0
164658
+ };
164659
+ }
164531
164660
  var saveEmbeddingStatements, loadAllEmbeddingsStatements, deleteEmbeddingStatements, getStoredModelIdStatements, clearAllEmbeddingsStatements, getDistinctStoredModelIdsStatements;
164532
164661
  var init_storage_memory_embeddings = __esm(() => {
164533
164662
  saveEmbeddingStatements = new WeakMap;
@@ -165572,6 +165701,28 @@ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId)
165572
165701
  )`).get(projectPath, sessionId, projectPath, modelId);
165573
165702
  return typeof row?.n === "number" ? row.n : 0;
165574
165703
  }
165704
+ function countSessionCompartmentEmbedCoverage(db, projectPath, sessionId, modelId) {
165705
+ const row = db.prepare(`SELECT
165706
+ COUNT(*) AS total,
165707
+ SUM(CASE WHEN EXISTS (
165708
+ SELECT 1 FROM compartment_chunk_embeddings e
165709
+ WHERE e.compartment_id = c.id
165710
+ AND e.project_path = ?
165711
+ AND e.model_id = ?
165712
+ ) THEN 1 ELSE 0 END) AS embedded
165713
+ FROM compartments c
165714
+ JOIN session_projects sp
165715
+ ON sp.session_id = c.session_id
165716
+ AND sp.harness = c.harness
165717
+ AND sp.project_path = ?
165718
+ WHERE c.session_id = ?
165719
+ AND c.start_message IS NOT NULL
165720
+ AND c.end_message IS NOT NULL`).get(projectPath, modelId, projectPath, sessionId);
165721
+ return {
165722
+ total: typeof row?.total === "number" ? row.total : 0,
165723
+ embedded: typeof row?.embedded === "number" ? row.embedded : 0
165724
+ };
165725
+ }
165575
165726
  var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512, CHUNK_WINDOW_SAFETY_RATIO = 0.9, loadFtsRowsStatements, existingHashStatements, existingHashByProjectStatements, deleteByCompartmentStatements, insertEmbeddingStatements, distinctModelStatements, clearProjectStatements, clearProjectModelStatements, searchRowsStatements, searchRowsByModelStatements, backfillCandidateStatements, sessionBackfillCandidateStatements;
165576
165727
  var init_compartment_chunk_embedding = __esm(() => {
165577
165728
  init_read_session_formatting();
@@ -165729,6 +165880,18 @@ async function withQuietConsole(fn) {
165729
165880
  console.error = origError;
165730
165881
  }
165731
165882
  }
165883
+ function isNativeRuntimeMissingError(error51) {
165884
+ const message = error51 instanceof Error ? error51.message : String(error51 ?? "");
165885
+ const lower = message.toLowerCase();
165886
+ const code = error51?.code;
165887
+ const name2 = error51?.name;
165888
+ if (code === "ERR_DLOPEN_FAILED" && lower.includes("onnxruntime")) {
165889
+ return true;
165890
+ }
165891
+ if (!lower.includes("onnxruntime-node"))
165892
+ return false;
165893
+ return code === "ERR_MODULE_NOT_FOUND" || name2 === "ResolveMessage" || lower.includes("cannot find package") || lower.includes("cannot find module") || lower.includes("err_module_not_found");
165894
+ }
165732
165895
  function isTransientLoadError(error51) {
165733
165896
  const message = error51 instanceof Error ? error51.message : String(error51 ?? "");
165734
165897
  if (!message)
@@ -165793,6 +165956,9 @@ class LocalEmbeddingProvider {
165793
165956
  if (this.pipeline) {
165794
165957
  return true;
165795
165958
  }
165959
+ if (nativeRuntimeMissing) {
165960
+ return false;
165961
+ }
165796
165962
  if (this.initPromise) {
165797
165963
  await this.initPromise;
165798
165964
  return this.pipeline !== null;
@@ -165859,7 +166025,12 @@ class LocalEmbeddingProvider {
165859
166025
  await releaseLock();
165860
166026
  }
165861
166027
  } catch (error51) {
165862
- log("[magic-context] embedding model failed to load:", error51);
166028
+ if (isNativeRuntimeMissingError(error51)) {
166029
+ nativeRuntimeMissing = true;
166030
+ log("[magic-context] local embedding runtime is not installed (onnxruntime-node missing from this install). Local embeddings are disabled. Fix: reinstall the plugin (run `npx @cortexkit/magic-context@latest doctor --force`), or configure an `openai-compatible`/`ollama` embedding endpoint instead. Existing memories are unaffected.");
166031
+ } else {
166032
+ log("[magic-context] embedding model failed to load:", error51);
166033
+ }
165863
166034
  this.pipeline = null;
165864
166035
  } finally {
165865
166036
  this.initPromise = null;
@@ -165970,7 +166141,7 @@ class LocalEmbeddingProvider {
165970
166141
  return this.pipeline !== null;
165971
166142
  }
165972
166143
  }
165973
- var LOCK_POLL_MS = 150, STALE_LOCK_MS, MAX_LOCK_WAIT_MS;
166144
+ var LOCK_POLL_MS = 150, STALE_LOCK_MS, MAX_LOCK_WAIT_MS, nativeRuntimeMissing = false;
165974
166145
  var init_embedding_local = __esm(() => {
165975
166146
  init_magic_context();
165976
166147
  init_data_path();
@@ -166384,6 +166555,121 @@ var init_storage_git_commit_embeddings = __esm(() => {
166384
166555
  distinctModelIdStatements = new WeakMap;
166385
166556
  });
166386
166557
 
166558
+ // src/features/magic-context/git-commits/storage-git-commits.ts
166559
+ function getInsertStatement(db) {
166560
+ let stmt = insertStatements.get(db);
166561
+ if (!stmt) {
166562
+ stmt = db.prepare(`INSERT INTO git_commits (sha, project_path, short_sha, message, author, committed_at, indexed_at)
166563
+ VALUES (?, ?, ?, ?, ?, ?, ?)
166564
+ ON CONFLICT(sha) DO UPDATE SET
166565
+ project_path = excluded.project_path,
166566
+ short_sha = excluded.short_sha,
166567
+ message = excluded.message,
166568
+ author = excluded.author,
166569
+ committed_at = excluded.committed_at,
166570
+ indexed_at = excluded.indexed_at
166571
+ WHERE git_commits.message != excluded.message`);
166572
+ insertStatements.set(db, stmt);
166573
+ }
166574
+ return stmt;
166575
+ }
166576
+ function getExistingShasStatement(db) {
166577
+ let stmt = existingShasStatements.get(db);
166578
+ if (!stmt) {
166579
+ stmt = db.prepare("SELECT sha FROM git_commits WHERE project_path = ?");
166580
+ existingShasStatements.set(db, stmt);
166581
+ }
166582
+ return stmt;
166583
+ }
166584
+ function getProjectCountStatement(db) {
166585
+ let stmt = projectCountStatements.get(db);
166586
+ if (!stmt) {
166587
+ stmt = db.prepare("SELECT COUNT(*) AS count FROM git_commits WHERE project_path = ?");
166588
+ projectCountStatements.set(db, stmt);
166589
+ }
166590
+ return stmt;
166591
+ }
166592
+ function getLatestCommitTimeStatement(db) {
166593
+ let stmt = latestCommitTimeStatements.get(db);
166594
+ if (!stmt) {
166595
+ stmt = db.prepare("SELECT MAX(committed_at) AS latest FROM git_commits WHERE project_path = ?");
166596
+ latestCommitTimeStatements.set(db, stmt);
166597
+ }
166598
+ return stmt;
166599
+ }
166600
+ function getEvictOverflowStatement(db) {
166601
+ let stmt = evictOverflowStatements.get(db);
166602
+ if (!stmt) {
166603
+ stmt = db.prepare(`DELETE FROM git_commits
166604
+ WHERE rowid IN (
166605
+ SELECT rowid FROM git_commits
166606
+ WHERE project_path = ?
166607
+ ORDER BY committed_at DESC, sha DESC
166608
+ LIMIT -1 OFFSET ?
166609
+ )`);
166610
+ evictOverflowStatements.set(db, stmt);
166611
+ }
166612
+ return stmt;
166613
+ }
166614
+ function upsertCommits(db, projectPath, commits) {
166615
+ if (commits.length === 0)
166616
+ return { inserted: 0, updated: 0 };
166617
+ const existing = new Set;
166618
+ for (const row of getExistingShasStatement(db).all(projectPath)) {
166619
+ existing.add(row.sha);
166620
+ }
166621
+ let inserted = 0;
166622
+ let updated = 0;
166623
+ const now = Date.now();
166624
+ const insertStmt = getInsertStatement(db);
166625
+ db.transaction(() => {
166626
+ for (const commit of commits) {
166627
+ const result = insertStmt.run(commit.sha, projectPath, commit.shortSha, commit.message, commit.author, commit.committedAtMs, now);
166628
+ if (result.changes > 0) {
166629
+ if (existing.has(commit.sha)) {
166630
+ updated++;
166631
+ } else {
166632
+ inserted++;
166633
+ existing.add(commit.sha);
166634
+ }
166635
+ }
166636
+ }
166637
+ })();
166638
+ return { inserted, updated };
166639
+ }
166640
+ function getCommitCount(db, projectPath) {
166641
+ const row = getProjectCountStatement(db).get(projectPath);
166642
+ return row?.count ?? 0;
166643
+ }
166644
+ function getLatestIndexedCommitTimeMs(db, projectPath) {
166645
+ const row = getLatestCommitTimeStatement(db).get(projectPath);
166646
+ return row?.latest ?? null;
166647
+ }
166648
+ function enforceProjectCap(db, projectPath, maxCommits) {
166649
+ if (maxCommits <= 0)
166650
+ return 0;
166651
+ const count = getCommitCount(db, projectPath);
166652
+ if (count <= maxCommits)
166653
+ return 0;
166654
+ getEvictOverflowStatement(db).run(projectPath, maxCommits);
166655
+ const after = getCommitCount(db, projectPath);
166656
+ const evicted = Math.max(0, count - after);
166657
+ if (evicted > 0) {
166658
+ log(`[git-commits] evicted ${evicted} oldest commits for project ${projectPath} (cap=${maxCommits}, was=${count})`);
166659
+ }
166660
+ return evicted;
166661
+ }
166662
+ var insertStatements, existingShasStatements, projectCountStatements, evictStatements, evictOverflowStatements, latestCommitTimeStatements;
166663
+ var init_storage_git_commits = __esm(() => {
166664
+ init_logger();
166665
+ insertStatements = new WeakMap;
166666
+ existingShasStatements = new WeakMap;
166667
+ projectCountStatements = new WeakMap;
166668
+ evictStatements = new WeakMap;
166669
+ evictOverflowStatements = new WeakMap;
166670
+ latestCommitTimeStatements = new WeakMap;
166671
+ });
166672
+
166387
166673
  // src/features/magic-context/git-commits/sweep-coordinator.ts
166388
166674
  function runImmediate2(db, body) {
166389
166675
  db.exec("BEGIN IMMEDIATE");
@@ -166677,7 +166963,9 @@ function snapshotFor(registration) {
166677
166963
  enabled,
166678
166964
  gitCommitEnabled,
166679
166965
  modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
166680
- chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
166966
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId,
166967
+ model: registration.observationMode || !providerIsOn ? "off" : ("model" in registration.config) && registration.config.model.trim() ? registration.config.model.trim() : registration.modelId,
166968
+ provider: registration.observationMode || !providerIsOn ? "off" : registration.config.provider ?? "local"
166681
166969
  };
166682
166970
  }
166683
166971
  function disposeProvider(provider) {
@@ -166888,8 +167176,9 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
166888
167176
  }
166889
167177
  async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
166890
167178
  const noWork = [];
167179
+ const failed = [];
166891
167180
  if (candidates.length === 0)
166892
- return { embedded: 0, noWork };
167181
+ return { embedded: 0, noWork, failed };
166893
167182
  const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
166894
167183
  const prepared = [];
166895
167184
  for (const candidate of candidates) {
@@ -166906,7 +167195,7 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
166906
167195
  prepared.push({ candidate, windows });
166907
167196
  }
166908
167197
  if (prepared.length === 0)
166909
- return { embedded: 0, noWork };
167198
+ return { embedded: 0, noWork, failed };
166910
167199
  let embedded = 0;
166911
167200
  let i = 0;
166912
167201
  while (i < prepared.length) {
@@ -166923,35 +167212,60 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
166923
167212
  const texts = [];
166924
167213
  for (const item of slice)
166925
167214
  texts.push(...item.windows.map((w) => w.text));
166926
- try {
166927
- const result = await embedBatchForProject(projectIdentity, texts, signal);
166928
- if (!result)
166929
- continue;
167215
+ const persistedIds = new Set;
167216
+ for (let attempt = 0;attempt < EMBED_SLICE_RETRY_ATTEMPTS; attempt++) {
166930
167217
  if (signal?.aborted)
166931
167218
  break;
166932
- let offset = 0;
166933
- for (const item of slice) {
166934
- const vectors = result.vectors.slice(offset, offset + item.windows.length);
166935
- offset += item.windows.length;
166936
- if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
166937
- continue;
167219
+ let result = null;
167220
+ const attemptStart = Date.now();
167221
+ try {
167222
+ result = await embedBatchForProject(projectIdentity, texts, signal);
167223
+ } catch (error51) {
167224
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
167225
+ }
167226
+ if (signal?.aborted)
167227
+ break;
167228
+ if (result) {
167229
+ let offset = 0;
167230
+ for (const item of slice) {
167231
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
167232
+ offset += item.windows.length;
167233
+ if (persistedIds.has(item.candidate.id))
167234
+ continue;
167235
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
167236
+ continue;
167237
+ }
167238
+ const rows = item.windows.map((window, index) => ({
167239
+ compartmentId: item.candidate.id,
167240
+ sessionId: item.candidate.sessionId,
167241
+ projectPath: projectIdentity,
167242
+ window,
167243
+ modelId,
167244
+ vector: vectors[index]
167245
+ }));
167246
+ replaceCompartmentChunkEmbeddings(db, rows);
167247
+ persistedIds.add(item.candidate.id);
166938
167248
  }
166939
- const rows = item.windows.map((window, index) => ({
166940
- compartmentId: item.candidate.id,
166941
- sessionId: item.candidate.sessionId,
166942
- projectPath: projectIdentity,
166943
- window,
166944
- modelId,
166945
- vector: vectors[index]
166946
- }));
166947
- replaceCompartmentChunkEmbeddings(db, rows);
166948
- embedded += 1;
166949
167249
  }
166950
- } catch (error51) {
166951
- log("[magic-context] failed to proactively embed compartment chunks:", error51);
167250
+ if (persistedIds.size === slice.length)
167251
+ break;
167252
+ if (persistedIds.size > 0)
167253
+ break;
167254
+ if (Date.now() - attemptStart >= EMBED_SLOW_FAILURE_NO_RETRY_MS)
167255
+ break;
167256
+ if (attempt < EMBED_SLICE_RETRY_ATTEMPTS - 1) {
167257
+ await new Promise((resolve7) => setTimeout(resolve7, EMBED_SLICE_RETRY_BASE_MS * 2 ** attempt));
167258
+ }
167259
+ }
167260
+ embedded += persistedIds.size;
167261
+ if (!signal?.aborted) {
167262
+ for (const item of slice) {
167263
+ if (!persistedIds.has(item.candidate.id))
167264
+ failed.push(item.candidate.id);
167265
+ }
166952
167266
  }
166953
167267
  }
166954
- return { embedded, noWork };
167268
+ return { embedded, noWork, failed };
166955
167269
  }
166956
167270
  async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
166957
167271
  const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
@@ -166974,9 +167288,11 @@ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, opt
166974
167288
  renewal.unref?.();
166975
167289
  const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
166976
167290
  const skipIds = [];
167291
+ const failedIds = [];
166977
167292
  let embedded = 0;
166978
167293
  let aborted2 = false;
166979
- let providerStalled = false;
167294
+ let providerDown = false;
167295
+ let consecutiveFailedBatches = 0;
166980
167296
  try {
166981
167297
  options?.onProgress?.({ embedded, total });
166982
167298
  for (;; ) {
@@ -166984,15 +167300,26 @@ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, opt
166984
167300
  aborted2 = true;
166985
167301
  break;
166986
167302
  }
166987
- const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
167303
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, [...skipIds, ...failedIds]);
166988
167304
  if (candidates.length === 0)
166989
167305
  break;
166990
- const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
167306
+ const {
167307
+ embedded: n,
167308
+ noWork,
167309
+ failed
167310
+ } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
166991
167311
  for (const id of noWork)
166992
167312
  skipIds.push(id);
167313
+ for (const id of failed)
167314
+ failedIds.push(id);
166993
167315
  if (n === 0 && noWork.length === 0) {
166994
- providerStalled = true;
166995
- break;
167316
+ consecutiveFailedBatches += 1;
167317
+ if (consecutiveFailedBatches >= MAX_CONSECUTIVE_FAILED_BATCHES) {
167318
+ providerDown = true;
167319
+ break;
167320
+ }
167321
+ } else {
167322
+ consecutiveFailedBatches = 0;
166996
167323
  }
166997
167324
  embedded += n;
166998
167325
  options?.onProgress?.({ embedded: Math.min(embedded, total), total });
@@ -167000,23 +167327,58 @@ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, opt
167000
167327
  }
167001
167328
  } finally {
167002
167329
  clearInterval(renewal);
167003
- releaseGitSweepLease(db, projectIdentity, holderId);
167330
+ try {
167331
+ releaseGitSweepLease(db, projectIdentity, holderId);
167332
+ } catch (error51) {
167333
+ log("[magic-context] embed drain: lease release failed (will TTL-expire):", error51);
167334
+ }
167004
167335
  }
167005
167336
  if (aborted2)
167006
- return { status: "aborted", embedded, total };
167007
- if (providerStalled) {
167337
+ return { status: "aborted", embedded, total, failed: failedIds.length };
167338
+ if (providerDown || failedIds.length > 0) {
167008
167339
  const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
167009
- if (remaining > 0)
167010
- return { status: "stalled", embedded, total, remaining };
167340
+ if (remaining > 0) {
167341
+ return { status: "stalled", embedded, total, remaining, failed: failedIds.length };
167342
+ }
167011
167343
  }
167012
- return { status: "done", embedded, total };
167344
+ return { status: "done", embedded, total, failed: failedIds.length };
167013
167345
  }
167014
- var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, CHUNK_DRAIN_BATCH_SIZE = 8, MAX_WINDOWS_PER_EMBED_CALL = 16, SESSION_EMBED_LEASE_RENEWAL_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
167346
+ function getEmbeddingCoverageStatus(db, projectIdentity, sessionId) {
167347
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
167348
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
167349
+ return {
167350
+ enabled: false,
167351
+ model: snapshot?.model ?? "off",
167352
+ provider: snapshot?.provider ?? "off",
167353
+ session: { embedded: 0, total: 0 },
167354
+ memories: { embedded: 0, total: 0 },
167355
+ commits: { embedded: 0, total: 0, gitEnabled: false }
167356
+ };
167357
+ }
167358
+ const session = countSessionCompartmentEmbedCoverage(db, projectIdentity, sessionId, snapshot.chunkModelId);
167359
+ const memories = getMemoryEmbedCoverage(db, projectIdentity, snapshot.modelId);
167360
+ const gitEnabled = snapshot.gitCommitEnabled;
167361
+ const commits = gitEnabled ? {
167362
+ embedded: countEmbeddedCommits(db, projectIdentity),
167363
+ total: getCommitCount(db, projectIdentity),
167364
+ gitEnabled: true
167365
+ } : { embedded: 0, total: 0, gitEnabled: false };
167366
+ return {
167367
+ enabled: true,
167368
+ model: snapshot.model,
167369
+ provider: snapshot.provider,
167370
+ session,
167371
+ memories,
167372
+ commits
167373
+ };
167374
+ }
167375
+ var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, CHUNK_DRAIN_BATCH_SIZE = 8, MAX_WINDOWS_PER_EMBED_CALL = 2, SESSION_EMBED_LEASE_RENEWAL_MS, EMBED_SLICE_RETRY_ATTEMPTS = 3, EMBED_SLICE_RETRY_BASE_MS = 250, EMBED_SLOW_FAILURE_NO_RETRY_MS = 1e4, MAX_CONSECUTIVE_FAILED_BATCHES = 3, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
167015
167376
  var init_project_embedding_registry = __esm(() => {
167016
167377
  init_magic_context();
167017
167378
  init_logger();
167018
167379
  init_compartment_chunk_embedding();
167019
167380
  init_storage_git_commit_embeddings();
167381
+ init_storage_git_commits();
167020
167382
  init_sweep_coordinator();
167021
167383
  init_embedding_cache();
167022
167384
  init_embedding_identity();
@@ -167297,58 +167659,6 @@ var init_models_dev_cache = __esm(() => {
167297
167659
  init_logger();
167298
167660
  });
167299
167661
 
167300
- // src/shared/rpc-notifications.ts
167301
- var exports_rpc_notifications = {};
167302
- __export(exports_rpc_notifications, {
167303
- pushNotification: () => pushNotification,
167304
- isTuiConnected: () => isTuiConnected,
167305
- drainNotifications: () => drainNotifications
167306
- });
167307
- function pushNotification(type, payload, sessionId) {
167308
- queue2.push({ id: nextNotificationId++, type, payload, sessionId });
167309
- if (queue2.length > 100) {
167310
- const newestPerSession = new Map;
167311
- for (const n of queue2) {
167312
- const prev = newestPerSession.get(n.sessionId);
167313
- if (prev === undefined || n.id > prev) {
167314
- newestPerSession.set(n.sessionId, n.id);
167315
- }
167316
- }
167317
- const mustKeep = new Set(newestPerSession.values());
167318
- const byNewest = [...queue2].sort((a, b) => b.id - a.id);
167319
- const kept = [];
167320
- for (const n of byNewest) {
167321
- if (kept.length < 50 || mustKeep.has(n.id))
167322
- kept.push(n);
167323
- }
167324
- queue2 = kept.sort((a, b) => a.id - b.id);
167325
- }
167326
- }
167327
- function drainNotifications(lastReceivedId = 0, sessionId) {
167328
- const now = Date.now();
167329
- lastDrainAtAny = now;
167330
- if (sessionId !== undefined)
167331
- lastDrainAtBySession.set(sessionId, now);
167332
- const matchesClient = (notification) => sessionId === undefined || notification.sessionId === undefined || notification.sessionId === sessionId;
167333
- if (lastReceivedId > 0) {
167334
- queue2 = queue2.filter((notification) => !(notification.id <= lastReceivedId && matchesClient(notification)));
167335
- }
167336
- return queue2.filter((notification) => notification.id > lastReceivedId && matchesClient(notification));
167337
- }
167338
- function isTuiConnected(sessionId) {
167339
- const now = Date.now();
167340
- if (sessionId !== undefined) {
167341
- const at = lastDrainAtBySession.get(sessionId) ?? 0;
167342
- return at > 0 && now - at < TUI_CONNECTED_WINDOW_MS;
167343
- }
167344
- return lastDrainAtAny > 0 && now - lastDrainAtAny < TUI_CONNECTED_WINDOW_MS;
167345
- }
167346
- var queue2, nextNotificationId = 1, lastDrainAtBySession, lastDrainAtAny = 0, TUI_CONNECTED_WINDOW_MS = 3000;
167347
- var init_rpc_notifications = __esm(() => {
167348
- queue2 = [];
167349
- lastDrainAtBySession = new Map;
167350
- });
167351
-
167352
167662
  // src/features/magic-context/compartment-embedding.ts
167353
167663
  async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
167354
167664
  if (compartments.length === 0)
@@ -170900,7 +171210,7 @@ function buildToolArcs(messages) {
170900
171210
  }
170901
171211
  return arcs.sort((a, b) => a.invOrdinal - b.invOrdinal || (a.resOrdinal ?? Number.MAX_SAFE_INTEGER) - (b.resOrdinal ?? Number.MAX_SAFE_INTEGER));
170902
171212
  }
170903
- function fenceBoundaryForToolArcs(candidate, arcs, lastCompartmentEndOrdinal) {
171213
+ function fenceBoundaryForToolArcs(candidate, arcs, lastCompartmentEndOrdinal, recentOpenArcCutoff) {
170904
171214
  let boundary = candidate;
170905
171215
  for (const arc of arcs) {
170906
171216
  if (arc.resOrdinal !== null) {
@@ -170909,6 +171219,8 @@ function fenceBoundaryForToolArcs(candidate, arcs, lastCompartmentEndOrdinal) {
170909
171219
  }
170910
171220
  continue;
170911
171221
  }
171222
+ if (arc.invOrdinal < recentOpenArcCutoff)
171223
+ continue;
170912
171224
  if (arc.invOrdinal >= lastCompartmentEndOrdinal + 1 && arc.invOrdinal < boundary) {
170913
171225
  return arc.invOrdinal;
170914
171226
  }
@@ -171148,7 +171460,7 @@ function semanticSnapBoundary(args) {
171148
171460
  return snapped;
171149
171461
  }
171150
171462
  function applyHeadCap(args) {
171151
- const { index, protectedTailStart, offset, arcs, capTokens } = args;
171463
+ const { index, protectedTailStart, offset, arcs, capTokens, recentOpenArcCutoff } = args;
171152
171464
  if (offset >= protectedTailStart)
171153
171465
  return { eligibleEndOrdinal: offset, oversizeAtomicUnit: false };
171154
171466
  let end = index.findHeadEndForCap(offset, protectedTailStart, capTokens);
@@ -171156,7 +171468,7 @@ function applyHeadCap(args) {
171156
171468
  for (const arc of arcs) {
171157
171469
  const resOrdinal = arc.resOrdinal;
171158
171470
  if (resOrdinal === null) {
171159
- if (arc.invOrdinal >= offset && arc.invOrdinal < end) {
171471
+ if (arc.invOrdinal >= recentOpenArcCutoff && arc.invOrdinal >= offset && arc.invOrdinal < end) {
171160
171472
  end = Math.min(end, arc.invOrdinal);
171161
171473
  }
171162
171474
  continue;
@@ -171223,7 +171535,14 @@ function resolveProtectedTailBoundary(ctx) {
171223
171535
  }
171224
171536
  if (ctx.mode === "manual-full-recomp") {
171225
171537
  const arcs2 = buildToolArcs(messages);
171226
- const firstOpenArc = arcs2.find((arc) => arc.resOrdinal === null && arc.invOrdinal >= offset);
171538
+ const recompTarget = deriveProtectedTailTokenTarget({
171539
+ contextLimit: ctx.contextLimit,
171540
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
171541
+ usagePercentage: 0,
171542
+ triggerBudget: ctx.triggerBudget
171543
+ });
171544
+ const recentOpenArcCutoff2 = index.findSuffixStartForTokens(recompTarget.N);
171545
+ const firstOpenArc = arcs2.find((arc) => arc.resOrdinal === null && arc.invOrdinal >= offset && arc.invOrdinal >= recentOpenArcCutoff2);
171227
171546
  const protectedTailStart2 = firstOpenArc?.invOrdinal ?? rawMessageCount + 1;
171228
171547
  const rawRangeFingerprint2 = computeRawRangeFingerprint(messages, offset, protectedTailStart2);
171229
171548
  return {
@@ -171265,13 +171584,14 @@ function resolveProtectedTailBoundary(ctx) {
171265
171584
  const scaledN = ctx.emergencyTailScale ? Math.max(1, Math.floor(target.N * ctx.emergencyTailScale)) : target.N;
171266
171585
  const arcs = buildToolArcs(messages);
171267
171586
  let boundary = index.findSuffixStartForTokens(scaledN);
171587
+ const recentOpenArcCutoff = boundary;
171268
171588
  let boundaryReason = boundary === 1 ? "whole-session-smaller-than-tail" : "size-walk";
171269
171589
  const tokenAtBoundary = index.tokenForOrdinal(boundary);
171270
171590
  if (boundary <= rawMessageCount && tokenAtBoundary > Math.max(2 * scaledN, 64000) && boundary < rawMessageCount) {
171271
171591
  boundary += 1;
171272
171592
  boundaryReason = "huge-message-exception";
171273
171593
  }
171274
- boundary = fenceBoundaryForToolArcs(boundary, arcs, ctx.lastCompartmentEndOrdinal);
171594
+ boundary = fenceBoundaryForToolArcs(boundary, arcs, ctx.lastCompartmentEndOrdinal, recentOpenArcCutoff);
171275
171595
  const snapped = semanticSnapBoundary({
171276
171596
  messages,
171277
171597
  index,
@@ -171281,7 +171601,7 @@ function resolveProtectedTailBoundary(ctx) {
171281
171601
  });
171282
171602
  if (snapped !== boundary)
171283
171603
  boundaryReason = "semantic-snap";
171284
- boundary = fenceBoundaryForToolArcs(snapped, arcs, ctx.lastCompartmentEndOrdinal);
171604
+ boundary = fenceBoundaryForToolArcs(snapped, arcs, ctx.lastCompartmentEndOrdinal, recentOpenArcCutoff);
171285
171605
  let runtimeFloor = offset;
171286
171606
  if (ctx.migrationFloorActive)
171287
171607
  runtimeFloor = Math.max(runtimeFloor, ctx.priorBoundaryOrdinal);
@@ -171317,7 +171637,8 @@ function resolveProtectedTailBoundary(ctx) {
171317
171637
  offset,
171318
171638
  arcs,
171319
171639
  lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal,
171320
- capTokens: perRunCap
171640
+ capTokens: perRunCap,
171641
+ recentOpenArcCutoff
171321
171642
  });
171322
171643
  const rawRangeFingerprint = computeRawRangeFingerprint(messages, offset, head.eligibleEndOrdinal);
171323
171644
  return {
@@ -177274,15 +177595,15 @@ function shouldShowAnnouncement() {
177274
177595
  }
177275
177596
  return state.version !== ANNOUNCEMENT_VERSION;
177276
177597
  }
177277
- var ANNOUNCEMENT_VERSION = "0.24.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
177598
+ var ANNOUNCEMENT_VERSION = "0.25.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
177278
177599
  var init_announcement = __esm(() => {
177279
177600
  init_data_path();
177280
177601
  ANNOUNCEMENT_FEATURES = [
177281
- "Searchable session history: ctx_search can now find older discussion by meaning, not just keywords. New history is embedded automatically to backfill an EXISTING session's older history, run /ctx-embed-history once (it works in the background).",
177282
- "Cross-project workspaces: group related repos and share project memories across them, with per-category control over what's shared. Set them up in the dashboard's Workspaces panel.",
177283
- "Pi: fixed sessions overflowing the model context while still showing moderate usage Pi now sheds context before a tool-heavy turn overflows.",
177284
- "Fewer prompt-cache busts: doc edits, processed screenshots, and a rebuild-then-bust-again case no longer re-bill large prompt prefixes.",
177285
- "Setup wizard now lists your actual models with type-ahead instead of fixed recommendations, and explains the historian/dreamer roles (issue #144). Plus a GitHub Copilot tool-pairing fix (#135)."
177602
+ "Old tool output is now reclaimed automatically: once a file read / search / command output has gone a full execute cycle unused, it's dropped on the next one no need to call ctx_reduce for stale results.",
177603
+ "Recover anything that was dropped: ctx_expand({ message: N }) returns a dropped message's full content (every tool call's input + output) from storage. ctx_expand({ start, end, verbose: true }) lists a range message-by-message to find it.",
177604
+ "Searchable history made reliable: /ctx-embed shows embedding coverage and runs a resilient backfill (retries transient failures, no longer bails on the first hiccup); the active session now auto-embeds in the background. ctx_reduce guidance also reframed as deferred + recoverable so models trim spent output earlier.",
177605
+ "Pi: fixed /ctx-dream (was failing with 'Unknown named parameter') and local-embedding load failures on Windows/Desktop (#151, #128).",
177606
+ "Runaway background agents on weak/local models are now capped and force-stopped (#154, #152). Plus several prompt-cache busts removed."
177286
177607
  ];
177287
177608
  });
177288
177609
 
@@ -178077,9 +178398,9 @@ function getMagicContextBuiltinCommands() {
178077
178398
  template: "ctx-dream",
178078
178399
  description: "Run the hidden dreamer maintenance pass for this project now"
178079
178400
  },
178080
- "ctx-embed-history": {
178081
- template: "ctx-embed-history",
178082
- description: "Embed all of this session's history compartments for semantic search, in one pass"
178401
+ "ctx-embed": {
178402
+ template: "ctx-embed",
178403
+ description: "Embedding status, or start/pause history compartment embedding (start | pause)"
178083
178404
  }
178084
178405
  };
178085
178406
  }
@@ -179547,7 +179868,7 @@ function enqueueDream(db, projectIdentity, reason, force = false) {
179547
179868
  return db.transaction(() => {
179548
179869
  if (!hasActiveDreamLease(db)) {
179549
179870
  const staleThresholdMs = force ? 2 * 60 * 1000 : 120 * 60 * 1000;
179550
- db.prepare("DELETE FROM dream_queue WHERE project_path = ? AND started_at IS NOT NULL AND started_at < ?").run([projectIdentity, now - staleThresholdMs]);
179871
+ db.prepare("DELETE FROM dream_queue WHERE project_path = ? AND started_at IS NOT NULL AND started_at < ?").run(projectIdentity, now - staleThresholdMs);
179551
179872
  }
179552
179873
  const existing = db.prepare("SELECT id FROM dream_queue WHERE project_path = ?").get(projectIdentity);
179553
179874
  if (existing) {
@@ -181242,120 +181563,7 @@ ${body}` : subject;
181242
181563
  init_logger();
181243
181564
  init_embedding();
181244
181565
  init_storage_git_commit_embeddings();
181245
-
181246
- // src/features/magic-context/git-commits/storage-git-commits.ts
181247
- init_logger();
181248
- var insertStatements = new WeakMap;
181249
- var existingShasStatements = new WeakMap;
181250
- var projectCountStatements = new WeakMap;
181251
- var evictStatements = new WeakMap;
181252
- var evictOverflowStatements = new WeakMap;
181253
- var latestCommitTimeStatements = new WeakMap;
181254
- function getInsertStatement(db) {
181255
- let stmt = insertStatements.get(db);
181256
- if (!stmt) {
181257
- stmt = db.prepare(`INSERT INTO git_commits (sha, project_path, short_sha, message, author, committed_at, indexed_at)
181258
- VALUES (?, ?, ?, ?, ?, ?, ?)
181259
- ON CONFLICT(sha) DO UPDATE SET
181260
- project_path = excluded.project_path,
181261
- short_sha = excluded.short_sha,
181262
- message = excluded.message,
181263
- author = excluded.author,
181264
- committed_at = excluded.committed_at,
181265
- indexed_at = excluded.indexed_at
181266
- WHERE git_commits.message != excluded.message`);
181267
- insertStatements.set(db, stmt);
181268
- }
181269
- return stmt;
181270
- }
181271
- function getExistingShasStatement(db) {
181272
- let stmt = existingShasStatements.get(db);
181273
- if (!stmt) {
181274
- stmt = db.prepare("SELECT sha FROM git_commits WHERE project_path = ?");
181275
- existingShasStatements.set(db, stmt);
181276
- }
181277
- return stmt;
181278
- }
181279
- function getProjectCountStatement(db) {
181280
- let stmt = projectCountStatements.get(db);
181281
- if (!stmt) {
181282
- stmt = db.prepare("SELECT COUNT(*) AS count FROM git_commits WHERE project_path = ?");
181283
- projectCountStatements.set(db, stmt);
181284
- }
181285
- return stmt;
181286
- }
181287
- function getLatestCommitTimeStatement(db) {
181288
- let stmt = latestCommitTimeStatements.get(db);
181289
- if (!stmt) {
181290
- stmt = db.prepare("SELECT MAX(committed_at) AS latest FROM git_commits WHERE project_path = ?");
181291
- latestCommitTimeStatements.set(db, stmt);
181292
- }
181293
- return stmt;
181294
- }
181295
- function getEvictOverflowStatement(db) {
181296
- let stmt = evictOverflowStatements.get(db);
181297
- if (!stmt) {
181298
- stmt = db.prepare(`DELETE FROM git_commits
181299
- WHERE rowid IN (
181300
- SELECT rowid FROM git_commits
181301
- WHERE project_path = ?
181302
- ORDER BY committed_at DESC, sha DESC
181303
- LIMIT -1 OFFSET ?
181304
- )`);
181305
- evictOverflowStatements.set(db, stmt);
181306
- }
181307
- return stmt;
181308
- }
181309
- function upsertCommits(db, projectPath, commits) {
181310
- if (commits.length === 0)
181311
- return { inserted: 0, updated: 0 };
181312
- const existing = new Set;
181313
- for (const row of getExistingShasStatement(db).all(projectPath)) {
181314
- existing.add(row.sha);
181315
- }
181316
- let inserted = 0;
181317
- let updated = 0;
181318
- const now = Date.now();
181319
- const insertStmt = getInsertStatement(db);
181320
- db.transaction(() => {
181321
- for (const commit of commits) {
181322
- const result = insertStmt.run(commit.sha, projectPath, commit.shortSha, commit.message, commit.author, commit.committedAtMs, now);
181323
- if (result.changes > 0) {
181324
- if (existing.has(commit.sha)) {
181325
- updated++;
181326
- } else {
181327
- inserted++;
181328
- existing.add(commit.sha);
181329
- }
181330
- }
181331
- }
181332
- })();
181333
- return { inserted, updated };
181334
- }
181335
- function getCommitCount(db, projectPath) {
181336
- const row = getProjectCountStatement(db).get(projectPath);
181337
- return row?.count ?? 0;
181338
- }
181339
- function getLatestIndexedCommitTimeMs(db, projectPath) {
181340
- const row = getLatestCommitTimeStatement(db).get(projectPath);
181341
- return row?.latest ?? null;
181342
- }
181343
- function enforceProjectCap(db, projectPath, maxCommits) {
181344
- if (maxCommits <= 0)
181345
- return 0;
181346
- const count = getCommitCount(db, projectPath);
181347
- if (count <= maxCommits)
181348
- return 0;
181349
- getEvictOverflowStatement(db).run(projectPath, maxCommits);
181350
- const after = getCommitCount(db, projectPath);
181351
- const evicted = Math.max(0, count - after);
181352
- if (evicted > 0) {
181353
- log(`[git-commits] evicted ${evicted} oldest commits for project ${projectPath} (cap=${maxCommits}, was=${count})`);
181354
- }
181355
- return evicted;
181356
- }
181357
-
181358
- // src/features/magic-context/git-commits/indexer.ts
181566
+ init_storage_git_commits();
181359
181567
  var MS_PER_DAY = 24 * 60 * 60 * 1000;
181360
181568
  var EMBED_BATCH_SIZE = 16;
181361
181569
  var EMBED_MAX_PER_SWEEP = 500;
@@ -181593,6 +181801,7 @@ function searchGitCommitsSync(db, projectPath, query, options) {
181593
181801
 
181594
181802
  // src/features/magic-context/git-commits/index.ts
181595
181803
  init_storage_git_commit_embeddings();
181804
+ init_storage_git_commits();
181596
181805
  init_sweep_coordinator();
181597
181806
 
181598
181807
  // src/plugin/dream-timer.ts
@@ -183017,7 +183226,7 @@ function createMagicContextCommandHandler(deps) {
183017
183226
  const isAugCommand = (command) => command === "ctx-aug";
183018
183227
  const isDreamCommand = (command) => command === "ctx-dream";
183019
183228
  const isSessionUpgradeCommand = (command) => command === "ctx-session-upgrade";
183020
- const isEmbedHistoryCommand = (command) => command === "ctx-embed-history";
183229
+ const isEmbedCommand = (command) => command === "ctx-embed";
183021
183230
  return {
183022
183231
  "command.execute.before": async (input, _output, _params) => {
183023
183232
  const isStatus = isStatusCommand(input.command);
@@ -183026,8 +183235,8 @@ function createMagicContextCommandHandler(deps) {
183026
183235
  const isAug = isAugCommand(input.command);
183027
183236
  const isDream = isDreamCommand(input.command);
183028
183237
  const isSessionUpgrade = isSessionUpgradeCommand(input.command);
183029
- const isEmbedHistory = isEmbedHistoryCommand(input.command);
183030
- if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade && !isEmbedHistory) {
183238
+ const isEmbed = isEmbedCommand(input.command);
183239
+ if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade && !isEmbed) {
183031
183240
  return;
183032
183241
  }
183033
183242
  const sessionId = input.sessionID;
@@ -183040,15 +183249,50 @@ function createMagicContextCommandHandler(deps) {
183040
183249
  await executeDreaming(deps, sessionId);
183041
183250
  return;
183042
183251
  }
183043
- if (isEmbedHistory) {
183044
- const summary = deps.executeEmbedHistory ? await deps.executeEmbedHistory(sessionId) : "Semantic embedding is not configured for this project, so there is nothing to embed.";
183045
- await deps.sendNotification(sessionId, summary, {});
183046
- throwSentinel(input.command);
183252
+ if (isEmbed) {
183253
+ const sub = input.arguments.trim().toLowerCase();
183254
+ if (sub === "pause") {
183255
+ const summary = deps.pauseEmbedDrain ? deps.pauseEmbedDrain(sessionId) : "Embedding pause is unavailable.";
183256
+ if (isTuiConnected(sessionId)) {
183257
+ pushNotification("action", { action: "show-result-dialog", title: "Embed", message: summary }, sessionId);
183258
+ } else {
183259
+ await deps.sendNotification(sessionId, summary, {});
183260
+ }
183261
+ throwSentinel(input.command);
183262
+ }
183263
+ if (sub === "start") {
183264
+ const summary = deps.executeEmbedHistory ? await deps.executeEmbedHistory(sessionId) : "Semantic embedding is not configured for this project, so there is nothing to embed.";
183265
+ if (isTuiConnected(sessionId)) {
183266
+ pushNotification("action", { action: "show-result-dialog", title: "Embed", message: summary }, sessionId);
183267
+ } else {
183268
+ await deps.sendNotification(sessionId, summary, {});
183269
+ }
183270
+ throwSentinel(input.command);
183271
+ }
183272
+ if (sub !== "") {
183273
+ await deps.sendNotification(sessionId, "Usage: `/ctx-embed` (status), `/ctx-embed start`, or `/ctx-embed pause`.", {});
183274
+ throwSentinel(input.command);
183275
+ }
183276
+ if (isTuiConnected(sessionId)) {
183277
+ pushNotification("action", { action: "show-embed-dialog" }, sessionId);
183278
+ sessionLog(sessionId, "command ctx-embed: pushed show-embed-dialog to TUI");
183279
+ throwSentinel(input.command);
183280
+ }
183281
+ result = deps.getEmbedStatusText ? `## Embedding Status
183282
+
183283
+ ${deps.getEmbedStatusText(sessionId)}` : `## Embedding Status
183284
+
183285
+ Embedding status is unavailable.`;
183047
183286
  }
183048
183287
  if (isFlush) {
183049
183288
  result = executeFlush(deps.db, sessionId);
183050
183289
  clearCachedM0M1(deps.db, sessionId);
183051
183290
  deps.onFlush?.(sessionId);
183291
+ if (isTuiConnected(sessionId)) {
183292
+ pushNotification("action", { action: "show-flush-dialog", message: result }, sessionId);
183293
+ sessionLog(sessionId, "command ctx-flush: pushed show-flush-dialog to TUI");
183294
+ throwSentinel(input.command);
183295
+ }
183052
183296
  }
183053
183297
  if (isStatus) {
183054
183298
  if (isTuiConnected(sessionId)) {
@@ -183175,6 +183419,34 @@ ${snap.error}`;
183175
183419
  // src/hooks/magic-context/hook.ts
183176
183420
  init_derive_budgets();
183177
183421
 
183422
+ // src/hooks/magic-context/embed-session-state.ts
183423
+ var embedPauseBySession = new Set;
183424
+ var embedRunStateBySession = new Map;
183425
+ var autoEmbedAttemptedBySession = new Set;
183426
+ function getEmbedDrainUiStatus(sessionId, progress) {
183427
+ if (embedPauseBySession.has(sessionId)) {
183428
+ return { status: "paused" };
183429
+ }
183430
+ if (progress?.kind === "embed" && progress.phase === "recomp") {
183431
+ return { status: "running" };
183432
+ }
183433
+ if (progress?.kind === "embed" && (progress.phase === "failed" || progress.phase === "skipped") && progress.message) {
183434
+ if (/provider/i.test(progress.message)) {
183435
+ return { status: "stopped", detail: progress.message };
183436
+ }
183437
+ }
183438
+ return { status: "idle" };
183439
+ }
183440
+ function clearEmbedSessionState(sessionId) {
183441
+ embedPauseBySession.delete(sessionId);
183442
+ const ctrl = embedRunStateBySession.get(sessionId);
183443
+ if (ctrl) {
183444
+ ctrl.abort();
183445
+ embedRunStateBySession.delete(sessionId);
183446
+ }
183447
+ autoEmbedAttemptedBySession.delete(sessionId);
183448
+ }
183449
+
183178
183450
  // src/features/magic-context/message-index-async.ts
183179
183451
  init_logger();
183180
183452
  await init_message_index();
@@ -183535,6 +183807,13 @@ function computePressure(input) {
183535
183807
  function approxThousands(tokens) {
183536
183808
  return `${Math.round(tokens / 1000)}k`;
183537
183809
  }
183810
+ function formatOldestReclaimableHint(hint) {
183811
+ if (!hint || hint.length === 0)
183812
+ return "";
183813
+ const rendered = hint.slice(0, 4).map((tag) => `§${tag.tagNumber}§ ${tag.toolName ?? "tool"}`).join(" · ");
183814
+ return rendered.length > 0 ? `
183815
+ oldest reclaimable: ${rendered}.` : "";
183816
+ }
183538
183817
  var CHANNEL2_USABLE_FRACTION = 1 / 3;
183539
183818
  var CHANNEL2_MIN_RECLAIMABLE = 1e4;
183540
183819
  function shouldTriggerChannel2(input) {
@@ -183544,14 +183823,16 @@ function shouldTriggerChannel2(input) {
183544
183823
  return true;
183545
183824
  return input.reclaimableTokens >= input.usableTokens * CHANNEL2_USABLE_FRACTION;
183546
183825
  }
183547
- function buildChannel2Reminder(undroppedTokens) {
183826
+ function buildChannel2Reminder(undroppedTokens, hint) {
183548
183827
  const amount = approxThousands(undroppedTokens);
183828
+ const hintText = formatOldestReclaimableHint(hint);
183549
183829
  return `<system-reminder>
183550
- ` + `Routine context housekeeping is near: a large span of this session will be comparted soon, ` + `and ~${amount} tokens of tool output remain unreduced. Drop spent outputs with ctx_reduce ` + `first so the archived span is the part that matters.
183830
+ ` + `Routine context housekeeping is near: a large span of this session will be comparted soon, ` + `and ~${amount} tokens of tool output remain unreduced. Drop spent outputs with ctx_reduce ` + `first so the archived span is the part that matters.${hintText}
183551
183831
  ` + `</system-reminder>`;
183552
183832
  }
183553
- function buildChannel1Reminder(level, undroppedTokens) {
183833
+ function buildChannel1Reminder(level, undroppedTokens, hint) {
183554
183834
  const amount = approxThousands(undroppedTokens);
183835
+ const hintText = formatOldestReclaimableHint(hint);
183555
183836
  let body;
183556
183837
  switch (level) {
183557
183838
  case "gentle":
@@ -183567,7 +183848,7 @@ function buildChannel1Reminder(level, undroppedTokens) {
183567
183848
  return `
183568
183849
 
183569
183850
  <system-reminder>
183570
- ${body}
183851
+ ${body}${hintText}
183571
183852
  </system-reminder>`;
183572
183853
  }
183573
183854
 
@@ -183621,10 +183902,10 @@ async function maybeDeliverChannel2(sessionId, deps) {
183621
183902
  try {
183622
183903
  const client3 = getLiveServerClient(serverUrl, deps.directory);
183623
183904
  const promptContext = await resolvePromptContext(client3, sessionId);
183624
- const reminder = buildChannel2Reminder(deps.reclaimableTokens);
183905
+ const reminder = buildChannel2Reminder(deps.reclaimableTokens, deps.oldestReclaimableToolTags);
183625
183906
  const body = {
183626
183907
  noReply: false,
183627
- parts: [{ type: "text", text: reminder }]
183908
+ parts: [{ type: "text", text: reminder, synthetic: true }]
183628
183909
  };
183629
183910
  if (promptContext?.agent)
183630
183911
  body.agent = promptContext.agent;
@@ -184082,7 +184363,8 @@ function applyCavemanCleanup(sessionId, db, targets, tags, config2) {
184082
184363
  const result = {
184083
184364
  compressedToLite: 0,
184084
184365
  compressedToFull: 0,
184085
- compressedToUltra: 0
184366
+ compressedToUltra: 0,
184367
+ mutatedTextTags: 0
184086
184368
  };
184087
184369
  if (!config2.enabled)
184088
184370
  return result;
@@ -184123,7 +184405,9 @@ function applyCavemanCleanup(sessionId, db, targets, tags, config2) {
184123
184405
  const target = targets.get(tag.tagNumber);
184124
184406
  if (!target)
184125
184407
  continue;
184126
- target.setContent(compressed);
184408
+ const didMutate = target.setContent(compressed);
184409
+ if (didMutate)
184410
+ result.mutatedTextTags += 1;
184127
184411
  updateCavemanDepth(db, sessionId, tag.tagNumber, targetDepth);
184128
184412
  if (targetDepth === DEPTH_LITE)
184129
184413
  result.compressedToLite += 1;
@@ -184711,28 +184995,6 @@ function stripInlineThinking(messages, messageTagNumbers, clearReasoningAge) {
184711
184995
  }
184712
184996
  return stripped;
184713
184997
  }
184714
- function truncateErroredTools(messages, watermark, messageTagNumbers) {
184715
- let truncated = 0;
184716
- for (let i = 0;i < messages.length; i++) {
184717
- const maxTag = messageTagNumbers.get(messages[i]) ?? 0;
184718
- if (maxTag > watermark) {
184719
- continue;
184720
- }
184721
- for (const part of messages[i].parts) {
184722
- if (!isRecord(part) || part.type !== "tool" || !isRecord(part.state)) {
184723
- continue;
184724
- }
184725
- if (part.state.status !== "error") {
184726
- continue;
184727
- }
184728
- if (typeof part.state.error === "string" && part.state.error.length > 100) {
184729
- part.state.error = `${part.state.error.slice(0, 100)}... [truncated]`;
184730
- truncated++;
184731
- }
184732
- }
184733
- }
184734
- return truncated;
184735
- }
184736
184998
  var REASONING_IGNORED_PART_TYPES = new Set([
184737
184999
  "step-start",
184738
185000
  "step-finish",
@@ -185150,78 +185412,11 @@ function appendReminderToUserMessage(message, reminder) {
185150
185412
 
185151
185413
  // src/hooks/magic-context/apply-operations.ts
185152
185414
  await init_storage();
185153
-
185154
- // src/hooks/magic-context/system-injection-stripper.ts
185155
- var SYSTEM_INJECTION_MARKERS = [
185156
- "<!-- OMO_INTERNAL_INITIATOR -->",
185157
- "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
185158
- "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
185159
- "[Category+Skill Reminder]",
185160
- "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
185161
- "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
185162
- "[EMERGENCY CONTEXT WINDOW WARNING]",
185163
- "Unstable background agent appears idle",
185164
- "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
185165
- ];
185166
- var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
185167
- var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
185168
- function stripSystemInjection(text) {
185169
- let hasInjection = false;
185170
- for (const marker of SYSTEM_INJECTION_MARKERS) {
185171
- if (text.includes(marker)) {
185172
- hasInjection = true;
185173
- break;
185174
- }
185175
- }
185176
- if (SYSTEM_REMINDER_REGEX.test(text))
185177
- hasInjection = true;
185178
- SYSTEM_REMINDER_REGEX.lastIndex = 0;
185179
- if (!hasInjection)
185180
- return null;
185181
- let cleaned = text;
185182
- cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
185183
- cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
185184
- cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
185185
- for (const marker of SYSTEM_INJECTION_MARKERS) {
185186
- if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
185187
- continue;
185188
- const idx = cleaned.indexOf(marker);
185189
- if (idx === -1)
185190
- continue;
185191
- const blockEnd = cleaned.indexOf(`
185192
-
185193
- `, idx + marker.length);
185194
- cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
185195
- }
185196
- return cleaned.trim();
185197
- }
185198
-
185199
- // src/hooks/magic-context/apply-operations.ts
185200
- init_tag_part_guards();
185201
- var USER_DROP_PREVIEW_CHARS = 250;
185202
185415
  var RECENT_TOOL_SKELETON_WINDOW = 20;
185203
- function buildReplacementContent(tagId, target) {
185204
- const role = target.message?.info.role;
185205
- if (role !== "user") {
185206
- return `[dropped §${tagId}§]`;
185207
- }
185208
- const currentContent = target.getContent?.() ?? "";
185209
- const strippedInjection = stripSystemInjection(currentContent);
185210
- if (strippedInjection !== null && stripTagPrefix(strippedInjection).trim().length === 0) {
185211
- return `[dropped §${tagId}§]`;
185212
- }
185213
- const originalText = stripTagPrefix(currentContent);
185214
- if (originalText.length <= USER_DROP_PREVIEW_CHARS) {
185215
- return `[truncated §${tagId}§]
185216
- ${originalText}`;
185217
- }
185218
- const hardCut = originalText.slice(0, USER_DROP_PREVIEW_CHARS);
185219
- const softCutIndex = hardCut.search(/\s\S*$/);
185220
- const preview = softCutIndex > USER_DROP_PREVIEW_CHARS - 30 ? hardCut.slice(0, softCutIndex) : hardCut;
185221
- return `[truncated §${tagId}§]
185222
- ${preview}…`;
185223
- }
185224
- function applyPendingOperations(sessionId, db, targets, protectedTags = 0, preloadedTags, preloadedPendingOps) {
185416
+ function buildReplacementContent(tagId) {
185417
+ return `[dropped §${tagId}§]`;
185418
+ }
185419
+ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, preloadedTags, preloadedPendingOps, syntheticPendingOps = []) {
185225
185420
  let didMutateMessage = false;
185226
185421
  db.transaction(() => {
185227
185422
  const tags = preloadedTags ?? getTagsBySession(db, sessionId);
@@ -185229,11 +185424,16 @@ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, prelo
185229
185424
  const tagTypeById = new Map(tags.map((tag) => [tag.tagNumber, tag.type]));
185230
185425
  const protectedTagIds = protectedTags > 0 ? new Set(tags.filter((tag) => tag.status === "active").map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, protectedTags)) : new Set;
185231
185426
  const pendingOps = preloadedPendingOps ?? getPendingOps(db, sessionId);
185427
+ const opsToApply = [
185428
+ ...pendingOps.map((op) => ({ op, synthetic: false })),
185429
+ ...syntheticPendingOps.map((op) => ({ op, synthetic: true }))
185430
+ ];
185232
185431
  const skeletonWindow = new Set(tags.filter((tag) => tag.type === "tool").map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, RECENT_TOOL_SKELETON_WINDOW));
185233
- for (const pendingOp of pendingOps) {
185432
+ for (const { op: pendingOp, synthetic } of opsToApply) {
185234
185433
  const tagStatus = tagStatusById.get(pendingOp.tagId);
185235
185434
  if (tagStatus === "compacted" || tagStatus === "dropped") {
185236
- removePendingOp(db, sessionId, pendingOp.tagId);
185435
+ if (!synthetic)
185436
+ removePendingOp(db, sessionId, pendingOp.tagId);
185237
185437
  continue;
185238
185438
  }
185239
185439
  if (protectedTagIds.has(pendingOp.tagId)) {
@@ -185241,33 +185441,46 @@ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, prelo
185241
185441
  }
185242
185442
  const target = targets.get(pendingOp.tagId);
185243
185443
  const isToolTag = tagTypeById.get(pendingOp.tagId) === "tool";
185444
+ if (synthetic) {
185445
+ if (!isToolTag || target?.canDrop?.() !== true)
185446
+ continue;
185447
+ }
185448
+ let shouldPersistDrop = false;
185244
185449
  if (isToolTag) {
185245
185450
  if (skeletonWindow.has(pendingOp.tagId)) {
185246
185451
  const truncResult = target?.truncate?.() ?? "absent";
185247
- if (truncResult === "incomplete") {
185452
+ if (truncResult === "incomplete" || synthetic && truncResult !== "truncated") {
185248
185453
  continue;
185249
185454
  }
185250
185455
  if (truncResult === "truncated") {
185251
185456
  didMutateMessage = true;
185252
185457
  }
185253
185458
  updateTagDropMode(db, sessionId, pendingOp.tagId, "truncated");
185459
+ shouldPersistDrop = true;
185254
185460
  } else {
185255
185461
  const dropResult = target?.drop?.() ?? "absent";
185256
- if (dropResult === "incomplete") {
185462
+ if (dropResult === "incomplete" || synthetic && dropResult !== "removed") {
185257
185463
  continue;
185258
185464
  }
185259
185465
  if (dropResult === "removed") {
185260
185466
  didMutateMessage = true;
185261
185467
  }
185262
185468
  updateTagDropMode(db, sessionId, pendingOp.tagId, "full");
185469
+ shouldPersistDrop = true;
185263
185470
  }
185264
185471
  } else if (target) {
185265
- const changed = target.setContent(buildReplacementContent(pendingOp.tagId, target));
185472
+ const changed = target.setContent(buildReplacementContent(pendingOp.tagId));
185266
185473
  if (changed)
185267
185474
  didMutateMessage = true;
185475
+ shouldPersistDrop = true;
185476
+ } else if (!synthetic) {
185477
+ shouldPersistDrop = true;
185268
185478
  }
185479
+ if (!shouldPersistDrop)
185480
+ continue;
185269
185481
  updateTagStatus(db, sessionId, pendingOp.tagId, "dropped");
185270
- removePendingOp(db, sessionId, pendingOp.tagId);
185482
+ if (!synthetic)
185483
+ removePendingOp(db, sessionId, pendingOp.tagId);
185271
185484
  }
185272
185485
  })();
185273
185486
  return didMutateMessage;
@@ -185291,7 +185504,7 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
185291
185504
  }
185292
185505
  }
185293
185506
  } else if (target) {
185294
- const changed = target.setContent(buildReplacementContent(tag.tagNumber, target));
185507
+ const changed = target.setContent(buildReplacementContent(tag.tagNumber));
185295
185508
  if (changed)
185296
185509
  didMutateMessage = true;
185297
185510
  }
@@ -185808,7 +186021,7 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
185808
186021
  logTransformTiming(sessionId, "tag.saveSource", performance.now() - accSaveSource);
185809
186022
  for (const [compositeKey, tagId] of toolTagByCallId) {
185810
186023
  const thinkingParts = toolThinkingByCallId.get(compositeKey) ?? [];
185811
- targets.set(tagId, createToolDropTarget(compositeKey, thinkingParts, toolCallIndex, batch));
186024
+ targets.set(tagId, createToolDropTarget(compositeKey, thinkingParts, toolCallIndex, batch, tagId));
185812
186025
  }
185813
186026
  const hasRecentReduceCall = lastReduceMessageIndex >= 0 && messages.length - lastReduceMessageIndex <= RECENT_REDUCE_LOOKBACK;
185814
186027
  return {
@@ -186916,6 +187129,51 @@ function planEmergencyDrop(input) {
186916
187129
  };
186917
187130
  }
186918
187131
 
187132
+ // src/hooks/magic-context/system-injection-stripper.ts
187133
+ var SYSTEM_INJECTION_MARKERS = [
187134
+ "<!-- OMO_INTERNAL_INITIATOR -->",
187135
+ "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
187136
+ "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
187137
+ "[Category+Skill Reminder]",
187138
+ "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
187139
+ "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
187140
+ "[EMERGENCY CONTEXT WINDOW WARNING]",
187141
+ "Unstable background agent appears idle",
187142
+ "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
187143
+ ];
187144
+ var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
187145
+ var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
187146
+ function stripSystemInjection(text) {
187147
+ let hasInjection = false;
187148
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
187149
+ if (text.includes(marker)) {
187150
+ hasInjection = true;
187151
+ break;
187152
+ }
187153
+ }
187154
+ if (SYSTEM_REMINDER_REGEX.test(text))
187155
+ hasInjection = true;
187156
+ SYSTEM_REMINDER_REGEX.lastIndex = 0;
187157
+ if (!hasInjection)
187158
+ return null;
187159
+ let cleaned = text;
187160
+ cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
187161
+ cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
187162
+ cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
187163
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
187164
+ if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
187165
+ continue;
187166
+ const idx = cleaned.indexOf(marker);
187167
+ if (idx === -1)
187168
+ continue;
187169
+ const blockEnd = cleaned.indexOf(`
187170
+
187171
+ `, idx + marker.length);
187172
+ cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
187173
+ }
187174
+ return cleaned.trim();
187175
+ }
187176
+
186919
187177
  // src/hooks/magic-context/heuristic-cleanup.ts
186920
187178
  init_tag_part_guards();
186921
187179
  var DEDUP_SAFE_TOOLS = new Set([
@@ -187043,7 +187301,9 @@ function applyHeuristicCleanup(sessionId, db, targets, messageTagNumbers, config
187043
187301
  continue;
187044
187302
  updateTagDropMode(db, sessionId, tag.tagNumber, "full");
187045
187303
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
187046
- deduplicatedTools++;
187304
+ if (result === "removed" || result === "truncated") {
187305
+ deduplicatedTools++;
187306
+ }
187047
187307
  }
187048
187308
  }
187049
187309
  })();
@@ -187052,6 +187312,7 @@ function applyHeuristicCleanup(sessionId, db, targets, messageTagNumbers, config
187052
187312
  sessionLog(sessionId, `heuristic cleanup: dropped ${droppedTools} tool tags, deduplicated ${deduplicatedTools} tool calls, dropped ${droppedInjections} system injections`);
187053
187313
  }
187054
187314
  let compressedTextTags = 0;
187315
+ let mutatedTextTags = 0;
187055
187316
  if (config2.caveman?.enabled) {
187056
187317
  const cavemanResult = applyCavemanCleanup(sessionId, db, targets, tags, {
187057
187318
  enabled: true,
@@ -187059,8 +187320,15 @@ function applyHeuristicCleanup(sessionId, db, targets, messageTagNumbers, config
187059
187320
  protectedTags: config2.protectedTags
187060
187321
  });
187061
187322
  compressedTextTags = cavemanResult.compressedToLite + cavemanResult.compressedToFull + cavemanResult.compressedToUltra;
187323
+ mutatedTextTags = cavemanResult.mutatedTextTags;
187062
187324
  }
187063
- return { droppedTools, deduplicatedTools, droppedInjections, compressedTextTags };
187325
+ return {
187326
+ droppedTools,
187327
+ deduplicatedTools,
187328
+ droppedInjections,
187329
+ compressedTextTags,
187330
+ mutatedTextTags
187331
+ };
187064
187332
  }
187065
187333
  function extractToolInfo(part) {
187066
187334
  if (part.type === "tool" && typeof part.tool === "string" && DEDUP_SAFE_TOOLS.has(part.tool)) {
@@ -187234,6 +187502,42 @@ function isTodoItem(value) {
187234
187502
  return typeof todo.content === "string" && typeof todo.status === "string" && (todo.priority === undefined || typeof todo.priority === "string");
187235
187503
  }
187236
187504
 
187505
+ // src/hooks/magic-context/tool-reclaim.ts
187506
+ await init_storage();
187507
+ function buildSyntheticToolReclaimOps(input) {
187508
+ const watermark = Math.max(0, input.watermark);
187509
+ if (watermark <= 0)
187510
+ return [];
187511
+ const realPendingTagIds = new Set((input.pendingOps ?? []).map((op) => op.tagId));
187512
+ const tags = getActiveTagsBySession(input.db, input.sessionId);
187513
+ const synthetic = [];
187514
+ for (const tag of tags) {
187515
+ if (tag.type !== "tool")
187516
+ continue;
187517
+ if (tag.status !== "active")
187518
+ continue;
187519
+ if (tag.tagNumber > watermark)
187520
+ continue;
187521
+ if (realPendingTagIds.has(tag.tagNumber))
187522
+ continue;
187523
+ if (input.targets.get(tag.tagNumber)?.canDrop?.() !== true)
187524
+ continue;
187525
+ synthetic.push({
187526
+ id: 0,
187527
+ sessionId: input.sessionId,
187528
+ tagId: tag.tagNumber,
187529
+ operation: "drop",
187530
+ queuedAt: 0
187531
+ });
187532
+ }
187533
+ return synthetic;
187534
+ }
187535
+ function advanceToolReclaimWatermarkToCurrentMax(db, sessionId) {
187536
+ const maxTagNumber = getMaxTagNumberBySession(db, sessionId);
187537
+ advanceToolReclaimWatermark(db, sessionId, maxTagNumber);
187538
+ return maxTagNumber;
187539
+ }
187540
+
187237
187541
  // src/hooks/magic-context/transform-postprocess-phase.ts
187238
187542
  var DEGRADE_CACHE_WARNING_THRESHOLD = 10;
187239
187543
  var degradedCacheCountBySession = new BoundedSessionMap(100);
@@ -187288,12 +187592,14 @@ async function runPostTransformPhase(args) {
187288
187592
  let deferredMaterializedSuccessfully = false;
187289
187593
  let heuristicsRanSuccessfully = false;
187290
187594
  let pendingOpsRanSuccessfully = false;
187595
+ let pendingOpsDidMutate = false;
187596
+ let heuristicOrReasoningDidMutate = false;
187291
187597
  try {
187292
187598
  if (shouldApplyPendingOps) {
187293
187599
  const applyReason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
187294
187600
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
187295
187601
  const tApply = performance.now();
187296
- applyPendingOperations(args.sessionId, args.db, args.targets, args.protectedTags, undefined, pendingOps);
187602
+ pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, args.targets, args.protectedTags, undefined, pendingOps);
187297
187603
  logTransformTiming(args.sessionId, "applyPendingOperations", tApply);
187298
187604
  }
187299
187605
  if (shouldRunHeuristics) {
@@ -187311,7 +187617,8 @@ async function runPostTransformPhase(args) {
187311
187617
  } : undefined,
187312
187618
  caveman: cavemanConfig
187313
187619
  }, heuristicTags);
187314
- logTransformTiming(args.sessionId, "applyHeuristicCleanup", t5, `droppedTools=${cleanup.droppedTools} deduplicatedTools=${cleanup.deduplicatedTools} droppedInjections=${cleanup.droppedInjections} compressedTextTags=${cleanup.compressedTextTags}`);
187620
+ logTransformTiming(args.sessionId, "applyHeuristicCleanup", t5, `droppedTools=${cleanup.droppedTools} deduplicatedTools=${cleanup.deduplicatedTools} droppedInjections=${cleanup.droppedInjections} compressedTextTags=${cleanup.compressedTextTags} mutatedTextTags=${cleanup.mutatedTextTags}`);
187621
+ const heuristicMutationCount = cleanup.droppedTools + cleanup.deduplicatedTools + cleanup.droppedInjections + cleanup.mutatedTextTags;
187315
187622
  const t7 = performance.now();
187316
187623
  const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
187317
187624
  if (canUseEmptySentinels) {
@@ -187337,6 +187644,7 @@ async function runPostTransformPhase(args) {
187337
187644
  }
187338
187645
  }
187339
187646
  logTransformTiming(args.sessionId, "clearOldReasoning", t7);
187647
+ heuristicOrReasoningDidMutate = heuristicMutationCount + clearedReasoning + strippedInline > 0;
187340
187648
  if (pendingMaterializationAtPassStart) {
187341
187649
  args.pendingMaterializationSessions.delete(args.sessionId);
187342
187650
  }
@@ -187347,7 +187655,31 @@ async function runPostTransformPhase(args) {
187347
187655
  if (args.schedulerDecision === "execute" && !materializationRequested) {
187348
187656
  updateSessionMeta(args.db, args.sessionId, { lastResponseTime: Date.now() });
187349
187657
  }
187658
+ const toolReclaimExecutePass = args.schedulerDecision === "execute";
187659
+ const alreadyMutatingThisPass = pendingOpsDidMutate || heuristicOrReasoningDidMutate;
187660
+ let autoReclaimTargetCount = 0;
187661
+ let autoReclaimDidMutate = false;
187662
+ if (toolReclaimExecutePass && alreadyMutatingThisPass && !emergencyDropEligible) {
187663
+ const syntheticPendingOps = buildSyntheticToolReclaimOps({
187664
+ db: args.db,
187665
+ sessionId: args.sessionId,
187666
+ targets: args.targets,
187667
+ watermark: args.sessionMeta.toolReclaimWatermark ?? 0,
187668
+ pendingOps
187669
+ });
187670
+ autoReclaimTargetCount = syntheticPendingOps.length;
187671
+ if (syntheticPendingOps.length > 0) {
187672
+ autoReclaimDidMutate = applyPendingOperations(args.sessionId, args.db, args.targets, args.protectedTags, undefined, [], syntheticPendingOps);
187673
+ }
187674
+ }
187350
187675
  args.batch?.finalize();
187676
+ if (toolReclaimExecutePass) {
187677
+ const maxTagNumber = advanceToolReclaimWatermarkToCurrentMax(args.db, args.sessionId);
187678
+ args.sessionMeta.toolReclaimWatermark = Math.max(args.sessionMeta.toolReclaimWatermark ?? 0, maxTagNumber);
187679
+ }
187680
+ if (autoReclaimTargetCount > 0) {
187681
+ sessionLog(args.sessionId, `tool reclaim auto-drop: targets=${autoReclaimTargetCount} mutated=${autoReclaimDidMutate}`);
187682
+ }
187351
187683
  logTransformTiming(args.sessionId, "batchFinalize:heuristics", performance.now());
187352
187684
  if (args.sessionMeta.lastTransformError !== null) {
187353
187685
  updateSessionMeta(args.db, args.sessionId, { lastTransformError: null });
@@ -187359,11 +187691,6 @@ async function runPostTransformPhase(args) {
187359
187691
  deferredMaterializedSuccessfully = true;
187360
187692
  heuristicsRanSuccessfully = true;
187361
187693
  }
187362
- if (args.watermark > 0) {
187363
- const tWatermarkCleanup = performance.now();
187364
- truncateErroredTools(args.messages, args.watermark, args.messageTagNumbers);
187365
- logTransformTiming(args.sessionId, "watermarkCleanup", tWatermarkCleanup);
187366
- }
187367
187694
  if (shouldApplyPendingOps) {
187368
187695
  pendingOpsRanSuccessfully = true;
187369
187696
  }
@@ -188390,6 +188717,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
188390
188717
  const executeThresholdTokens = Math.round((resolvedContextLimit ?? 0) * resolvedExecuteThresholdPct / 100);
188391
188718
  const usableTokens = Math.max(0, executeThresholdTokens - contextUsage.inputTokens + liveTailTokens);
188392
188719
  resetLastNudgeCycleIfTailShrank(db, sessionId, tailToolTokens);
188720
+ const oldestReclaimableToolTags = getOldestActiveUnprotectedToolTags(db, sessionId, deps.protectedTags);
188393
188721
  deps.channel1StateBySession.set(sessionId, {
188394
188722
  tailToolTokens,
188395
188723
  historyBudgetTokens: historyBudgetTokens ?? 0,
@@ -188398,9 +188726,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
188398
188726
  lastInputTokens: contextUsage.inputTokens,
188399
188727
  turnToolTokens: 0,
188400
188728
  usableTokens,
188401
- reducedSinceRefresh: false
188729
+ reducedSinceRefresh: false,
188730
+ oldestReclaimableToolTags
188402
188731
  });
188403
- const channel2MetricsKnown = fullFeatureMode && resolvedContextLimit !== undefined && resolvedContextLimit > 0 && resolvedExecuteThresholdPct > 0;
188732
+ const channel2MetricsKnown = resolvedContextLimit !== undefined && resolvedContextLimit > 0 && resolvedExecuteThresholdPct > 0;
188404
188733
  if (channel2MetricsKnown) {
188405
188734
  const channel2ShouldTrigger = shouldTriggerChannel2({
188406
188735
  reclaimableTokens: tailToolTokens,
@@ -188424,6 +188753,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
188424
188753
  }
188425
188754
  const elapsed = (performance.now() - startTime).toFixed(1);
188426
188755
  sessionLog(sessionId, `transform completed in ${elapsed}ms (${messages.length} messages, ${targets.size} targets, watermark: ${watermark})`);
188756
+ deps.maybeAutoEmbedSession?.(sessionId);
188427
188757
  };
188428
188758
  }
188429
188759
  function resolveHistoryBudgetTokens(historyBudgetPercentage, contextUsage, executeThresholdPercentage, modelKey, executeThresholdTokens, resolvedContextLimit) {
@@ -188469,18 +188799,14 @@ function evictExpiredUsageEntries(contextUsageMap) {
188469
188799
  }
188470
188800
  async function deliverChannel2IfPending(deps, sessionId) {
188471
188801
  try {
188472
- try {
188473
- const meta3 = getOrCreateSessionMeta(deps.db, sessionId);
188474
- if (meta3.isSubagent)
188475
- return;
188476
- } catch {}
188477
188802
  const baseline = deps.channel1StateBySession?.get(sessionId);
188478
188803
  await maybeDeliverChannel2(sessionId, {
188479
188804
  db: deps.db,
188480
188805
  serverUrl: deps.serverUrl,
188481
188806
  directory: deps.directory ?? ".",
188482
188807
  reclaimableTokens: baseline ? baseline.tailToolTokens + baseline.turnToolTokens : undefined,
188483
- usableTokens: baseline?.usableTokens
188808
+ usableTokens: baseline?.usableTokens,
188809
+ oldestReclaimableToolTags: baseline?.oldestReclaimableToolTags
188484
188810
  });
188485
188811
  } catch (error51) {
188486
188812
  sessionLog(sessionId, "channel2 delivery wrapper failed (ignored):", error51);
@@ -188786,6 +189112,46 @@ function createEventHandler2(deps) {
188786
189112
  };
188787
189113
  }
188788
189114
 
189115
+ // src/hooks/magic-context/format-embed-status.ts
189116
+ function formatEmbedStatusText(coverage, drain) {
189117
+ if (!coverage.enabled) {
189118
+ return "Embedding is off (no provider configured).";
189119
+ }
189120
+ const lines = [];
189121
+ lines.push(`Embedding — model: ${coverage.model} (${coverage.provider})`);
189122
+ lines.push(`This session: ${coverage.session.embedded} / ${coverage.session.total} compartments embedded`);
189123
+ lines.push(`Project memories: ${coverage.memories.embedded} / ${coverage.memories.total} embedded`);
189124
+ if (coverage.commits.gitEnabled) {
189125
+ lines.push(`Git commits: ${coverage.commits.embedded} / ${coverage.commits.total}`);
189126
+ } else {
189127
+ lines.push("Git commits: 0 / 0 (git indexing off)");
189128
+ }
189129
+ let drainLine = "Drain: idle";
189130
+ switch (drain.status) {
189131
+ case "running": {
189132
+ const e = drain.embedded ?? coverage.session.embedded;
189133
+ const t = drain.total ?? coverage.session.total;
189134
+ const failedSuffix = drain.failed && drain.failed > 0 ? ` (${drain.failed} failed)` : "";
189135
+ drainLine = `Drain: running ${e}/${t}${failedSuffix}`;
189136
+ break;
189137
+ }
189138
+ case "paused": {
189139
+ const e = drain.embedded ?? coverage.session.embedded;
189140
+ const t = drain.total ?? coverage.session.total;
189141
+ drainLine = `Drain: paused ${e}/${t}`;
189142
+ break;
189143
+ }
189144
+ case "stopped":
189145
+ drainLine = "Drain: stopped (provider down)";
189146
+ break;
189147
+ default:
189148
+ drainLine = "Drain: idle";
189149
+ }
189150
+ lines.push(drainLine);
189151
+ return lines.join(`
189152
+ `);
189153
+ }
189154
+
188789
189155
  // src/hooks/magic-context/hook.ts
188790
189156
  await __promiseAll([
188791
189157
  init_inject_compartments(),
@@ -189003,7 +189369,7 @@ function maybeInjectChannel1Nudge(args, sessionId, tool, output) {
189003
189369
  setLastNudgeLevel(args.db, sessionId, decision.nextLastNudgeLevel);
189004
189370
  if (!decision.fire)
189005
189371
  return;
189006
- out.output += buildChannel1Reminder(decision.level, decision.undroppedTokens);
189372
+ out.output += buildChannel1Reminder(decision.level, decision.undroppedTokens, state.oldestReclaimableToolTags);
189007
189373
  sessionLog(sessionId, `channel1 nudge fired: level=${decision.level} undropped~${Math.round(decision.undroppedTokens / 1000)}k tool=${tool}`);
189008
189374
  }
189009
189375
  function createToolExecuteAfterHook(args) {
@@ -189075,9 +189441,7 @@ Context is managed for you entirely automatically — there's nothing to prune a
189075
189441
  var CTX_NOTE_GUIDANCE = `Use \`ctx_note\` ONLY for genuinely future concerns — something to revisit much later, not work coming up in the next few turns (that's already in your active context) and not active multi-step work (use todos for that). Magic Context preserves your full context across both compaction and restarts, so an upcoming restart or "let's come back to this later" is never a reason to take a note — nothing is lost either way. Notes you do take survive compression and resurface at natural work boundaries (after commits, historian runs, todo completion).`;
189076
189442
  var TOOL_HISTORY_GUIDANCE = `Compressed history intentionally omits tool calls and their outputs — summaries like "I edited file X" are historian records, not patterns to replicate. In the live conversation, older tool calls and their results are cleaned up to save context — you may see your own past messages referencing actions without the corresponding tool call or result visible. This is normal context management. ALWAYS use real tool calls; never simulate, fabricate, or inline tool outputs in your text. If there is no tool result message, the action did not happen. NEVER simulate, hallucinate or claim tool calls, command output, search results, file edits, or diffs in plain text as if they actually occurred.`;
189077
189443
  var BASE_INTRO = (protectedTags) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
189078
- Use \`ctx_reduce\` to manage context size. It supports one operation:
189079
- - \`drop\`: Remove entirely (best for tool outputs you already acted on).
189080
- Syntax: "3-5", "1,2,9", or "1-5,8,12-15". Last ${protectedTags} tags are protected.
189444
+ Use \`ctx_reduce\` to mark spent tagged content as discardable and reclaim space. Marking is NOT an immediate delete — it queues the content, which stays fully visible until space is actually needed (as soon as the next turn if you're already under pressure, much later if not), so mark a tool output as soon as you're done with it rather than hoarding the call for the end of the turn. The last ${protectedTags} tags are protected (marking one just queues it until it ages out). Syntax: "3-5", "1,2,9", or "1-5,8,12-15".
189081
189445
  Do not announce or narrate \`ctx_reduce\` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
189082
189446
  ${CTX_NOTE_GUIDANCE}
189083
189447
  Use \`ctx_memory\` for durable project knowledge: write what future sessions must know, update/archive/merge the memories you see in \`<project-memory>\` when they drift. Memories persist across sessions and every new session starts with them.
@@ -189096,7 +189460,7 @@ Use \`ctx_expand\` to recover the raw conversation behind a \`<compartment>\` su
189096
189460
  \`ctx_search\` returns ranked results from memories, git commits, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
189097
189461
  ${TOOL_HISTORY_GUIDANCE}
189098
189462
  NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
189099
- NEVER drop user messagesthey are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
189463
+ Keep your user's instructions and intent never drop a user message for its directive, even an old one. But a large block of pasted content inside a user message (logs, data dumps, long code, attachments) is fair to mark discardable once you've extracted what you need — it stays searchable via \`ctx_search\`.
189100
189464
  NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
189101
189465
  Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
189102
189466
  var BASE_INTRO_NO_REDUCE = () => `${CTX_NOTE_GUIDANCE}
@@ -189517,29 +189881,55 @@ function createMagicContextHook(deps) {
189517
189881
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
189518
189882
  getNotificationParams: (sid) => getLiveNotificationParams(sid, liveModelBySession, variantBySession, agentBySession)
189519
189883
  });
189520
- const executeEmbedHistory = async (sessionId) => {
189884
+ const executeEmbedHistory = async (sessionId, options) => {
189521
189885
  if (deps.config.memory?.enabled === false) {
189522
189886
  return "Memory is disabled for this project, so there is no semantic embedding to backfill.";
189523
189887
  }
189524
189888
  const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
189889
+ const active = embedRunStateBySession.get(sessionId);
189890
+ if (active && !active.signal.aborted && !options?.signal) {
189891
+ return "Embedding is already running for this session.";
189892
+ }
189525
189893
  await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
189526
189894
  const sessionProjectIdentity = resolveProjectIdentity(directory);
189527
- setRecompStarting({ recompProgressBySession }, sessionId, "Embedding history…", "embed");
189528
- const outcome = await embedSessionCompartmentChunks(db, sessionProjectIdentity, sessionId, {
189529
- onProgress: ({ embedded, total }) => {
189530
- const cur = recompProgressBySession.get(sessionId);
189531
- if (!cur || cur.phase !== "recomp")
189532
- return;
189533
- recompProgressBySession.set(sessionId, {
189534
- ...cur,
189535
- processedMessages: embedded,
189536
- totalMessages: total,
189537
- updatedAt: Date.now()
189538
- });
189895
+ embedPauseBySession.delete(sessionId);
189896
+ const prior = embedRunStateBySession.get(sessionId);
189897
+ if (prior)
189898
+ prior.abort();
189899
+ const controller = new AbortController;
189900
+ embedRunStateBySession.set(sessionId, controller);
189901
+ const signal = options?.signal ?? controller.signal;
189902
+ if (!options?.silent) {
189903
+ setRecompStarting({ recompProgressBySession }, sessionId, "Embedding history…", "embed");
189904
+ }
189905
+ let runFailed = 0;
189906
+ let outcome;
189907
+ try {
189908
+ outcome = await embedSessionCompartmentChunks(db, sessionProjectIdentity, sessionId, {
189909
+ signal,
189910
+ onProgress: ({ embedded, total }) => {
189911
+ const cur = recompProgressBySession.get(sessionId);
189912
+ if (!cur || cur.phase !== "recomp")
189913
+ return;
189914
+ recompProgressBySession.set(sessionId, {
189915
+ ...cur,
189916
+ processedMessages: embedded,
189917
+ totalMessages: total,
189918
+ updatedAt: Date.now()
189919
+ });
189920
+ }
189921
+ });
189922
+ } finally {
189923
+ if (embedRunStateBySession.get(sessionId) === controller) {
189924
+ embedRunStateBySession.delete(sessionId);
189539
189925
  }
189540
- });
189926
+ }
189927
+ if ("failed" in outcome)
189928
+ runFailed = outcome.failed;
189541
189929
  const terminal = (phase, message) => {
189542
- setRecompTerminal({ recompProgressBySession }, sessionId, phase, message);
189930
+ if (!options?.silent) {
189931
+ setRecompTerminal({ recompProgressBySession }, sessionId, phase, message);
189932
+ }
189543
189933
  return message;
189544
189934
  };
189545
189935
  switch (outcome.status) {
@@ -189548,15 +189938,78 @@ function createMagicContextHook(deps) {
189548
189938
  case "disabled":
189549
189939
  return terminal("skipped", "No embedding provider is configured, so there is nothing to embed.");
189550
189940
  case "busy":
189551
- return terminal("skipped", `Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`);
189552
- case "aborted":
189553
- return terminal("done", `Embedded ${outcome.embedded} of ${outcome.total} compartments before stopping.`);
189941
+ return terminal("skipped", "Embedding is already running for this project. Try again shortly.");
189942
+ case "aborted": {
189943
+ const cov = getEmbeddingCoverageStatus(db, sessionProjectIdentity, sessionId);
189944
+ const msg = `Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`;
189945
+ return terminal("skipped", msg);
189946
+ }
189554
189947
  case "stalled":
189555
- return terminal("skipped", `Embedded ${outcome.embedded} compartments; ${outcome.remaining} could not be embedded (the provider returned no result). Run /ctx-embed-history again to retry them.`);
189948
+ return terminal("skipped", `Embedded ${outcome.embedded} compartments; ${outcome.remaining} could not be embedded (the provider returned no result). Run /ctx-embed start again to retry them.`);
189556
189949
  default:
189557
- return terminal("done", `Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`);
189950
+ return terminal("done", `Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search${runFailed > 0 ? ` (${runFailed} failed)` : ""}.`);
189558
189951
  }
189559
189952
  };
189953
+ const pauseEmbedDrain = (sessionId) => {
189954
+ embedPauseBySession.add(sessionId);
189955
+ const ctrl = embedRunStateBySession.get(sessionId);
189956
+ if (ctrl)
189957
+ ctrl.abort();
189958
+ const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
189959
+ const sessionProjectIdentity = resolveProjectIdentity(directory);
189960
+ const cov = getEmbeddingCoverageStatus(db, sessionProjectIdentity, sessionId);
189961
+ return `Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`;
189962
+ };
189963
+ const getEmbedStatusText = (sessionId) => {
189964
+ const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
189965
+ const sessionProjectIdentity = resolveProjectIdentity(directory);
189966
+ const coverage = getEmbeddingCoverageStatus(db, sessionProjectIdentity, sessionId);
189967
+ const progress = recompProgressBySession.get(sessionId);
189968
+ const drainUi = getEmbedDrainUiStatus(sessionId, progress);
189969
+ return formatEmbedStatusText(coverage, {
189970
+ status: drainUi.status,
189971
+ embedded: progress?.processedMessages,
189972
+ total: progress?.totalMessages
189973
+ });
189974
+ };
189975
+ const maybeAutoEmbedSession = (sessionId) => {
189976
+ if (autoEmbedAttemptedBySession.has(sessionId))
189977
+ return;
189978
+ if (embedPauseBySession.has(sessionId))
189979
+ return;
189980
+ if (deps.config.memory?.enabled === false)
189981
+ return;
189982
+ autoEmbedAttemptedBySession.add(sessionId);
189983
+ const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
189984
+ (async () => {
189985
+ try {
189986
+ await new Promise((resolve7) => setTimeout(resolve7, 0));
189987
+ await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
189988
+ const sessionProjectIdentity = resolveProjectIdentity(directory);
189989
+ const coverage = getEmbeddingCoverageStatus(db, sessionProjectIdentity, sessionId);
189990
+ if (!coverage.enabled)
189991
+ return;
189992
+ const remaining = coverage.session.total - coverage.session.embedded;
189993
+ if (remaining <= 0)
189994
+ return;
189995
+ const notifyParams = getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession);
189996
+ if (!isTuiConnected(sessionId)) {
189997
+ const startMsg = `Embedding ${remaining} compartment${remaining === 1 ? "" : "s"} of history in the background…`;
189998
+ await sendIgnoredMessage(deps.client, sessionId, startMsg, {
189999
+ ...notifyParams
190000
+ });
190001
+ }
190002
+ const summary = await executeEmbedHistory(sessionId);
190003
+ if (!isTuiConnected(sessionId)) {
190004
+ await sendIgnoredMessage(deps.client, sessionId, summary, {
190005
+ ...notifyParams
190006
+ });
190007
+ }
190008
+ } catch (error51) {
190009
+ log("[magic-context] auto-embed drain failed:", error51);
190010
+ }
190011
+ })();
190012
+ };
189560
190013
  const sidekickRunnable = isSidekickRunnable(deps.config);
189561
190014
  const sidekickConfig = sidekickRunnable ? deps.config.sidekick : undefined;
189562
190015
  const transform2 = createTransform({
@@ -189618,7 +190071,8 @@ function createMagicContextHook(deps) {
189618
190071
  cavemanTextCompression: ctxReduceEnabled === false && deps.config.caveman_text_compression?.enabled === true ? {
189619
190072
  enabled: true,
189620
190073
  minChars: deps.config.caveman_text_compression.min_chars ?? 500
189621
- } : undefined
190074
+ } : undefined,
190075
+ maybeAutoEmbedSession
189622
190076
  });
189623
190077
  const eventHandler = createEventHandler2({
189624
190078
  contextUsageMap,
@@ -189647,6 +190101,7 @@ function createMagicContextHook(deps) {
189647
190101
  recompProgressBySession.delete(sessionId);
189648
190102
  internalChildSessions.delete(sessionId);
189649
190103
  channel1StateBySession.delete(sessionId);
190104
+ clearEmbedSessionState(sessionId);
189650
190105
  }
189651
190106
  });
189652
190107
  const runDreamQueueInBackground = () => {
@@ -189712,6 +190167,8 @@ function createMagicContextHook(deps) {
189712
190167
  executeRecomp: historianRunnable ? async (sessionId, options) => runManagedRecomp(buildManagedRecompCtx(sessionId), sessionId, options) : undefined,
189713
190168
  runUpgrade: historianRunnable ? async (sessionId) => runManagedUpgrade(buildManagedRecompCtx(sessionId), sessionId) : undefined,
189714
190169
  executeEmbedHistory,
190170
+ pauseEmbedDrain,
190171
+ getEmbedStatusText,
189715
190172
  sendNotification: async (sessionId, text, params) => {
189716
190173
  await sendIgnoredMessage(deps.client, sessionId, text, {
189717
190174
  ...getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
@@ -189923,6 +190380,7 @@ function truncateError(name2, code, message, maxLen = 240) {
189923
190380
 
189924
190381
  // src/plugin/rpc-handlers.ts
189925
190382
  init_project_identity();
190383
+ init_project_embedding_registry();
189926
190384
  init_tool_definition_tokens();
189927
190385
  await init_storage();
189928
190386
 
@@ -190643,6 +191101,26 @@ function buildStatusDetail(db, sessionId, directory, modelKey, config2, liveSess
190643
191101
  }
190644
191102
  return detail;
190645
191103
  }
191104
+ function buildEmbedDetail(db, sessionId, dir, liveSessionState) {
191105
+ const projectIdentity = resolveProjectIdentity(dir);
191106
+ const coverage = getEmbeddingCoverageStatus(db, projectIdentity, sessionId);
191107
+ const progress = liveSessionState.recompProgressBySession.get(sessionId);
191108
+ const drainUi = getEmbedDrainUiStatus(sessionId, progress);
191109
+ const statusText = formatEmbedStatusText(coverage, {
191110
+ status: drainUi.status,
191111
+ embedded: progress?.processedMessages,
191112
+ total: progress?.totalMessages
191113
+ });
191114
+ return {
191115
+ enabled: coverage.enabled,
191116
+ model: coverage.model,
191117
+ provider: coverage.provider,
191118
+ session: coverage.session,
191119
+ memories: coverage.memories,
191120
+ commits: coverage.commits,
191121
+ statusText
191122
+ };
191123
+ }
190646
191124
  function registerRpcHandlers(rpcServer, args) {
190647
191125
  const { directory, config: config2, liveSessionState } = args;
190648
191126
  const rawConfig = config2;
@@ -190665,6 +191143,19 @@ function registerRpcHandlers(rpcServer, args) {
190665
191143
  return { error: "unavailable" };
190666
191144
  return buildStatusDetail(db, sessionId, dir, modelKey, rawConfig, liveSessionState, injectionBudgetTokens);
190667
191145
  });
191146
+ rpcServer.handle("embed-detail", async (params) => {
191147
+ const sessionId = String(params.sessionId ?? "");
191148
+ const dir = String(params.directory ?? directory);
191149
+ const db = getDb();
191150
+ if (!db || !sessionId)
191151
+ return { error: "unavailable" };
191152
+ try {
191153
+ return buildEmbedDetail(db, sessionId, dir, liveSessionState);
191154
+ } catch (err) {
191155
+ log("[rpc] embed-detail error:", err);
191156
+ return { error: "unavailable" };
191157
+ }
191158
+ });
190668
191159
  rpcServer.handle("compartment-count", async (params) => {
190669
191160
  const sessionId = String(params.sessionId ?? "");
190670
191161
  const db = getDb();
@@ -190787,27 +191278,225 @@ Older parts of this session are summarized into <compartment> blocks inside <ses
190787
191278
 
190788
191279
  ctx_expand(start=120, end=245) ← the compartment's own start/end attributes
190789
191280
 
190790
- Returns the raw transcript as [N] U:/A: lines, capped at ~15K tokens; an oversized range returns the head and tells you where to continue. Also works with ordinals from ctx_search message results — expand a window around a hit (e.g. start=N-10, end=N+5). Ranges after the last compartment are your live tail — already visible in context, not expandable.`;
191281
+ Returns the raw transcript as [N] U:/A: lines, capped at ~15K tokens; an oversized range returns the head and tells you where to continue. Also works with ordinals from ctx_search message results — expand a window around a hit (e.g. start=N-10, end=N+5). Ranges after the last compartment are your live tail — already visible in context, not expandable.
191282
+
191283
+ Two recovery modes for finer detail:
191284
+ - ctx_expand(start=120, end=245, verbose=true) — lists each message SEPARATELY with its ordinal [N] and a per-part preview (each tool call shown with its output size). Use this to find the exact message or tool call you want, then recover it in full by ordinal.
191285
+ - ctx_expand(message=138) — returns the FULL untruncated content of the message at that ordinal: every text part, and every tool call's complete input + output, read from stored history. This is the cheap way to get back a tool output you dropped with ctx_reduce — the original is still in storage even though the wire shows [dropped §N§]. If the message was deleted from history (session prune/revert), it says so.`;
190791
191286
  var CTX_EXPAND_TOKEN_BUDGET = 15000;
190792
191287
 
191288
+ // src/tools/ctx-expand/render.ts
191289
+ init_read_session_formatting();
191290
+ await init_read_session_chunk();
191291
+ function isRecord3(value) {
191292
+ return value !== null && typeof value === "object" && !Array.isArray(value);
191293
+ }
191294
+ function roleLabel(role) {
191295
+ if (role === "assistant")
191296
+ return "A (assistant)";
191297
+ if (role === "user")
191298
+ return "U (user)";
191299
+ return role;
191300
+ }
191301
+ function truncate2(value, max) {
191302
+ const t = value.trim();
191303
+ return t.length <= max ? t : `${t.slice(0, max)}…`;
191304
+ }
191305
+ function keyArg(input) {
191306
+ if (!input)
191307
+ return "";
191308
+ for (const k of ["filePath", "path", "pattern", "query", "symbol", "module", "action"]) {
191309
+ const v = input[k];
191310
+ if (typeof v === "string" && v.length > 0)
191311
+ return truncate2(v, 60);
191312
+ }
191313
+ if (typeof input.description === "string")
191314
+ return truncate2(input.description, 60);
191315
+ return "";
191316
+ }
191317
+ function asToolPart(part) {
191318
+ const type = typeof part.type === "string" ? part.type : "";
191319
+ if (type === "tool") {
191320
+ const state = isRecord3(part.state) ? part.state : null;
191321
+ const output = state && typeof state.output === "string" ? state.output : state && state.output != null ? JSON.stringify(state.output) : null;
191322
+ const metadata = state && isRecord3(state.metadata) ? state.metadata : null;
191323
+ const title = state && typeof state.title === "string" && state.title || metadata && typeof metadata.title === "string" && metadata.title || null;
191324
+ return {
191325
+ name: typeof part.tool === "string" ? part.tool : "tool",
191326
+ callId: typeof part.callID === "string" ? part.callID : "",
191327
+ title,
191328
+ input: state && isRecord3(state.input) ? state.input : null,
191329
+ output
191330
+ };
191331
+ }
191332
+ if (type === "tool_use") {
191333
+ return {
191334
+ name: typeof part.name === "string" ? part.name : "tool",
191335
+ callId: typeof part.id === "string" ? part.id : "",
191336
+ title: null,
191337
+ input: isRecord3(part.input) ? part.input : null,
191338
+ output: null
191339
+ };
191340
+ }
191341
+ if (type === "tool_result") {
191342
+ const content = part.content;
191343
+ const output = typeof content === "string" ? content : content != null ? JSON.stringify(content) : null;
191344
+ return {
191345
+ name: "tool_result",
191346
+ callId: typeof part.tool_use_id === "string" ? part.tool_use_id : "",
191347
+ title: null,
191348
+ input: null,
191349
+ output
191350
+ };
191351
+ }
191352
+ return null;
191353
+ }
191354
+ function textOf(part) {
191355
+ if (part.type === "text" && typeof part.text === "string")
191356
+ return part.text;
191357
+ return null;
191358
+ }
191359
+ function reasoningOf(part) {
191360
+ if ((part.type === "reasoning" || part.type === "thinking") && typeof part.text === "string") {
191361
+ return part.text;
191362
+ }
191363
+ return null;
191364
+ }
191365
+ function renderPartPreview(part) {
191366
+ if (!isRecord3(part))
191367
+ return null;
191368
+ const text = textOf(part);
191369
+ if (text !== null) {
191370
+ const t = truncate2(text, 200);
191371
+ return t.length > 0 ? ` • ${t}` : null;
191372
+ }
191373
+ const tool = asToolPart(part);
191374
+ if (tool) {
191375
+ const arg = keyArg(tool.input);
191376
+ const head = arg ? `${tool.name}(${arg})` : tool.name;
191377
+ return tool.output !== null ? ` • tool ${head} → output ~${estimateTokens(tool.output)} tok` : ` • tool ${head}`;
191378
+ }
191379
+ const reasoning = reasoningOf(part);
191380
+ if (reasoning !== null)
191381
+ return ` • [reasoning] ${truncate2(reasoning, 120)}`;
191382
+ const type = typeof part.type === "string" ? part.type : "part";
191383
+ if (type === "file")
191384
+ return " • [file]";
191385
+ if (type === "step-start" || type === "step-finish")
191386
+ return null;
191387
+ return ` • [${type}]`;
191388
+ }
191389
+ function renderPartFull(part) {
191390
+ if (!isRecord3(part))
191391
+ return null;
191392
+ const text = textOf(part);
191393
+ if (text !== null) {
191394
+ return text.trim().length > 0 ? ` [text]
191395
+ ${text}` : null;
191396
+ }
191397
+ const tool = asToolPart(part);
191398
+ if (tool) {
191399
+ const lines = [];
191400
+ const idSuffix = tool.callId ? ` #${tool.callId}` : "";
191401
+ lines.push(` [tool: ${tool.name}${idSuffix}]`);
191402
+ if (tool.title && tool.title.trim().length > 0) {
191403
+ lines.push(` description: ${tool.title.trim()}`);
191404
+ }
191405
+ if (tool.input)
191406
+ lines.push(` input: ${JSON.stringify(tool.input)}`);
191407
+ if (tool.output !== null)
191408
+ lines.push(` output:
191409
+ ${tool.output}`);
191410
+ return lines.join(`
191411
+ `);
191412
+ }
191413
+ const type = typeof part.type === "string" ? part.type : "part";
191414
+ if (type === "file") {
191415
+ const name2 = typeof part.filename === "string" && part.filename || typeof part.url === "string" && part.url || "";
191416
+ return ` [file]${name2 ? ` ${name2}` : ""}`;
191417
+ }
191418
+ return null;
191419
+ }
191420
+ function renderMessageByOrdinal(sessionId, ordinal) {
191421
+ const msg = readRawSessionMessages(sessionId).find((m) => m.ordinal === ordinal);
191422
+ if (!msg) {
191423
+ return `No message at ordinal ${ordinal} in this session's stored history — it was deleted ` + `(session prune/revert) or the ordinal is wrong, so it can't be recovered. ` + `Re-run the tool if you still need the data.`;
191424
+ }
191425
+ const rendered = msg.parts.map(renderPartFull).filter((l) => l !== null);
191426
+ const lines = [`[${msg.ordinal}] ${roleLabel(msg.role)} — full recovery:`, ""];
191427
+ if (rendered.length === 0) {
191428
+ lines.push(" (no recoverable content — message had only structural/reasoning parts)");
191429
+ } else {
191430
+ lines.push(...rendered);
191431
+ }
191432
+ return lines.join(`
191433
+ `);
191434
+ }
191435
+ function renderVerboseRange(sessionId, start, end, tokenBudget) {
191436
+ const messages = readRawSessionMessages(sessionId).filter((m) => m.ordinal >= start && m.ordinal <= end);
191437
+ const out = [];
191438
+ let usedTokens = 0;
191439
+ let lastOrdinal = start - 1;
191440
+ let truncated = false;
191441
+ for (const msg of messages) {
191442
+ const header = `[${msg.ordinal}] ${roleLabel(msg.role)}`;
191443
+ const partLines = msg.parts.map(renderPartPreview).filter((l) => l !== null);
191444
+ const block = partLines.length > 0 ? `${header}
191445
+ ${partLines.join(`
191446
+ `)}` : header;
191447
+ const blockTokens = estimateTokens(block);
191448
+ if (usedTokens + blockTokens > tokenBudget && out.length > 0) {
191449
+ truncated = true;
191450
+ break;
191451
+ }
191452
+ out.push(block);
191453
+ usedTokens += blockTokens;
191454
+ lastOrdinal = msg.ordinal;
191455
+ }
191456
+ return { text: out.join(`
191457
+
191458
+ `), lastOrdinal, truncated };
191459
+ }
191460
+
190793
191461
  // src/tools/ctx-expand/tools.ts
190794
191462
  function createCtxExpandTool(deps) {
190795
191463
  return tool({
190796
191464
  description: CTX_EXPAND_DESCRIPTION,
190797
191465
  args: {
190798
- start: tool.schema.number().describe(`First message ordinal to expand — a compartment's start="N" attribute, or an ordinal from a ctx_search message hit`),
190799
- end: tool.schema.number().describe(`Last message ordinal to expand (inclusive) — a compartment's end="M" attribute`)
191466
+ start: tool.schema.number().optional().describe(`First message ordinal to expand — a compartment's start="N" attribute, or an ordinal from a ctx_search message hit`),
191467
+ end: tool.schema.number().optional().describe(`Last message ordinal to expand (inclusive) — a compartment's end="M" attribute`),
191468
+ verbose: tool.schema.boolean().optional().describe("With start/end: list each message separately with its ordinal [N] and per-part preview (each tool call shown with its output size), so you can pick one to recover in full by ordinal."),
191469
+ message: tool.schema.number().optional().describe("Full untruncated recovery of ONE message by its ordinal (every text part + every tool call's complete input/output). Use an ordinal from a compartment, ctx_search hit, or verbose range. Recovers a tool output you dropped with ctx_reduce.")
190800
191470
  },
190801
191471
  async execute(args, toolContext) {
190802
191472
  const sessionId = toolContext.sessionID;
191473
+ if (typeof args.message === "number" && args.message >= 1) {
191474
+ return renderMessageByOrdinal(sessionId, args.message);
191475
+ }
190803
191476
  if (!args.start || !args.end || args.start < 1 || args.end < args.start) {
190804
- return "Error: start and end must be positive integers with start <= end.";
191477
+ return "Error: provide either message=<ordinal>, or start and end (positive integers, start <= end).";
190805
191478
  }
190806
191479
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, sessionId);
190807
191480
  if (lastCompartmentEnd >= 0 && args.start > lastCompartmentEnd) {
190808
191481
  return `Range ${args.start}-${args.end} is entirely within the live tail (after the last compacted message ${lastCompartmentEnd}); those messages are already visible in context.`;
190809
191482
  }
190810
191483
  const effectiveEnd = lastCompartmentEnd >= 0 ? Math.min(args.end, lastCompartmentEnd) : args.end;
191484
+ if (args.verbose === true) {
191485
+ const v = renderVerboseRange(sessionId, args.start, effectiveEnd, CTX_EXPAND_TOKEN_BUDGET);
191486
+ if (!v.text) {
191487
+ return `No messages found in range ${args.start}-${effectiveEnd}. The range may be outside this session's history.`;
191488
+ }
191489
+ const out = [
191490
+ `Messages ${args.start}-${v.lastOrdinal} (verbose). Recover any one in full with ctx_expand(message=<ordinal>):`,
191491
+ "",
191492
+ v.text
191493
+ ];
191494
+ if (v.truncated) {
191495
+ out.push("", `Truncated at message ${v.lastOrdinal} (budget: ~${CTX_EXPAND_TOKEN_BUDGET} tokens). Call again with start=${v.lastOrdinal + 1} end=${effectiveEnd} verbose=true for more.`);
191496
+ }
191497
+ return out.join(`
191498
+ `);
191499
+ }
190811
191500
  const chunk = readSessionChunk(sessionId, CTX_EXPAND_TOKEN_BUDGET, args.start, effectiveEnd + 1);
190812
191501
  if (!chunk.text || chunk.messageCount === 0) {
190813
191502
  return `No messages found in range ${args.start}-${args.end}. The range may be outside this session's history.`;
@@ -191501,15 +192190,16 @@ function createCtxNoteTools(deps) {
191501
192190
  };
191502
192191
  }
191503
192192
  // src/tools/ctx-reduce/constants.ts
191504
- var CTX_REDUCE_DESCRIPTION = `Reduce context by dropping tagged content you no longer need.
191505
- Use §N§ identifiers visible in conversation. The \`drop\` param accepts ranges: "3-5", "1,2,9", "1-5,8".
191506
-
191507
- CRITICAL RULES:
191508
- - NEVER blanket-drop large ranges (e.g., "1-50"). Always review what each tag contains first.
191509
- - Only drop tool outputs you have already processed and no longer need.
191510
- - Protected tags are accepted but deferred until they leave the last protected range.
191511
- - Keep recent context only reduce OLD content that is no longer relevant to current work.
191512
- - Dropped content is gone forever.`;
192193
+ var CTX_REDUCE_DESCRIPTION = `Mark spent tagged content as discardable to reclaim context space. This is NOT an immediate delete. Use §N§ identifiers visible in the conversation. The \`drop\` param accepts ranges: "3-5", "1,2,9", "1-5,8".
192194
+
192195
+ How it works:
192196
+ - Marking QUEUES content for release. It stays fully visible to you until context space is actually needed — which may be as soon as the next turn if you are already under pressure, or many turns later if not. So mark spent outputs as soon as you finish with them; don't hoard the call for the end of the turn.
192197
+ - The newest tags are protected: marking one just queues it until it ages out of the recent window, so marking recent output is harmless.
192198
+ - When content is finally released it becomes a short placeholder, and re-running the tool is the only way to get it back. So mark only what you are genuinely DONE with — the test is "have I extracted what I need from this?", not "is it safe / do I have time before it drops?".
192199
+
192200
+ Mark discardable once processed: large outputs you've summarized, repeated or redundant dumps, data written to disk, status/log output that only confirmed an expected state.
192201
+ Keep: user messages, unresolved errors, raw evidence you haven't extracted yet, and outputs whose exact wording may matter later.
192202
+ Never blanket-mark large ranges (e.g. "1-50") — review what each tag holds first.`;
191513
192203
  // src/tools/ctx-reduce/tools.ts
191514
192204
  import { tool as tool4 } from "@opencode-ai/plugin";
191515
192205
 
@@ -192092,6 +192782,9 @@ class MagicContextRpcServer {
192092
192782
  }
192093
192783
 
192094
192784
  // src/index.ts
192785
+ var HISTORIAN_MAX_STEPS = 40;
192786
+ var SIDEKICK_MAX_STEPS = 40;
192787
+ var DREAMER_MAX_STEPS = 150;
192095
192788
  var plugin = async (ctx) => {
192096
192789
  const pluginConfig = loadPluginConfig(ctx.directory);
192097
192790
  setSqlitePragmaConfig({
@@ -192308,11 +193001,13 @@ var plugin = async (ctx) => {
192308
193001
  await hooks.magicContext?.["experimental.text.complete"]?.(input, output);
192309
193002
  },
192310
193003
  config: async (config2) => {
192311
- const buildHiddenAgentConfig = (agentId, prompt, allowedTools, overrides) => {
193004
+ const buildHiddenAgentConfig = (agentId, prompt, allowedTools, maxSteps, overrides) => {
192312
193005
  const { permission: overridePermission, ...restOverrides } = overrides ?? {};
192313
193006
  const basePermission = buildAllowOnlyPermission(allowedTools);
192314
193007
  return {
192315
193008
  prompt,
193009
+ steps: maxSteps,
193010
+ maxSteps,
192316
193011
  ...getAgentFallbackModels(agentId) ? { fallback_models: getAgentFallbackModels(agentId) } : {},
192317
193012
  ...restOverrides,
192318
193013
  permission: {
@@ -192353,10 +193048,10 @@ var plugin = async (ctx) => {
192353
193048
  })() : undefined;
192354
193049
  config2.agent = {
192355
193050
  ...config2.agent ?? {},
192356
- [DREAMER_AGENT]: buildHiddenAgentConfig(DREAMER_AGENT, DREAMER_SYSTEM_PROMPT, DREAMER_ALLOWED_TOOLS, dreamerAgentOverrides),
192357
- [HISTORIAN_AGENT]: buildHiddenAgentConfig(HISTORIAN_AGENT, COMPARTMENT_AGENT_SYSTEM_PROMPT, HISTORIAN_ALLOWED_TOOLS, historianAgentOverrides),
192358
- [HISTORIAN_EDITOR_AGENT]: buildHiddenAgentConfig(HISTORIAN_EDITOR_AGENT, HISTORIAN_EDITOR_SYSTEM_PROMPT, HISTORIAN_ALLOWED_TOOLS, historianAgentOverrides),
192359
- [SIDEKICK_AGENT]: buildHiddenAgentConfig(SIDEKICK_AGENT, SIDEKICK_SYSTEM_PROMPT, SIDEKICK_ALLOWED_TOOLS, sidekickAgentOverrides)
193051
+ [DREAMER_AGENT]: buildHiddenAgentConfig(DREAMER_AGENT, DREAMER_SYSTEM_PROMPT, DREAMER_ALLOWED_TOOLS, DREAMER_MAX_STEPS, dreamerAgentOverrides),
193052
+ [HISTORIAN_AGENT]: buildHiddenAgentConfig(HISTORIAN_AGENT, COMPARTMENT_AGENT_SYSTEM_PROMPT, HISTORIAN_ALLOWED_TOOLS, HISTORIAN_MAX_STEPS, historianAgentOverrides),
193053
+ [HISTORIAN_EDITOR_AGENT]: buildHiddenAgentConfig(HISTORIAN_EDITOR_AGENT, HISTORIAN_EDITOR_SYSTEM_PROMPT, HISTORIAN_ALLOWED_TOOLS, HISTORIAN_MAX_STEPS, historianAgentOverrides),
193054
+ [SIDEKICK_AGENT]: buildHiddenAgentConfig(SIDEKICK_AGENT, SIDEKICK_SYSTEM_PROMPT, SIDEKICK_ALLOWED_TOOLS, SIDEKICK_MAX_STEPS, sidekickAgentOverrides)
192360
193055
  };
192361
193056
  }
192362
193057
  };