@cortexkit/opencode-magic-context 0.5.1 → 0.6.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 (95) hide show
  1. package/README.md +18 -2
  2. package/dist/cli/doctor.d.ts.map +1 -1
  3. package/dist/cli/setup.d.ts.map +1 -1
  4. package/dist/cli.js +23 -6
  5. package/dist/config/schema/magic-context.d.ts +21 -0
  6. package/dist/config/schema/magic-context.d.ts.map +1 -1
  7. package/dist/features/magic-context/compaction-marker.d.ts +72 -0
  8. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -0
  9. package/dist/features/magic-context/dreamer/runner.d.ts +8 -0
  10. package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
  11. package/dist/features/magic-context/dreamer/scheduler.d.ts.map +1 -1
  12. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  13. package/dist/features/magic-context/migrations.d.ts +8 -0
  14. package/dist/features/magic-context/migrations.d.ts.map +1 -0
  15. package/dist/features/magic-context/plugin-messages.d.ts +75 -0
  16. package/dist/features/magic-context/plugin-messages.d.ts.map +1 -0
  17. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  18. package/dist/features/magic-context/storage-meta-persisted.d.ts +10 -0
  19. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  20. package/dist/features/magic-context/storage-notes.d.ts +51 -5
  21. package/dist/features/magic-context/storage-notes.d.ts.map +1 -1
  22. package/dist/features/magic-context/storage.d.ts +1 -2
  23. package/dist/features/magic-context/storage.d.ts.map +1 -1
  24. package/dist/features/magic-context/user-memory/review-user-memories.d.ts +20 -0
  25. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -0
  26. package/dist/features/magic-context/user-memory/storage-user-memory.d.ts +33 -0
  27. package/dist/features/magic-context/user-memory/storage-user-memory.d.ts.map +1 -0
  28. package/dist/hooks/magic-context/command-handler.d.ts +4 -0
  29. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +27 -0
  31. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -0
  32. package/dist/hooks/magic-context/compartment-parser.d.ts +1 -0
  33. package/dist/hooks/magic-context/compartment-parser.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/compartment-prompt.d.ts +4 -1
  35. package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/compartment-runner-types.d.ts +5 -0
  40. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/hook.d.ts +7 -0
  44. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/system-prompt-hash.d.ts +2 -0
  49. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +4 -0
  51. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/transform.d.ts +2 -0
  54. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +1231 -151
  57. package/dist/plugin/dream-timer.d.ts +4 -0
  58. package/dist/plugin/dream-timer.d.ts.map +1 -1
  59. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  60. package/dist/plugin/tui-action-consumer.d.ts +13 -0
  61. package/dist/plugin/tui-action-consumer.d.ts.map +1 -0
  62. package/dist/shared/tui-config.d.ts.map +1 -1
  63. package/dist/tools/ctx-note/constants.d.ts +1 -1
  64. package/dist/tools/ctx-note/constants.d.ts.map +1 -1
  65. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  66. package/dist/tools/ctx-note/types.d.ts +3 -1
  67. package/dist/tools/ctx-note/types.d.ts.map +1 -1
  68. package/dist/tui/data/context-db.d.ts +20 -0
  69. package/dist/tui/data/context-db.d.ts.map +1 -1
  70. package/package.json +2 -1
  71. package/src/shared/assistant-message-extractor.ts +74 -0
  72. package/src/shared/conflict-detector.ts +310 -0
  73. package/src/shared/conflict-fixer.ts +216 -0
  74. package/src/shared/data-path.ts +10 -0
  75. package/src/shared/error-message.ts +3 -0
  76. package/src/shared/format-bytes.ts +5 -0
  77. package/src/shared/index.ts +4 -0
  78. package/src/shared/internal-initiator-marker.ts +1 -0
  79. package/src/shared/jsonc-parser.ts +138 -0
  80. package/src/shared/logger.ts +63 -0
  81. package/src/shared/model-requirements.ts +84 -0
  82. package/src/shared/model-suggestion-retry.ts +160 -0
  83. package/src/shared/normalize-sdk-response.ts +40 -0
  84. package/src/shared/opencode-compaction-detector.test.ts +222 -0
  85. package/src/shared/opencode-compaction-detector.ts +81 -0
  86. package/src/shared/opencode-config-dir-types.ts +15 -0
  87. package/src/shared/opencode-config-dir.ts +38 -0
  88. package/src/shared/record-type-guard.ts +3 -0
  89. package/src/shared/system-directive.ts +9 -0
  90. package/src/shared/tui-config.ts +60 -0
  91. package/src/tui/data/context-db.ts +114 -6
  92. package/src/tui/index.tsx +77 -2
  93. package/src/tui/slots/sidebar-content.tsx +3 -2
  94. package/dist/features/magic-context/storage-smart-notes.d.ts +0 -24
  95. package/dist/features/magic-context/storage-smart-notes.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -8323,10 +8323,10 @@ var require_stringify = __commonJS((exports, module) => {
8323
8323
  replacer = null;
8324
8324
  indent = EMPTY;
8325
8325
  };
8326
- var join12 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
8326
+ var join13 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
8327
8327
  var join_content = (inside, value, gap) => {
8328
8328
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
8329
- return join12(comment, inside, gap);
8329
+ return join13(comment, inside, gap);
8330
8330
  };
8331
8331
  var stringify_string = (holder, key, value) => {
8332
8332
  const raw = get_raw_string_literal(holder, key);
@@ -8348,13 +8348,13 @@ var require_stringify = __commonJS((exports, module) => {
8348
8348
  if (i !== 0) {
8349
8349
  inside += COMMA;
8350
8350
  }
8351
- const before = join12(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
8351
+ const before = join13(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
8352
8352
  inside += before || LF + deeper_gap;
8353
8353
  inside += stringify(i, value, deeper_gap) || STR_NULL;
8354
8354
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
8355
8355
  after_comma = process_comments(value, AFTER(i), deeper_gap);
8356
8356
  }
8357
- inside += join12(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8357
+ inside += join13(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8358
8358
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
8359
8359
  };
8360
8360
  var object_stringify = (value, gap) => {
@@ -8375,13 +8375,13 @@ var require_stringify = __commonJS((exports, module) => {
8375
8375
  inside += COMMA;
8376
8376
  }
8377
8377
  first = false;
8378
- const before = join12(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
8378
+ const before = join13(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
8379
8379
  inside += before || LF + deeper_gap;
8380
8380
  inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
8381
8381
  after_comma = process_comments(value, AFTER(key), deeper_gap);
8382
8382
  };
8383
8383
  keys.forEach(iteratee);
8384
- inside += join12(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8384
+ inside += join13(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8385
8385
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
8386
8386
  };
8387
8387
  function stringify(key, holder, gap) {
@@ -8479,11 +8479,11 @@ __export(exports_tui_config, {
8479
8479
  ensureTuiPluginEntry: () => ensureTuiPluginEntry
8480
8480
  });
8481
8481
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
8482
- import { dirname, join as join12 } from "path";
8482
+ import { dirname, join as join13 } from "path";
8483
8483
  function resolveTuiConfigPath() {
8484
8484
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
8485
- const jsoncPath = join12(configDir, "tui.jsonc");
8486
- const jsonPath = join12(configDir, "tui.json");
8485
+ const jsoncPath = join13(configDir, "tui.jsonc");
8486
+ const jsonPath = join13(configDir, "tui.json");
8487
8487
  if (existsSync6(jsoncPath))
8488
8488
  return jsoncPath;
8489
8489
  if (existsSync6(jsonPath))
@@ -8502,7 +8502,7 @@ function ensureTuiPluginEntry() {
8502
8502
  if (plugins.some((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`))) {
8503
8503
  return false;
8504
8504
  }
8505
- plugins.push(PLUGIN_NAME);
8505
+ plugins.push(PLUGIN_ENTRY);
8506
8506
  config2.plugin = plugins;
8507
8507
  mkdirSync3(dirname(configPath), { recursive: true });
8508
8508
  writeFileSync2(configPath, `${import_comment_json.stringify(config2, null, 2)}
@@ -8514,11 +8514,12 @@ function ensureTuiPluginEntry() {
8514
8514
  return false;
8515
8515
  }
8516
8516
  }
8517
- var import_comment_json, PLUGIN_NAME = "@cortexkit/opencode-magic-context";
8517
+ var import_comment_json, PLUGIN_NAME = "@cortexkit/opencode-magic-context", PLUGIN_ENTRY;
8518
8518
  var init_tui_config = __esm(() => {
8519
8519
  init_logger();
8520
8520
  init_opencode_config_dir();
8521
8521
  import_comment_json = __toESM(require_src2(), 1);
8522
+ PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
8522
8523
  });
8523
8524
 
8524
8525
  // src/agents/dreamer.ts
@@ -22196,6 +22197,16 @@ var MagicContextConfigSchema = exports_external.object({
22196
22197
  provider: "local",
22197
22198
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
22198
22199
  }),
22200
+ experimental: exports_external.object({
22201
+ compaction_markers: exports_external.boolean().default(false),
22202
+ user_memories: exports_external.object({
22203
+ enabled: exports_external.boolean().default(false),
22204
+ promotion_threshold: exports_external.number().min(2).max(20).default(3)
22205
+ }).default({ enabled: false, promotion_threshold: 3 })
22206
+ }).default({
22207
+ compaction_markers: false,
22208
+ user_memories: { enabled: false, promotion_threshold: 3 }
22209
+ }),
22199
22210
  memory: exports_external.object({
22200
22211
  enabled: exports_external.boolean().default(true),
22201
22212
  injection_budget_tokens: exports_external.number().min(500).max(20000).default(4000),
@@ -23261,6 +23272,22 @@ More summary text.
23261
23272
  </output>
23262
23273
 
23263
23274
  Omit empty fact categories. Compartments must be ordered, contiguous for the ranges they cover, and non-overlapping.`;
23275
+ var USER_OBSERVATIONS_APPENDIX = `
23276
+
23277
+ User observation rules (EXPERIMENTAL):
23278
+ - After outputting compartments and facts, also output a <user_observations> section.
23279
+ - User observations capture UNIVERSAL behavioral patterns about the human user \u2014 not project-specific or technical.
23280
+ - Good observations: communication preferences, review focus areas, expertise level, decision-making patterns, frustration triggers, working style.
23281
+ - Bad observations (DO NOT emit): project-specific preferences, framework choices, coding language preferences, one-off moods, task-local frustration.
23282
+ - Each observation must be a single concise sentence in present tense.
23283
+ - Only emit observations you have strong evidence for from the conversation. Do not speculate.
23284
+ - Emit 0-5 observations per run. Zero is fine if nothing stands out.
23285
+ - The output shape inside <output> gains an additional section:
23286
+ <user_observations>
23287
+ * User prefers terse communication and dislikes verbose explanations.
23288
+ * User is technically deep \u2014 understands cache invalidation, SQLite internals, and prompt engineering.
23289
+ </user_observations>
23290
+ If no observations, omit the <user_observations> section entirely.`;
23264
23291
  function buildCompressorPrompt(compartments, currentTokens, targetTokens, averageDepth = 0) {
23265
23292
  const lines = [];
23266
23293
  lines.push(`These ${compartments.length} compartments use approximately ${currentTokens} tokens. Compress them to approximately ${targetTokens} tokens.`);
@@ -23289,7 +23316,7 @@ function buildCompressorPrompt(compartments, currentTokens, targetTokens, averag
23289
23316
  return lines.join(`
23290
23317
  `);
23291
23318
  }
23292
- function buildCompartmentAgentPrompt(existingState, inputSource) {
23319
+ function buildCompartmentAgentPrompt(existingState, inputSource, options) {
23293
23320
  return [
23294
23321
  "Existing state (read-only context for continuity and fact normalization \u2014 do NOT re-emit these compartments):",
23295
23322
  existingState,
@@ -23304,7 +23331,10 @@ function buildCompartmentAgentPrompt(existingState, inputSource) {
23304
23331
  "- Rewrite every fact into terse, present-tense operational form. Merge semantic duplicates within each category.",
23305
23332
  "- Drop any session fact already covered by a project memory in the existing state.",
23306
23333
  "- Do not preserve prior narrative wording verbatim; if a fact is already canonical and still correct, keep or lightly normalize it.",
23307
- "- Drop obsolete or task-local facts."
23334
+ "- Drop obsolete or task-local facts.",
23335
+ ...options?.userMemoriesEnabled ? [
23336
+ "- Also emit <user_observations> with universal behavioral observations about the user (see system prompt for rules)."
23337
+ ] : []
23308
23338
  ].join(`
23309
23339
  `);
23310
23340
  }
@@ -23954,40 +23984,96 @@ function getMemoryCountsByStatus(db, projectPath) {
23954
23984
  return counts;
23955
23985
  }
23956
23986
 
23957
- // src/features/magic-context/storage-smart-notes.ts
23958
- function isSmartNoteRow(row) {
23987
+ // src/features/magic-context/storage-notes.ts
23988
+ var NOTE_TYPES = new Set(["session", "smart"]);
23989
+ var NOTE_STATUSES = new Set(["active", "pending", "ready", "dismissed"]);
23990
+ var DEFAULT_SMART_STATUSES = ["pending", "ready"];
23991
+ function toNullableString(value) {
23992
+ return typeof value === "string" && value.length > 0 ? value : null;
23993
+ }
23994
+ function toNullableNumber(value) {
23995
+ return typeof value === "number" ? value : null;
23996
+ }
23997
+ function isNoteRow(row) {
23959
23998
  if (row === null || typeof row !== "object")
23960
23999
  return false;
23961
- const r = row;
23962
- return typeof r.id === "number" && typeof r.project_path === "string" && typeof r.content === "string" && typeof r.surface_condition === "string" && typeof r.status === "string" && typeof r.created_at === "number" && typeof r.updated_at === "number";
24000
+ const candidate = row;
24001
+ return typeof candidate.id === "number" && typeof candidate.type === "string" && NOTE_TYPES.has(candidate.type) && typeof candidate.status === "string" && NOTE_STATUSES.has(candidate.status) && typeof candidate.content === "string" && (candidate.session_id === null || typeof candidate.session_id === "string") && (candidate.project_path === null || typeof candidate.project_path === "string") && (candidate.surface_condition === null || typeof candidate.surface_condition === "string") && typeof candidate.created_at === "number" && typeof candidate.updated_at === "number" && (candidate.last_checked_at === null || typeof candidate.last_checked_at === "number") && (candidate.ready_at === null || typeof candidate.ready_at === "number") && (candidate.ready_reason === null || typeof candidate.ready_reason === "string");
23963
24002
  }
23964
- function toSmartNote(row) {
24003
+ function toNote(row) {
23965
24004
  return {
23966
24005
  id: row.id,
23967
- projectPath: row.project_path,
23968
- content: row.content,
23969
- surfaceCondition: row.surface_condition,
24006
+ type: row.type,
23970
24007
  status: row.status,
23971
- createdSessionId: row.created_session_id && row.created_session_id.length > 0 ? row.created_session_id : null,
24008
+ content: row.content,
24009
+ sessionId: toNullableString(row.session_id),
24010
+ projectPath: toNullableString(row.project_path),
24011
+ surfaceCondition: toNullableString(row.surface_condition),
23972
24012
  createdAt: row.created_at,
23973
24013
  updatedAt: row.updated_at,
23974
- lastCheckedAt: row.last_checked_at,
23975
- readyAt: row.ready_at,
23976
- readyReason: row.ready_reason && row.ready_reason.length > 0 ? row.ready_reason : null
24014
+ lastCheckedAt: toNullableNumber(row.last_checked_at),
24015
+ readyAt: toNullableNumber(row.ready_at),
24016
+ readyReason: toNullableString(row.ready_reason)
23977
24017
  };
23978
24018
  }
23979
- function addSmartNote(db, projectPath, content, surfaceCondition, sessionId) {
24019
+ function getNoteById(db, noteId) {
24020
+ const row = db.prepare("SELECT * FROM notes WHERE id = ?").get(noteId);
24021
+ return isNoteRow(row) ? toNote(row) : null;
24022
+ }
24023
+ function buildStatusClause(status) {
24024
+ if (status === undefined) {
24025
+ return null;
24026
+ }
24027
+ const statuses = Array.isArray(status) ? status : [status];
24028
+ if (statuses.length === 0) {
24029
+ return null;
24030
+ }
24031
+ const placeholders = statuses.map(() => "?").join(", ");
24032
+ return {
24033
+ sql: `status IN (${placeholders})`,
24034
+ params: statuses
24035
+ };
24036
+ }
24037
+ function getNotes(db, options = {}) {
24038
+ const clauses = [];
24039
+ const params = [];
24040
+ if (options.sessionId !== undefined) {
24041
+ clauses.push("session_id = ?");
24042
+ params.push(options.sessionId);
24043
+ }
24044
+ if (options.projectPath !== undefined) {
24045
+ clauses.push("project_path = ?");
24046
+ params.push(options.projectPath);
24047
+ }
24048
+ if (options.type !== undefined) {
24049
+ clauses.push("type = ?");
24050
+ params.push(options.type);
24051
+ }
24052
+ const statusClause = buildStatusClause(options.status);
24053
+ if (statusClause) {
24054
+ clauses.push(statusClause.sql);
24055
+ params.push(...statusClause.params);
24056
+ }
24057
+ const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
24058
+ return db.prepare(`SELECT * FROM notes${where} ORDER BY created_at ASC, id ASC`).all(...params).filter(isNoteRow).map(toNote);
24059
+ }
24060
+ function addNote(db, type, options) {
23980
24061
  const now = Date.now();
23981
- const result = db.prepare("INSERT INTO smart_notes (project_path, content, surface_condition, status, created_session_id, created_at, updated_at) VALUES (?, ?, ?, 'pending', ?, ?, ?) RETURNING *").get(projectPath, content, surfaceCondition, sessionId ?? null, now, now);
23982
- if (!isSmartNoteRow(result)) {
23983
- throw new Error("[smart-notes] failed to insert smart note");
24062
+ const result = type === "session" ? db.prepare("INSERT INTO notes (type, status, content, session_id, created_at, updated_at) VALUES ('session', 'active', ?, ?, ?, ?) RETURNING *").get(options.content, options.sessionId, now, now) : db.prepare("INSERT INTO notes (type, status, content, session_id, project_path, surface_condition, created_at, updated_at) VALUES ('smart', 'pending', ?, ?, ?, ?, ?, ?) RETURNING *").get(options.content, options.sessionId ?? null, options.projectPath, options.surfaceCondition, now, now);
24063
+ if (!isNoteRow(result)) {
24064
+ throw new Error("[notes] failed to insert note");
23984
24065
  }
23985
- return toSmartNote(result);
24066
+ return toNote(result);
24067
+ }
24068
+ function getSessionNotes(db, sessionId) {
24069
+ return getNotes(db, { sessionId, type: "session", status: "active" });
23986
24070
  }
23987
24071
  function getSmartNotes(db, projectPath, status) {
23988
- const query = status ? "SELECT * FROM smart_notes WHERE project_path = ? AND status = ? ORDER BY created_at ASC" : "SELECT * FROM smart_notes WHERE project_path = ? AND status != 'dismissed' ORDER BY created_at ASC";
23989
- const params = status ? [projectPath, status] : [projectPath];
23990
- return db.prepare(query).all(...params).filter(isSmartNoteRow).map(toSmartNote);
24072
+ return getNotes(db, {
24073
+ projectPath,
24074
+ type: "smart",
24075
+ status: status ?? DEFAULT_SMART_STATUSES
24076
+ });
23991
24077
  }
23992
24078
  function getPendingSmartNotes(db, projectPath) {
23993
24079
  return getSmartNotes(db, projectPath, "pending");
@@ -23995,17 +24081,294 @@ function getPendingSmartNotes(db, projectPath) {
23995
24081
  function getReadySmartNotes(db, projectPath) {
23996
24082
  return getSmartNotes(db, projectPath, "ready");
23997
24083
  }
23998
- function markSmartNoteReady(db, noteId, readyReason) {
24084
+ function updateNote(db, noteId, updates) {
24085
+ const existing = getNoteById(db, noteId);
24086
+ if (!existing) {
24087
+ return null;
24088
+ }
24089
+ const now = Date.now();
24090
+ const sets = ["updated_at = ?"];
24091
+ const params = [now];
24092
+ if (updates.content !== undefined) {
24093
+ sets.push("content = ?");
24094
+ params.push(updates.content);
24095
+ }
24096
+ if (updates.sessionId !== undefined) {
24097
+ sets.push("session_id = ?");
24098
+ params.push(updates.sessionId);
24099
+ }
24100
+ if (updates.status !== undefined) {
24101
+ sets.push("status = ?");
24102
+ params.push(updates.status);
24103
+ }
24104
+ if (existing.type === "smart") {
24105
+ if (updates.projectPath !== undefined) {
24106
+ sets.push("project_path = ?");
24107
+ params.push(updates.projectPath);
24108
+ }
24109
+ if (updates.surfaceCondition !== undefined) {
24110
+ sets.push("surface_condition = ?");
24111
+ params.push(updates.surfaceCondition);
24112
+ }
24113
+ if (updates.lastCheckedAt !== undefined) {
24114
+ sets.push("last_checked_at = ?");
24115
+ params.push(updates.lastCheckedAt);
24116
+ }
24117
+ if (updates.readyAt !== undefined) {
24118
+ sets.push("ready_at = ?");
24119
+ params.push(updates.readyAt);
24120
+ }
24121
+ if (updates.readyReason !== undefined) {
24122
+ sets.push("ready_reason = ?");
24123
+ params.push(updates.readyReason);
24124
+ }
24125
+ }
24126
+ if (sets.length === 1) {
24127
+ return null;
24128
+ }
24129
+ params.push(noteId);
24130
+ const result = db.prepare(`UPDATE notes SET ${sets.join(", ")} WHERE id = ? RETURNING *`).get(...params);
24131
+ return isNoteRow(result) ? toNote(result) : null;
24132
+ }
24133
+ function dismissNote(db, noteId) {
24134
+ const result = db.prepare("UPDATE notes SET status = 'dismissed', updated_at = ? WHERE id = ? AND status != 'dismissed'").run(Date.now(), noteId);
24135
+ return result.changes > 0;
24136
+ }
24137
+ function markNoteReady(db, noteId, reason) {
23999
24138
  const now = Date.now();
24000
- db.prepare("UPDATE smart_notes SET status = 'ready', ready_at = ?, ready_reason = ?, updated_at = ?, last_checked_at = ? WHERE id = ?").run(now, readyReason ?? null, now, now, noteId);
24139
+ db.prepare("UPDATE notes SET status = 'ready', ready_at = ?, ready_reason = ?, updated_at = ?, last_checked_at = ? WHERE id = ? AND type = 'smart'").run(now, reason ?? null, now, now, noteId);
24001
24140
  }
24002
- function markSmartNoteChecked(db, noteId) {
24141
+ function markNoteChecked(db, noteId) {
24003
24142
  const now = Date.now();
24004
- db.prepare("UPDATE smart_notes SET last_checked_at = ?, updated_at = ? WHERE id = ?").run(now, now, noteId);
24143
+ db.prepare("UPDATE notes SET last_checked_at = ?, updated_at = ? WHERE id = ? AND type = 'smart'").run(now, now, noteId);
24005
24144
  }
24006
- function dismissSmartNote(db, noteId) {
24007
- const result = db.prepare("UPDATE smart_notes SET status = 'dismissed', updated_at = ? WHERE id = ?").run(Date.now(), noteId);
24008
- return result.changes > 0;
24145
+
24146
+ // src/features/magic-context/user-memory/review-user-memories.ts
24147
+ init_logger();
24148
+
24149
+ // src/features/magic-context/user-memory/storage-user-memory.ts
24150
+ function insertUserMemoryCandidates(db, candidates) {
24151
+ if (candidates.length === 0)
24152
+ return;
24153
+ const now = Date.now();
24154
+ const stmt = db.prepare("INSERT INTO user_memory_candidates (content, session_id, source_compartment_start, source_compartment_end, created_at) VALUES (?, ?, ?, ?, ?)");
24155
+ db.transaction(() => {
24156
+ for (const c of candidates) {
24157
+ stmt.run(c.content, c.sessionId, c.sourceCompartmentStart ?? null, c.sourceCompartmentEnd ?? null, now);
24158
+ }
24159
+ })();
24160
+ }
24161
+ function getUserMemoryCandidates(db) {
24162
+ const rows = db.prepare("SELECT id, content, session_id, source_compartment_start, source_compartment_end, created_at FROM user_memory_candidates ORDER BY created_at ASC").all();
24163
+ return rows.map((r) => ({
24164
+ id: r.id,
24165
+ content: r.content,
24166
+ sessionId: r.session_id,
24167
+ sourceCompartmentStart: r.source_compartment_start,
24168
+ sourceCompartmentEnd: r.source_compartment_end,
24169
+ createdAt: r.created_at
24170
+ }));
24171
+ }
24172
+ function deleteUserMemoryCandidates(db, ids) {
24173
+ if (ids.length === 0)
24174
+ return;
24175
+ const placeholders = ids.map(() => "?").join(",");
24176
+ db.prepare(`DELETE FROM user_memory_candidates WHERE id IN (${placeholders})`).run(...ids);
24177
+ }
24178
+ function insertUserMemory(db, content, sourceCandidateIds) {
24179
+ const now = Date.now();
24180
+ const result = db.prepare("INSERT INTO user_memories (content, status, promoted_at, source_candidate_ids, created_at, updated_at) VALUES (?, 'active', ?, ?, ?, ?)").run(content, now, JSON.stringify(sourceCandidateIds), now, now);
24181
+ return Number(result.lastInsertRowid);
24182
+ }
24183
+ function getActiveUserMemories(db) {
24184
+ const rows = db.prepare("SELECT id, content, status, promoted_at, source_candidate_ids, created_at, updated_at FROM user_memories WHERE status = 'active' ORDER BY promoted_at ASC").all();
24185
+ return rows.map(parseUserMemoryRow);
24186
+ }
24187
+ function updateUserMemoryContent(db, id, content) {
24188
+ db.prepare("UPDATE user_memories SET content = ?, updated_at = ? WHERE id = ?").run(content, Date.now(), id);
24189
+ }
24190
+ function dismissUserMemory(db, id) {
24191
+ db.prepare("UPDATE user_memories SET status = 'dismissed', updated_at = ? WHERE id = ?").run(Date.now(), id);
24192
+ }
24193
+ function parseUserMemoryRow(row) {
24194
+ let candidateIds = [];
24195
+ try {
24196
+ candidateIds = JSON.parse(row.source_candidate_ids);
24197
+ } catch {}
24198
+ return {
24199
+ id: row.id,
24200
+ content: row.content,
24201
+ status: row.status === "dismissed" ? "dismissed" : "active",
24202
+ promotedAt: row.promoted_at,
24203
+ sourceCandidateIds: candidateIds,
24204
+ createdAt: row.created_at,
24205
+ updatedAt: row.updated_at
24206
+ };
24207
+ }
24208
+
24209
+ // src/features/magic-context/user-memory/review-user-memories.ts
24210
+ async function reviewUserMemories(args) {
24211
+ const result = { promoted: 0, merged: 0, dismissed: 0, candidatesConsumed: 0 };
24212
+ const candidates = getUserMemoryCandidates(args.db);
24213
+ if (candidates.length < args.promotionThreshold) {
24214
+ log(`[dreamer] user-memories: ${candidates.length} candidate(s), need ${args.promotionThreshold} \u2014 skipping`);
24215
+ return result;
24216
+ }
24217
+ const stableMemories = getActiveUserMemories(args.db);
24218
+ log(`[dreamer] user-memories: reviewing ${candidates.length} candidate(s) against ${stableMemories.length} stable memorie(s)`);
24219
+ const candidateList = candidates.map((c) => `- Candidate #${c.id} [session ${c.sessionId.slice(0, 12)}]: "${c.content}"`).join(`
24220
+ `);
24221
+ const stableList = stableMemories.length > 0 ? stableMemories.map((m) => `- Memory #${m.id}: "${m.content}"`).join(`
24222
+ `) : "(none)";
24223
+ const prompt = `## Task: Review User Memory Candidates
24224
+
24225
+ You are reviewing behavioral observations about a human user to decide which patterns are real and persistent.
24226
+
24227
+ ### Current Stable User Memories
24228
+ ${stableList}
24229
+
24230
+ ### Candidate Observations (from recent historian runs)
24231
+ ${candidateList}
24232
+
24233
+ ### Instructions
24234
+
24235
+ 1. Look for **recurring patterns** across multiple candidates \u2014 observations that appear independently from different sessions or historian runs indicate a real user trait.
24236
+ 2. A candidate must appear in at least ${args.promotionThreshold} semantically similar variants before promotion.
24237
+ 3. Only promote **truly universal** user traits \u2014 communication style, expertise level, review focus, decision-making patterns, working habits.
24238
+ 4. Do NOT promote: project-specific preferences, framework choices, one-off moods, task-local frustrations.
24239
+ 5. If a candidate is semantically equivalent to an existing stable memory, mark it as already covered.
24240
+ 6. If multiple candidates describe the same trait, merge them into one clean statement.
24241
+ 7. If an existing stable memory should be updated based on new evidence, include the update.
24242
+
24243
+ ### Output Format
24244
+
24245
+ Return valid JSON (no markdown fencing):
24246
+
24247
+ {
24248
+ "promote": [
24249
+ { "content": "Clean universal observation text", "candidate_ids": [1, 3, 7] }
24250
+ ],
24251
+ "update_existing": [
24252
+ { "memory_id": 5, "content": "Updated text incorporating new evidence", "candidate_ids": [2] }
24253
+ ],
24254
+ "dismiss_existing": [
24255
+ { "memory_id": 3, "reason": "No longer supported by recent observations" }
24256
+ ],
24257
+ "consume_candidate_ids": [1, 2, 3, 4, 5, 7, 8]
24258
+ }
24259
+
24260
+ - \`promote\`: new stable memories to create from candidates
24261
+ - \`update_existing\`: existing stable memories to rewrite with new evidence
24262
+ - \`dismiss_existing\`: existing stable memories that are no longer valid
24263
+ - \`consume_candidate_ids\`: ALL candidate IDs that were reviewed (promoted, merged, or rejected) \u2014 they will be deleted from the candidate pool
24264
+
24265
+ If no promotions are warranted, return empty arrays. Always consume reviewed candidates so they don't accumulate indefinitely.`;
24266
+ let agentSessionId = null;
24267
+ const abortController = new AbortController;
24268
+ const leaseInterval = setInterval(() => {
24269
+ try {
24270
+ if (!renewLease(args.db, args.holderId)) {
24271
+ log("[dreamer] user-memories: lease renewal failed \u2014 aborting");
24272
+ abortController.abort();
24273
+ }
24274
+ } catch {
24275
+ abortController.abort();
24276
+ }
24277
+ }, 60000);
24278
+ try {
24279
+ const createResponse = await args.client.session.create({
24280
+ body: {
24281
+ ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
24282
+ title: "magic-context-dream-user-memories"
24283
+ },
24284
+ query: { directory: args.sessionDirectory }
24285
+ });
24286
+ const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
24287
+ agentSessionId = typeof created?.id === "string" ? created.id : null;
24288
+ if (!agentSessionId)
24289
+ throw new Error("Could not create user memory review session.");
24290
+ log(`[dreamer] user-memories: child session created ${agentSessionId}`);
24291
+ const remainingMs = Math.max(0, args.deadline - Date.now());
24292
+ await promptSyncWithModelSuggestionRetry(args.client, {
24293
+ path: { id: agentSessionId },
24294
+ query: { directory: args.sessionDirectory },
24295
+ body: {
24296
+ agent: DREAMER_AGENT,
24297
+ system: DREAMER_SYSTEM_PROMPT,
24298
+ parts: [{ type: "text", text: prompt }]
24299
+ }
24300
+ }, { timeoutMs: Math.min(remainingMs, 5 * 60 * 1000), signal: abortController.signal });
24301
+ const messagesResponse = await args.client.session.messages({
24302
+ path: { id: agentSessionId },
24303
+ query: { directory: args.sessionDirectory }
24304
+ });
24305
+ const messages = normalizeSDKResponse(messagesResponse, [], {
24306
+ preferResponseOnMissingData: true
24307
+ });
24308
+ const responseText = extractLatestAssistantText(messages);
24309
+ if (!responseText) {
24310
+ log("[dreamer] user-memories: no response from review agent");
24311
+ return result;
24312
+ }
24313
+ const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/) ?? responseText.match(/(\{[\s\S]*\})/);
24314
+ if (!jsonMatch) {
24315
+ log("[dreamer] user-memories: could not parse JSON from response");
24316
+ return result;
24317
+ }
24318
+ let parsed;
24319
+ try {
24320
+ parsed = JSON.parse(jsonMatch[1]);
24321
+ } catch {
24322
+ log("[dreamer] user-memories: JSON parse failed");
24323
+ return result;
24324
+ }
24325
+ if (parsed.promote) {
24326
+ for (const p of parsed.promote) {
24327
+ if (p.content?.trim()) {
24328
+ insertUserMemory(args.db, p.content.trim(), p.candidate_ids ?? []);
24329
+ result.promoted++;
24330
+ log(`[dreamer] user-memories: promoted "${p.content.trim().slice(0, 60)}..."`);
24331
+ }
24332
+ }
24333
+ }
24334
+ if (parsed.update_existing) {
24335
+ for (const u of parsed.update_existing) {
24336
+ if (u.memory_id && u.content?.trim()) {
24337
+ updateUserMemoryContent(args.db, u.memory_id, u.content.trim());
24338
+ result.merged++;
24339
+ log(`[dreamer] user-memories: updated memory #${u.memory_id}`);
24340
+ }
24341
+ }
24342
+ }
24343
+ if (parsed.dismiss_existing) {
24344
+ for (const d of parsed.dismiss_existing) {
24345
+ if (d.memory_id) {
24346
+ dismissUserMemory(args.db, d.memory_id);
24347
+ result.dismissed++;
24348
+ log(`[dreamer] user-memories: dismissed memory #${d.memory_id} \u2014 ${d.reason ?? "no reason"}`);
24349
+ }
24350
+ }
24351
+ }
24352
+ if (parsed.consume_candidate_ids && parsed.consume_candidate_ids.length > 0) {
24353
+ deleteUserMemoryCandidates(args.db, parsed.consume_candidate_ids);
24354
+ result.candidatesConsumed = parsed.consume_candidate_ids.length;
24355
+ log(`[dreamer] user-memories: consumed ${result.candidatesConsumed} candidate(s)`);
24356
+ }
24357
+ return result;
24358
+ } catch (error48) {
24359
+ log(`[dreamer] user-memories: review failed: ${getErrorMessage(error48)}`);
24360
+ return result;
24361
+ } finally {
24362
+ clearInterval(leaseInterval);
24363
+ if (agentSessionId) {
24364
+ await args.client.session.delete({
24365
+ path: { id: agentSessionId },
24366
+ query: { directory: args.sessionDirectory }
24367
+ }).catch((e) => {
24368
+ log(`[dreamer] user-memories: session cleanup failed: ${getErrorMessage(e)}`);
24369
+ });
24370
+ }
24371
+ }
24009
24372
  }
24010
24373
 
24011
24374
  // src/features/magic-context/dreamer/storage-dream-runs.ts
@@ -24185,6 +24548,24 @@ async function runDream(args) {
24185
24548
  }
24186
24549
  }
24187
24550
  }
24551
+ if (args.experimentalUserMemories?.enabled && Date.now() <= deadline) {
24552
+ try {
24553
+ const reviewResult = await reviewUserMemories({
24554
+ db: args.db,
24555
+ client: args.client,
24556
+ parentSessionId,
24557
+ sessionDirectory: args.sessionDirectory,
24558
+ holderId,
24559
+ deadline,
24560
+ promotionThreshold: args.experimentalUserMemories.promotionThreshold
24561
+ });
24562
+ if (reviewResult.promoted > 0 || reviewResult.merged > 0 || reviewResult.dismissed > 0) {
24563
+ log(`[dreamer] user-memories: promoted=${reviewResult.promoted} merged=${reviewResult.merged} dismissed=${reviewResult.dismissed} consumed=${reviewResult.candidatesConsumed}`);
24564
+ }
24565
+ } catch (error48) {
24566
+ log(`[dreamer] user-memory review failed: ${getErrorMessage(error48)}`);
24567
+ }
24568
+ }
24188
24569
  if (Date.now() <= deadline) {
24189
24570
  try {
24190
24571
  await evaluateSmartNotes({
@@ -24325,7 +24706,7 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
24325
24706
  if (!jsonMatch) {
24326
24707
  log("[dreamer] smart notes: no JSON array found in output, skipping");
24327
24708
  for (const note of pendingNotes)
24328
- markSmartNoteChecked(args.db, note.id);
24709
+ markNoteChecked(args.db, note.id);
24329
24710
  throw new Error("Smart note evaluation returned no JSON array.");
24330
24711
  }
24331
24712
  let evaluations;
@@ -24334,7 +24715,7 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
24334
24715
  } catch {
24335
24716
  log(`[dreamer] smart notes: failed to parse JSON from LLM output, marking all checked`);
24336
24717
  for (const note of pendingNotes)
24337
- markSmartNoteChecked(args.db, note.id);
24718
+ markNoteChecked(args.db, note.id);
24338
24719
  throw new Error("Smart note evaluation returned invalid JSON.");
24339
24720
  }
24340
24721
  let surfaced = 0;
@@ -24345,16 +24726,16 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
24345
24726
  if (!note)
24346
24727
  continue;
24347
24728
  if (evaluation.met) {
24348
- markSmartNoteReady(args.db, note.id, evaluation.reason);
24729
+ markNoteReady(args.db, note.id, evaluation.reason);
24349
24730
  surfaced++;
24350
24731
  log(`[dreamer] smart notes: #${note.id} condition MET \u2014 "${evaluation.reason ?? "condition satisfied"}"`);
24351
24732
  } else {
24352
- markSmartNoteChecked(args.db, note.id);
24733
+ markNoteChecked(args.db, note.id);
24353
24734
  }
24354
24735
  }
24355
24736
  for (const note of pendingNotes) {
24356
24737
  if (!evaluations.some((e) => e.id === note.id)) {
24357
- markSmartNoteChecked(args.db, note.id);
24738
+ markNoteChecked(args.db, note.id);
24358
24739
  }
24359
24740
  }
24360
24741
  const durationMs = Date.now() - taskStartedAt;
@@ -24408,7 +24789,8 @@ async function processDreamQueue(args) {
24408
24789
  tasks: args.tasks,
24409
24790
  taskTimeoutMinutes: args.taskTimeoutMinutes,
24410
24791
  maxRuntimeMinutes: args.maxRuntimeMinutes,
24411
- sessionDirectory: projectDirectory
24792
+ sessionDirectory: projectDirectory,
24793
+ experimentalUserMemories: args.experimentalUserMemories
24412
24794
  });
24413
24795
  } catch (error48) {
24414
24796
  log(`[dreamer] runDream threw for ${entry.projectIdentity}: ${getErrorMessage(error48)}`);
@@ -24460,7 +24842,8 @@ function isInScheduleWindow(schedule, now = new Date) {
24460
24842
  function findProjectsNeedingDream(db) {
24461
24843
  const projectRows = db.query(`SELECT DISTINCT project_path FROM memories WHERE status = 'active'
24462
24844
  UNION
24463
- SELECT DISTINCT project_path FROM smart_notes WHERE status = 'pending'
24845
+ SELECT DISTINCT project_path FROM notes
24846
+ WHERE type = 'smart' AND status = 'pending' AND project_path IS NOT NULL
24464
24847
  ORDER BY project_path`).all();
24465
24848
  const projects = [];
24466
24849
  for (const row of projectRows) {
@@ -24469,8 +24852,8 @@ function findProjectsNeedingDream(db) {
24469
24852
  const lastDreamAt = Number(lastDreamAtStr ?? fallbackStr ?? "0") || 0;
24470
24853
  const updatedMemories = db.query(`SELECT COUNT(*) as cnt FROM memories
24471
24854
  WHERE project_path = ? AND status = 'active' AND updated_at > ?`).get(row.project_path, lastDreamAt);
24472
- const pendingSmartNotes = db.query(`SELECT COUNT(*) as cnt FROM smart_notes
24473
- WHERE project_path = ? AND status = 'pending'`).get(row.project_path);
24855
+ const pendingSmartNotes = db.query(`SELECT COUNT(*) as cnt FROM notes
24856
+ WHERE project_path = ? AND type = 'smart' AND status = 'pending'`).get(row.project_path);
24474
24857
  if (updatedMemories && updatedMemories.cnt > 0 || pendingSmartNotes && pendingSmartNotes.cnt > 0) {
24475
24858
  projects.push(row.project_path);
24476
24859
  }
@@ -24517,6 +24900,22 @@ function cosineSimilarity(a, b) {
24517
24900
 
24518
24901
  // src/features/magic-context/memory/embedding-local.ts
24519
24902
  init_logger();
24903
+ async function withQuietConsole(fn) {
24904
+ const origWarn = console.warn;
24905
+ const origError = console.error;
24906
+ const redirect = (...args) => {
24907
+ const message = args.map((a) => typeof a === "string" ? a : String(a)).join(" ");
24908
+ log(`[transformers] ${message}`);
24909
+ };
24910
+ console.warn = redirect;
24911
+ console.error = redirect;
24912
+ try {
24913
+ return await fn();
24914
+ } finally {
24915
+ console.warn = origWarn;
24916
+ console.error = origError;
24917
+ }
24918
+ }
24520
24919
  function isArrayLikeNumber(value) {
24521
24920
  if (typeof value !== "object" || value === null || !("length" in value)) {
24522
24921
  return false;
@@ -24572,10 +24971,16 @@ class LocalEmbeddingProvider {
24572
24971
  this.initPromise = (async () => {
24573
24972
  try {
24574
24973
  const transformersModule = await import("@huggingface/transformers");
24974
+ const env = transformersModule.env;
24975
+ const LogLevel = transformersModule.LogLevel;
24976
+ if (LogLevel && "ERROR" in LogLevel) {
24977
+ env.logLevel = LogLevel.ERROR;
24978
+ }
24575
24979
  const createPipeline = transformersModule.pipeline;
24576
- this.pipeline = await createPipeline("feature-extraction", this.model, {
24577
- quantized: true
24578
- });
24980
+ this.pipeline = await withQuietConsole(() => createPipeline("feature-extraction", this.model, {
24981
+ quantized: true,
24982
+ dtype: "fp32"
24983
+ }));
24579
24984
  log(`[magic-context] embedding model loaded: ${this.model}`);
24580
24985
  } catch (error48) {
24581
24986
  log("[magic-context] embedding model failed to load:", error48);
@@ -24592,13 +24997,14 @@ class LocalEmbeddingProvider {
24592
24997
  return null;
24593
24998
  }
24594
24999
  try {
24595
- if (!this.pipeline) {
25000
+ const pipeline = this.pipeline;
25001
+ if (!pipeline) {
24596
25002
  return null;
24597
25003
  }
24598
- const result = await this.pipeline(text, {
25004
+ const result = await withQuietConsole(() => pipeline(text, {
24599
25005
  pooling: "mean",
24600
25006
  normalize: true
24601
- });
25007
+ }));
24602
25008
  return extractBatchEmbeddings(result, 1)[0] ?? null;
24603
25009
  } catch (error48) {
24604
25010
  log("[magic-context] embedding failed:", error48);
@@ -24613,13 +25019,14 @@ class LocalEmbeddingProvider {
24613
25019
  return Array.from({ length: texts.length }, () => null);
24614
25020
  }
24615
25021
  try {
24616
- if (!this.pipeline) {
25022
+ const pipeline = this.pipeline;
25023
+ if (!pipeline) {
24617
25024
  return Array.from({ length: texts.length }, () => null);
24618
25025
  }
24619
- const result = await this.pipeline(texts, {
25026
+ const result = await withQuietConsole(() => pipeline(texts, {
24620
25027
  pooling: "mean",
24621
25028
  normalize: true
24622
- });
25029
+ }));
24623
25030
  return extractBatchEmbeddings(result, texts.length);
24624
25031
  } catch (error48) {
24625
25032
  log("[magic-context] embedding batch failed:", error48);
@@ -25219,7 +25626,11 @@ function readRawSessionMessagesFromDb(db, sessionId) {
25219
25626
  list.push(parseJsonUnknown(part.data));
25220
25627
  partsByMessageId.set(part.message_id, list);
25221
25628
  }
25222
- return messageRows.flatMap((row, index) => {
25629
+ const filtered = messageRows.filter((row) => {
25630
+ const info = parseJsonRecord(row.data);
25631
+ return !(info?.summary === true && info?.finish === "stop");
25632
+ });
25633
+ return filtered.flatMap((row, index) => {
25223
25634
  const info = parseJsonRecord(row.data);
25224
25635
  if (!info)
25225
25636
  return [];
@@ -25593,6 +26004,158 @@ import { Database as Database2 } from "bun:sqlite";
25593
26004
  import { mkdirSync } from "fs";
25594
26005
  import { join as join9 } from "path";
25595
26006
  init_logger();
26007
+
26008
+ // src/features/magic-context/migrations.ts
26009
+ init_logger();
26010
+ var MIGRATIONS = [
26011
+ {
26012
+ version: 1,
26013
+ description: "Merge session_notes + smart_notes into unified notes table",
26014
+ up: (db) => {
26015
+ db.exec(`
26016
+ CREATE TABLE IF NOT EXISTS notes (
26017
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26018
+ type TEXT NOT NULL DEFAULT 'session',
26019
+ status TEXT NOT NULL DEFAULT 'active',
26020
+ content TEXT NOT NULL,
26021
+ session_id TEXT,
26022
+ project_path TEXT,
26023
+ surface_condition TEXT,
26024
+ created_at INTEGER NOT NULL,
26025
+ updated_at INTEGER NOT NULL,
26026
+ last_checked_at INTEGER,
26027
+ ready_at INTEGER,
26028
+ ready_reason TEXT
26029
+ );
26030
+ CREATE INDEX IF NOT EXISTS idx_notes_session_status ON notes(session_id, status);
26031
+ CREATE INDEX IF NOT EXISTS idx_notes_project_status ON notes(project_path, status);
26032
+ CREATE INDEX IF NOT EXISTS idx_notes_type_status ON notes(type, status);
26033
+ `);
26034
+ const hasSessionNotes = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='session_notes'").get();
26035
+ if (hasSessionNotes) {
26036
+ db.exec(`
26037
+ INSERT INTO notes (type, status, content, session_id, created_at, updated_at)
26038
+ SELECT 'session', 'active', content, session_id, created_at, created_at
26039
+ FROM session_notes
26040
+ `);
26041
+ }
26042
+ const hasSmartNotes = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='smart_notes'").get();
26043
+ if (hasSmartNotes) {
26044
+ db.exec(`
26045
+ INSERT INTO notes (type, status, content, session_id, project_path, surface_condition,
26046
+ created_at, updated_at, last_checked_at, ready_at, ready_reason)
26047
+ SELECT 'smart', status, content, created_session_id, project_path, surface_condition,
26048
+ created_at, updated_at, last_checked_at, ready_at, ready_reason
26049
+ FROM smart_notes
26050
+ `);
26051
+ }
26052
+ if (hasSessionNotes) {
26053
+ const sourceCount = db.prepare("SELECT COUNT(*) as c FROM session_notes").get().c;
26054
+ const migratedCount = db.prepare("SELECT COUNT(*) as c FROM notes WHERE type = 'session'").get().c;
26055
+ if (migratedCount >= sourceCount) {
26056
+ db.exec("DROP TABLE session_notes");
26057
+ } else {
26058
+ throw new Error(`session_notes migration verification failed: expected ${sourceCount} rows, got ${migratedCount}`);
26059
+ }
26060
+ }
26061
+ if (hasSmartNotes) {
26062
+ const sourceCount = db.prepare("SELECT COUNT(*) as c FROM smart_notes").get().c;
26063
+ const migratedCount = db.prepare("SELECT COUNT(*) as c FROM notes WHERE type = 'smart'").get().c;
26064
+ if (migratedCount >= sourceCount) {
26065
+ db.exec("DROP TABLE smart_notes");
26066
+ } else {
26067
+ throw new Error(`smart_notes migration verification failed: expected ${sourceCount} rows, got ${migratedCount}`);
26068
+ }
26069
+ }
26070
+ }
26071
+ },
26072
+ {
26073
+ version: 2,
26074
+ description: "Add plugin_messages table for TUI \u2194 server communication",
26075
+ up: (db) => {
26076
+ db.exec(`
26077
+ CREATE TABLE IF NOT EXISTS plugin_messages (
26078
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26079
+ direction TEXT NOT NULL,
26080
+ type TEXT NOT NULL,
26081
+ payload TEXT NOT NULL DEFAULT '{}',
26082
+ session_id TEXT,
26083
+ created_at INTEGER NOT NULL,
26084
+ consumed_at INTEGER
26085
+ );
26086
+ CREATE INDEX IF NOT EXISTS idx_plugin_messages_direction_consumed
26087
+ ON plugin_messages(direction, consumed_at);
26088
+ CREATE INDEX IF NOT EXISTS idx_plugin_messages_created
26089
+ ON plugin_messages(created_at);
26090
+ `);
26091
+ }
26092
+ },
26093
+ {
26094
+ version: 3,
26095
+ description: "Add user_memory_candidates and user_memories tables",
26096
+ up: (db) => {
26097
+ db.exec(`
26098
+ CREATE TABLE IF NOT EXISTS user_memory_candidates (
26099
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26100
+ content TEXT NOT NULL,
26101
+ session_id TEXT NOT NULL,
26102
+ source_compartment_start INTEGER,
26103
+ source_compartment_end INTEGER,
26104
+ created_at INTEGER NOT NULL
26105
+ );
26106
+ CREATE INDEX IF NOT EXISTS idx_umc_created ON user_memory_candidates(created_at);
26107
+
26108
+ CREATE TABLE IF NOT EXISTS user_memories (
26109
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26110
+ content TEXT NOT NULL,
26111
+ status TEXT NOT NULL DEFAULT 'active',
26112
+ promoted_at INTEGER NOT NULL,
26113
+ source_candidate_ids TEXT DEFAULT '[]',
26114
+ created_at INTEGER NOT NULL,
26115
+ updated_at INTEGER NOT NULL
26116
+ );
26117
+ CREATE INDEX IF NOT EXISTS idx_um_status ON user_memories(status);
26118
+ `);
26119
+ }
26120
+ }
26121
+ ];
26122
+ function ensureMigrationsTable(db) {
26123
+ db.exec(`
26124
+ CREATE TABLE IF NOT EXISTS schema_migrations (
26125
+ version INTEGER PRIMARY KEY,
26126
+ description TEXT NOT NULL,
26127
+ applied_at INTEGER NOT NULL
26128
+ )
26129
+ `);
26130
+ }
26131
+ function getCurrentVersion(db) {
26132
+ const row = db.prepare("SELECT MAX(version) as version FROM schema_migrations").get();
26133
+ return row?.version ?? 0;
26134
+ }
26135
+ function runMigrations(db) {
26136
+ ensureMigrationsTable(db);
26137
+ const currentVersion = getCurrentVersion(db);
26138
+ const pendingMigrations = MIGRATIONS.filter((m) => m.version > currentVersion);
26139
+ if (pendingMigrations.length === 0) {
26140
+ return;
26141
+ }
26142
+ log(`[migrations] current schema version: ${currentVersion}, applying ${pendingMigrations.length} migration(s)`);
26143
+ for (const migration of pendingMigrations) {
26144
+ try {
26145
+ db.transaction(() => {
26146
+ migration.up(db);
26147
+ db.prepare("INSERT INTO schema_migrations (version, description, applied_at) VALUES (?, ?, ?)").run(migration.version, migration.description, Date.now());
26148
+ })();
26149
+ log(`[migrations] applied v${migration.version}: ${migration.description}`);
26150
+ } catch (error48) {
26151
+ log(`[migrations] FAILED v${migration.version}: ${migration.description} \u2014 ${error48 instanceof Error ? error48.message : String(error48)}`);
26152
+ throw new Error(`Migration v${migration.version} failed: ${error48 instanceof Error ? error48.message : String(error48)}. Database may need manual repair.`);
26153
+ }
26154
+ }
26155
+ log(`[migrations] schema version now: ${MIGRATIONS[MIGRATIONS.length - 1].version}`);
26156
+ }
26157
+
26158
+ // src/features/magic-context/storage-db.ts
25596
26159
  var databases = new Map;
25597
26160
  var FALLBACK_DATABASE_KEY = "__fallback__:memory:";
25598
26161
  var persistenceByDatabase = new WeakMap;
@@ -25872,6 +26435,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
25872
26435
  ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
25873
26436
  ensureColumn(db, "tags", "reasoning_byte_size", "INTEGER DEFAULT 0");
25874
26437
  ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
26438
+ ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
25875
26439
  }
25876
26440
  function ensureColumn(db, table, column, definition) {
25877
26441
  if (!/^[a-z_]+$/.test(table) || !/^[a-z_]+$/.test(column) || !/^[A-Z0-9_'(),\s]+$/i.test(definition)) {
@@ -25887,6 +26451,7 @@ function createFallbackDatabase() {
25887
26451
  try {
25888
26452
  const fallback = new Database2(":memory:");
25889
26453
  initializeDatabase(fallback);
26454
+ runMigrations(fallback);
25890
26455
  return fallback;
25891
26456
  } catch (error48) {
25892
26457
  throw new Error(`[magic-context] storage fatal: failed to initialize fallback database: ${getErrorMessage(error48)}`);
@@ -25905,6 +26470,7 @@ function openDatabase() {
25905
26470
  mkdirSync(dbDir, { recursive: true });
25906
26471
  const db = new Database2(dbPath);
25907
26472
  initializeDatabase(db);
26473
+ runMigrations(db);
25908
26474
  databases.set(dbPath, db);
25909
26475
  persistenceByDatabase.set(db, true);
25910
26476
  persistenceErrorByDatabase.delete(db);
@@ -26137,6 +26703,24 @@ function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
26137
26703
  function clearPersistedNoteNudge(db, sessionId) {
26138
26704
  db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '', note_nudge_sticky_text = '', note_nudge_sticky_message_id = '' WHERE session_id = ?").run(sessionId);
26139
26705
  }
26706
+ function getPersistedCompactionMarkerState(db, sessionId) {
26707
+ const row = db.prepare("SELECT compaction_marker_state FROM session_meta WHERE session_id = ?").get(sessionId);
26708
+ const raw = row?.compaction_marker_state;
26709
+ if (!raw || raw.length === 0)
26710
+ return null;
26711
+ try {
26712
+ const parsed = JSON.parse(raw);
26713
+ if (parsed && typeof parsed === "object" && typeof parsed.boundaryMessageId === "string" && typeof parsed.summaryMessageId === "string" && typeof parsed.compactionPartId === "string" && typeof parsed.summaryPartId === "string" && typeof parsed.boundaryOrdinal === "number") {
26714
+ return parsed;
26715
+ }
26716
+ } catch {}
26717
+ return null;
26718
+ }
26719
+ function setPersistedCompactionMarkerState(db, sessionId, state) {
26720
+ ensureSessionMetaRow(db, sessionId);
26721
+ const json2 = state ? JSON.stringify(state) : "";
26722
+ db.prepare("UPDATE session_meta SET compaction_marker_state = ? WHERE session_id = ?").run(json2, sessionId);
26723
+ }
26140
26724
  function getStrippedPlaceholderIds(db, sessionId) {
26141
26725
  const row = db.prepare("SELECT stripped_placeholder_ids FROM session_meta WHERE session_id = ?").get(sessionId);
26142
26726
  const raw = row?.stripped_placeholder_ids;
@@ -26207,37 +26791,12 @@ function clearSession(db, sessionId) {
26207
26791
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
26208
26792
  clearCompressionDepth(db, sessionId);
26209
26793
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
26210
- db.prepare("DELETE FROM session_notes WHERE session_id = ?").run(sessionId);
26794
+ db.prepare("DELETE FROM notes WHERE session_id = ? AND type = 'session'").run(sessionId);
26211
26795
  db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
26212
26796
  db.prepare("DELETE FROM recomp_facts WHERE session_id = ?").run(sessionId);
26213
26797
  clearIndexedMessages(db, sessionId);
26214
26798
  })();
26215
26799
  }
26216
- // src/features/magic-context/storage-notes.ts
26217
- function isSessionNoteRow(row) {
26218
- if (row === null || typeof row !== "object")
26219
- return false;
26220
- const candidate = row;
26221
- return typeof candidate.id === "number" && typeof candidate.session_id === "string" && typeof candidate.content === "string" && typeof candidate.created_at === "number";
26222
- }
26223
- function toSessionNote(row) {
26224
- return {
26225
- id: row.id,
26226
- sessionId: row.session_id,
26227
- content: row.content,
26228
- createdAt: row.created_at
26229
- };
26230
- }
26231
- function getSessionNotes(db, sessionId) {
26232
- const rows = db.prepare("SELECT * FROM session_notes WHERE session_id = ? ORDER BY id ASC").all(sessionId).filter(isSessionNoteRow);
26233
- return rows.map(toSessionNote);
26234
- }
26235
- function addSessionNote(db, sessionId, content) {
26236
- db.prepare("INSERT INTO session_notes (session_id, content, created_at) VALUES (?, ?, ?)").run(sessionId, content, Date.now());
26237
- }
26238
- function clearSessionNotes(db, sessionId) {
26239
- db.prepare("DELETE FROM session_notes WHERE session_id = ?").run(sessionId);
26240
- }
26241
26800
  // src/features/magic-context/storage-ops.ts
26242
26801
  init_logger();
26243
26802
  var queuePendingOpStatements = new WeakMap;
@@ -26454,7 +27013,14 @@ function getTopNBySize(db, sessionId, n) {
26454
27013
  init_logger();
26455
27014
  var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
26456
27015
  function startDreamScheduleTimer(args) {
26457
- const { client, directory, dreamerConfig, embeddingConfig: embeddingConfig2, memoryEnabled } = args;
27016
+ const {
27017
+ client,
27018
+ directory,
27019
+ dreamerConfig,
27020
+ embeddingConfig: embeddingConfig2,
27021
+ memoryEnabled,
27022
+ experimentalUserMemories
27023
+ } = args;
26458
27024
  const dreamingEnabled = Boolean(dreamerConfig?.enabled && dreamerConfig.schedule?.trim());
26459
27025
  const embeddingSweepEnabled = memoryEnabled && embeddingConfig2.provider !== "off";
26460
27026
  if (!dreamingEnabled && !embeddingSweepEnabled) {
@@ -26482,7 +27048,8 @@ function startDreamScheduleTimer(args) {
26482
27048
  client,
26483
27049
  tasks: dreamerConfig.tasks,
26484
27050
  taskTimeoutMinutes: dreamerConfig.task_timeout_minutes,
26485
- maxRuntimeMinutes: dreamerConfig.max_runtime_minutes
27051
+ maxRuntimeMinutes: dreamerConfig.max_runtime_minutes,
27052
+ experimentalUserMemories
26486
27053
  }).catch((error48) => {
26487
27054
  log("[dreamer] timer-triggered queue processing failed:", error48);
26488
27055
  });
@@ -27203,6 +27770,8 @@ async function sendUserPrompt(client, sessionId, text) {
27203
27770
  }
27204
27771
 
27205
27772
  // src/hooks/magic-context/command-handler.ts
27773
+ var recompConfirmationBySession = new Map;
27774
+ var RECOMP_CONFIRMATION_WINDOW_MS = 60000;
27206
27775
  var SENTINEL_PREFIX = "__CONTEXT_MANAGEMENT_";
27207
27776
  async function executeAugmentation(deps, sessionId, userPrompt) {
27208
27777
  if (!deps.sidekick?.config) {
@@ -27278,7 +27847,8 @@ Dreaming is not configured for this project.`, {});
27278
27847
  client: deps.dreamer.client,
27279
27848
  tasks: deps.dreamer.config.tasks,
27280
27849
  taskTimeoutMinutes: deps.dreamer.config.task_timeout_minutes,
27281
- maxRuntimeMinutes: deps.dreamer.config.max_runtime_minutes
27850
+ maxRuntimeMinutes: deps.dreamer.config.max_runtime_minutes,
27851
+ experimentalUserMemories: deps.dreamer.experimentalUserMemories
27282
27852
  });
27283
27853
  await deps.sendNotification(sessionId, result ? summarizeDreamResult(result) : "Dream queued, but another worker is already processing the queue.", {});
27284
27854
  throw new Error(`${SENTINEL_PREFIX}CTX-DREAM_HANDLED__`);
@@ -27321,12 +27891,40 @@ function createMagicContextCommandHandler(deps) {
27321
27891
  ${statusOutput}` : statusOutput;
27322
27892
  }
27323
27893
  if (isRecomp) {
27324
- await deps.sendNotification(sessionId, `## Magic Recomp
27325
-
27326
- Historian recomp started. Rebuilding compartments and facts from raw session history now.`, {});
27327
- result = deps.executeRecomp ? await deps.executeRecomp(sessionId) : `## Magic Recomp
27894
+ if (!deps.executeRecomp) {
27895
+ result = `## Magic Recomp
27328
27896
 
27329
27897
  /ctx-recomp is unavailable because the recomp handler is not configured.`;
27898
+ } else {
27899
+ const lastConfirmation = recompConfirmationBySession.get(sessionId);
27900
+ const now = Date.now();
27901
+ if (lastConfirmation && now - lastConfirmation < RECOMP_CONFIRMATION_WINDOW_MS) {
27902
+ recompConfirmationBySession.delete(sessionId);
27903
+ await deps.sendNotification(sessionId, `## Magic Recomp
27904
+
27905
+ Historian recomp started. Rebuilding compartments and facts from raw session history now.`, {});
27906
+ result = await deps.executeRecomp(sessionId);
27907
+ } else {
27908
+ recompConfirmationBySession.set(sessionId, now);
27909
+ const compartments = getCompartments(deps.db, sessionId);
27910
+ const compartmentCount = compartments.length;
27911
+ const warningLines = [
27912
+ "## \u26A0\uFE0F Recomp Confirmation Required",
27913
+ "",
27914
+ `You currently have **${compartmentCount}** compartments.`,
27915
+ "Running /ctx-recomp will **regenerate all compartments and facts** from raw session history.",
27916
+ "",
27917
+ "This operation:",
27918
+ "- May take a long time (minutes to hours for long sessions)",
27919
+ "- Will consume significant tokens on your historian model",
27920
+ "- Cannot be interrupted cleanly once started",
27921
+ "",
27922
+ "**To confirm, run `/ctx-recomp` again within 60 seconds.**"
27923
+ ];
27924
+ result = warningLines.join(`
27925
+ `);
27926
+ }
27927
+ }
27330
27928
  }
27331
27929
  await deps.sendNotification(sessionId, result, {});
27332
27930
  sessionLog(sessionId, `command ${input.command} handled via command.execute.before`);
@@ -27338,6 +27936,175 @@ Historian recomp started. Rebuilding compartments and facts from raw session his
27338
27936
  // src/hooks/magic-context/event-handler.ts
27339
27937
  init_logger();
27340
27938
 
27939
+ // src/features/magic-context/compaction-marker.ts
27940
+ import { Database as Database3 } from "bun:sqlite";
27941
+ import { join as join10 } from "path";
27942
+ init_logger();
27943
+ var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
27944
+ function randomBase62(length) {
27945
+ const chars = [];
27946
+ for (let i = 0;i < length; i++) {
27947
+ chars.push(BASE62_CHARS[Math.floor(Math.random() * BASE62_CHARS.length)]);
27948
+ }
27949
+ return chars.join("");
27950
+ }
27951
+ function generateId(prefix, timestampMs, counter = 0n) {
27952
+ const encoded = BigInt(timestampMs) * 0x1000n + counter;
27953
+ const hex3 = encoded.toString(16).padStart(14, "0");
27954
+ return `${prefix}_${hex3}${randomBase62(14)}`;
27955
+ }
27956
+ function generateMessageId(timestampMs, counter = 0n) {
27957
+ return generateId("msg", timestampMs, counter);
27958
+ }
27959
+ function generatePartId(timestampMs, counter = 0n) {
27960
+ return generateId("prt", timestampMs, counter);
27961
+ }
27962
+ function getOpenCodeDbPath2() {
27963
+ return join10(getDataDir(), "opencode", "opencode.db");
27964
+ }
27965
+ var cachedWriteDb = null;
27966
+ function getWritableOpenCodeDb() {
27967
+ const dbPath = getOpenCodeDbPath2();
27968
+ if (cachedWriteDb?.path === dbPath) {
27969
+ return cachedWriteDb.db;
27970
+ }
27971
+ if (cachedWriteDb) {
27972
+ try {
27973
+ cachedWriteDb.db.close(false);
27974
+ } catch {}
27975
+ }
27976
+ const db = new Database3(dbPath);
27977
+ db.exec("PRAGMA journal_mode=WAL");
27978
+ db.exec("PRAGMA busy_timeout=5000");
27979
+ cachedWriteDb = { path: dbPath, db };
27980
+ return db;
27981
+ }
27982
+ function findBoundaryUserMessage(sessionId, endOrdinal) {
27983
+ const db = getWritableOpenCodeDb();
27984
+ const rows = db.prepare("SELECT id, time_created, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId);
27985
+ const filtered = rows.filter((row) => {
27986
+ try {
27987
+ const info = JSON.parse(row.data);
27988
+ return !(info.summary === true && info.finish === "stop");
27989
+ } catch {
27990
+ return true;
27991
+ }
27992
+ });
27993
+ let bestMatch = null;
27994
+ for (let i = 0;i < filtered.length && i < endOrdinal; i++) {
27995
+ const row = filtered[i];
27996
+ try {
27997
+ const info = JSON.parse(row.data);
27998
+ if (info.role === "user") {
27999
+ bestMatch = { id: row.id, timeCreated: row.time_created };
28000
+ }
28001
+ } catch {}
28002
+ }
28003
+ return bestMatch;
28004
+ }
28005
+ function injectCompactionMarker(args) {
28006
+ const boundary = findBoundaryUserMessage(args.sessionId, args.endOrdinal);
28007
+ if (!boundary) {
28008
+ log(`[magic-context] compaction-marker: no user message found at or before ordinal ${args.endOrdinal}`);
28009
+ return null;
28010
+ }
28011
+ const db = getWritableOpenCodeDb();
28012
+ const boundaryTime = boundary.timeCreated;
28013
+ const summaryMsgId = generateMessageId(boundaryTime + 1, 1n);
28014
+ const compactionPartId = generatePartId(boundaryTime, 1n);
28015
+ const summaryPartId = generatePartId(boundaryTime + 1, 2n);
28016
+ const summaryMsgData = JSON.stringify({
28017
+ role: "assistant",
28018
+ parentID: boundary.id,
28019
+ summary: true,
28020
+ finish: "stop",
28021
+ mode: "compaction",
28022
+ agent: "compaction",
28023
+ path: { cwd: args.directory, root: args.directory },
28024
+ cost: 0,
28025
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
28026
+ modelID: "magic-context",
28027
+ providerID: "magic-context",
28028
+ time: { created: boundaryTime + 1 }
28029
+ });
28030
+ try {
28031
+ db.transaction(() => {
28032
+ db.prepare("INSERT INTO part (id, message_id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?, ?)").run(compactionPartId, boundary.id, args.sessionId, boundaryTime, boundaryTime, '{"type":"compaction","auto":true}');
28033
+ db.prepare("INSERT INTO message (id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?)").run(summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, summaryMsgData);
28034
+ db.prepare("INSERT INTO part (id, message_id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?, ?)").run(summaryPartId, summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, JSON.stringify({ type: "text", text: args.summaryText }));
28035
+ })();
28036
+ log(`[magic-context] compaction-marker: injected boundary at user msg ${boundary.id} (ordinal ~${args.endOrdinal}), summary msg ${summaryMsgId}`);
28037
+ return {
28038
+ boundaryMessageId: boundary.id,
28039
+ summaryMessageId: summaryMsgId,
28040
+ compactionPartId,
28041
+ summaryPartId
28042
+ };
28043
+ } catch (error48) {
28044
+ log(`[magic-context] compaction-marker: injection failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
28045
+ return null;
28046
+ }
28047
+ }
28048
+ function removeCompactionMarker(state) {
28049
+ try {
28050
+ const db = getWritableOpenCodeDb();
28051
+ db.transaction(() => {
28052
+ db.prepare("DELETE FROM part WHERE id = ?").run(state.summaryPartId);
28053
+ db.prepare("DELETE FROM message WHERE id = ?").run(state.summaryMessageId);
28054
+ db.prepare("DELETE FROM part WHERE id = ?").run(state.compactionPartId);
28055
+ })();
28056
+ return true;
28057
+ } catch (error48) {
28058
+ log(`[magic-context] compaction-marker: removal failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
28059
+ return false;
28060
+ }
28061
+ }
28062
+
28063
+ // src/hooks/magic-context/compaction-marker-manager.ts
28064
+ init_logger();
28065
+ var MARKER_SUMMARY_TEXT = "[Compacted by magic-context \u2014 session history is managed by the plugin]";
28066
+ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, directory) {
28067
+ const existing = getPersistedCompactionMarkerState(db, sessionId);
28068
+ if (existing) {
28069
+ if (existing.boundaryOrdinal === lastCompartmentEnd) {
28070
+ return;
28071
+ }
28072
+ try {
28073
+ removeCompactionMarker(existing);
28074
+ setPersistedCompactionMarkerState(db, sessionId, null);
28075
+ sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
28076
+ } catch (error48) {
28077
+ sessionLog(sessionId, `compaction-marker: failed to remove old boundary at ordinal ${existing.boundaryOrdinal}, proceeding with new injection:`, error48);
28078
+ }
28079
+ }
28080
+ const result = injectCompactionMarker({
28081
+ sessionId,
28082
+ endOrdinal: lastCompartmentEnd,
28083
+ summaryText: MARKER_SUMMARY_TEXT,
28084
+ directory: directory ?? process.cwd()
28085
+ });
28086
+ if (result) {
28087
+ setPersistedCompactionMarkerState(db, sessionId, {
28088
+ ...result,
28089
+ boundaryOrdinal: lastCompartmentEnd
28090
+ });
28091
+ sessionLog(sessionId, `compaction-marker: injected at ordinal ${lastCompartmentEnd}, boundary user msg ${result.boundaryMessageId}`);
28092
+ }
28093
+ }
28094
+ function removeCompactionMarkerForSession(db, sessionId) {
28095
+ const existing = getPersistedCompactionMarkerState(db, sessionId);
28096
+ if (existing) {
28097
+ try {
28098
+ removeCompactionMarker(existing);
28099
+ setPersistedCompactionMarkerState(db, sessionId, null);
28100
+ sessionLog(sessionId, "compaction-marker: removed on session cleanup");
28101
+ } catch (error48) {
28102
+ setPersistedCompactionMarkerState(db, sessionId, null);
28103
+ sessionLog(sessionId, "compaction-marker: removal failed during session cleanup, cleared persisted state:", error48);
28104
+ }
28105
+ }
28106
+ }
28107
+
27341
28108
  // src/hooks/magic-context/event-payloads.ts
27342
28109
  function getSessionProperties(properties) {
27343
28110
  if (!isRecord(properties)) {
@@ -27634,6 +28401,11 @@ function createEventHandler2(deps) {
27634
28401
  clearNoteNudgeState(deps.db, info.sessionID, { persist: false });
27635
28402
  sessionLog(info.sessionID, "event message.removed: cleared in-memory note nudge state");
27636
28403
  }
28404
+ const markerState = getPersistedCompactionMarkerState(deps.db, info.sessionID);
28405
+ if (markerState && (markerState.boundaryMessageId === info.messageID || markerState.summaryMessageId === info.messageID)) {
28406
+ removeCompactionMarkerForSession(deps.db, info.sessionID);
28407
+ sessionLog(info.sessionID, `event message.removed: cleared compaction marker (boundary or summary message removed)`);
28408
+ }
27637
28409
  deps.onSessionCacheInvalidated?.(info.sessionID);
27638
28410
  sessionLog(info.sessionID, "event message.removed: cleared session injection cache");
27639
28411
  } catch (error48) {
@@ -27651,6 +28423,11 @@ function createEventHandler2(deps) {
27651
28423
  } catch (error48) {
27652
28424
  sessionLog(sessionId, "event session.compacted handling failed:", error48);
27653
28425
  }
28426
+ try {
28427
+ removeCompactionMarkerForSession(deps.db, sessionId);
28428
+ } catch (error48) {
28429
+ sessionLog(sessionId, "event session.compacted marker cleanup failed:", error48);
28430
+ }
27654
28431
  deps.onSessionCacheInvalidated?.(sessionId);
27655
28432
  return;
27656
28433
  }
@@ -27661,6 +28438,7 @@ function createEventHandler2(deps) {
27661
28438
  }
27662
28439
  deps.nudgePlacements.clear(sessionId);
27663
28440
  try {
28441
+ removeCompactionMarkerForSession(deps.db, sessionId);
27664
28442
  clearSession(deps.db, sessionId);
27665
28443
  } catch (error48) {
27666
28444
  sessionLog(sessionId, "event session.deleted persistence failed:", error48);
@@ -27740,7 +28518,10 @@ function trimMemoriesToBudget(sessionId, memories, budgetTokens) {
27740
28518
  return -1;
27741
28519
  if (b.status === "permanent" && a.status !== "permanent")
27742
28520
  return 1;
27743
- return b.seenCount - a.seenCount;
28521
+ const seenDiff = b.seenCount - a.seenCount;
28522
+ if (seenDiff !== 0)
28523
+ return seenDiff;
28524
+ return a.id - b.id;
27744
28525
  });
27745
28526
  const result = [];
27746
28527
  let usedTokens = 0;
@@ -28478,6 +29259,9 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
28478
29259
  const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
28479
29260
  return rows.map(toMemory);
28480
29261
  }
29262
+ // src/hooks/magic-context/compartment-runner-incremental.ts
29263
+ init_logger();
29264
+
28481
29265
  // src/hooks/magic-context/compartment-runner-compressor.ts
28482
29266
  init_logger();
28483
29267
 
@@ -28486,6 +29270,8 @@ var COMPARTMENT_REGEX = /<compartment\s+(?:id="[^"]*"\s+)?start="(\d+)"\s+end="(
28486
29270
  var CATEGORY_BLOCK_REGEX = /<(WORKFLOW_RULES|ARCHITECTURE_DECISIONS|CONSTRAINTS|CONFIG_DEFAULTS|KNOWN_ISSUES|ENVIRONMENT|NAMING|USER_PREFERENCES|USER_DIRECTIVES)>(.*?)<\/\1>/gs;
28487
29271
  var FACT_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
28488
29272
  var UNPROCESSED_REGEX = /<unprocessed_from>(\d+)<\/unprocessed_from>/;
29273
+ var USER_OBSERVATIONS_REGEX = /<user_observations>(.*?)<\/user_observations>/s;
29274
+ var USER_OBS_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
28489
29275
  function parseCompartmentOutput(text) {
28490
29276
  const compartments = [];
28491
29277
  const facts = [];
@@ -28510,8 +29296,17 @@ function parseCompartmentOutput(text) {
28510
29296
  }
28511
29297
  const unprocessedMatch = text.match(UNPROCESSED_REGEX);
28512
29298
  const unprocessedFrom = unprocessedMatch ? parseInt(unprocessedMatch[1], 10) : null;
29299
+ const userObservations = [];
29300
+ const userObsMatch = text.match(USER_OBSERVATIONS_REGEX);
29301
+ if (userObsMatch) {
29302
+ for (const itemMatch of userObsMatch[1].matchAll(USER_OBS_ITEM_REGEX)) {
29303
+ const obs = unescapeXml(itemMatch[1].trim());
29304
+ if (obs)
29305
+ userObservations.push(obs);
29306
+ }
29307
+ }
28513
29308
  compartments.sort((a, b) => a.startMessage - b.startMessage);
28514
- return { compartments, facts, unprocessedFrom };
29309
+ return { compartments, facts, unprocessedFrom, userObservations };
28515
29310
  }
28516
29311
  function unescapeXml(s) {
28517
29312
  return s.replace(/&amp;/g, "&").replace(/&apos;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">");
@@ -28670,6 +29465,7 @@ ${compartment.content}
28670
29465
  }
28671
29466
  }
28672
29467
  replaceAllCompartmentState(db, sessionId, allCompartments, facts.map((f) => ({ category: f.category, content: f.content })));
29468
+ clearInjectionCache(sessionId);
28673
29469
  incrementCompressionDepth(db, sessionId, originalStart, originalEnd);
28674
29470
  sessionLog(sessionId, `compressor: replaced ${selectedCompartments.length} compartments with ${compressed.length} compressed compartments`);
28675
29471
  sessionLog(sessionId, `compressor: incremented compression depth for messages ${originalStart}-${originalEnd}`);
@@ -28789,7 +29585,7 @@ function queueDropsForCompartmentalizedMessages(db, sessionId, upToMessageIndex)
28789
29585
  // src/hooks/magic-context/compartment-runner-historian.ts
28790
29586
  import { mkdirSync as mkdirSync2, unlinkSync, writeFileSync } from "fs";
28791
29587
  import { tmpdir as tmpdir2 } from "os";
28792
- import { join as join10 } from "path";
29588
+ import { join as join11 } from "path";
28793
29589
 
28794
29590
  // src/hooks/magic-context/compartment-runner-mapping.ts
28795
29591
  function mapParsedCompartmentsToChunk(compartments, chunk, sequenceOffset) {
@@ -28856,7 +29652,8 @@ function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, se
28856
29652
  return {
28857
29653
  ok: true,
28858
29654
  compartments: mapped.compartments,
28859
- facts: parsed.facts
29655
+ facts: parsed.facts,
29656
+ userObservations: parsed.userObservations.length > 0 ? parsed.userObservations : undefined
28860
29657
  };
28861
29658
  }
28862
29659
  function buildHistorianRepairPrompt(originalPrompt, previousOutput, validationError) {
@@ -28949,7 +29746,7 @@ function getReducedRecompTokenBudget(currentBudget) {
28949
29746
  }
28950
29747
 
28951
29748
  // src/hooks/magic-context/compartment-runner-historian.ts
28952
- var HISTORIAN_RESPONSE_DUMP_DIR = join10(tmpdir2(), "magic-context-historian");
29749
+ var HISTORIAN_RESPONSE_DUMP_DIR = join11(tmpdir2(), "magic-context-historian");
28953
29750
  async function runValidatedHistorianPass(args) {
28954
29751
  const firstRun = await runHistorianPrompt({
28955
29752
  ...args,
@@ -29051,7 +29848,7 @@ function dumpHistorianResponse(sessionId, label, text) {
29051
29848
  mkdirSync2(HISTORIAN_RESPONSE_DUMP_DIR, { recursive: true });
29052
29849
  const safeSessionId = sanitizeDumpName(sessionId);
29053
29850
  const safeLabel = sanitizeDumpName(label);
29054
- const dumpPath = join10(HISTORIAN_RESPONSE_DUMP_DIR, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
29851
+ const dumpPath = join11(HISTORIAN_RESPONSE_DUMP_DIR, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
29055
29852
  writeFileSync(dumpPath, text, "utf8");
29056
29853
  sessionLog(sessionId, "compartment agent: historian response dumped", {
29057
29854
  label,
@@ -29200,11 +29997,15 @@ No new compartments or facts were written. Check the historian model/output and
29200
29997
  appendCompartments(db, sessionId, newCompartments);
29201
29998
  replaceSessionFacts(db, sessionId, validatedPass.facts ?? []);
29202
29999
  })();
30000
+ clearInjectionCache(sessionId);
29203
30001
  if (deps.directory) {
29204
30002
  promoteSessionFactsToMemory(db, sessionId, resolveProjectIdentity(deps.directory), validatedPass.facts ?? []);
29205
30003
  }
29206
30004
  const lastCompartmentEnd = lastNewEnd;
29207
30005
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
30006
+ if (deps.experimentalCompactionMarkers) {
30007
+ updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, sessionDirectory);
30008
+ }
29208
30009
  if (deps.historyBudgetTokens && deps.historyBudgetTokens > 0) {
29209
30010
  await runCompressionPassIfNeeded({
29210
30011
  client,
@@ -29218,6 +30019,20 @@ No new compartments or facts were written. Check the historian model/output and
29218
30019
  updateSessionMeta(db, sessionId, { compartmentInProgress: false });
29219
30020
  completedSuccessfully = true;
29220
30021
  onNoteTrigger(db, sessionId, "historian_complete");
30022
+ if (validatedPass.userObservations && validatedPass.userObservations.length > 0) {
30023
+ try {
30024
+ const lastNew = newCompartments[newCompartments.length - 1];
30025
+ insertUserMemoryCandidates(db, validatedPass.userObservations.map((obs) => ({
30026
+ content: obs,
30027
+ sessionId,
30028
+ sourceCompartmentStart: newCompartments[0]?.startMessage,
30029
+ sourceCompartmentEnd: lastNew?.endMessage
30030
+ })));
30031
+ sessionLog(sessionId, `stored ${validatedPass.userObservations.length} user memory candidate(s)`);
30032
+ } catch (error48) {
30033
+ sessionLog(sessionId, "failed to store user memory candidates:", error48);
30034
+ }
30035
+ }
29221
30036
  } catch (error48) {
29222
30037
  const msg = getErrorMessage(error48);
29223
30038
  if (!issueNotified) {
@@ -29258,6 +30073,7 @@ async function executeContextRecompInternal(deps) {
29258
30073
  const promoted2 = promoteRecompStaging(db, sessionId);
29259
30074
  if (!promoted2)
29260
30075
  return null;
30076
+ clearInjectionCache(sessionId);
29261
30077
  if (deps.directory) {
29262
30078
  promoteSessionFactsToMemory(db, sessionId, resolveProjectIdentity(deps.directory), promoted2.facts);
29263
30079
  }
@@ -29265,6 +30081,9 @@ async function executeContextRecompInternal(deps) {
29265
30081
  if (lastCompartmentEnd2 > 0) {
29266
30082
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd2);
29267
30083
  }
30084
+ if (deps.experimentalCompactionMarkers && lastCompartmentEnd2 > 0) {
30085
+ updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd2, deps.directory);
30086
+ }
29268
30087
  return [
29269
30088
  `Persisted ${promoted2.compartments.length} compartment${promoted2.compartments.length === 1 ? "" : "s"} from ${passCount} successful pass${passCount === 1 ? "" : "es"}.`,
29270
30089
  `Covered raw history 1-${lastCompartmentEnd2} out of ${rawMessageCount} total messages.`,
@@ -29419,6 +30238,7 @@ Nothing was written.`;
29419
30238
  replaceAllCompartmentState(db, sessionId, candidateCompartments, candidateFacts);
29420
30239
  clearRecompStaging(db, sessionId);
29421
30240
  }
30241
+ clearInjectionCache(sessionId);
29422
30242
  const finalCompartments = promoted?.compartments ?? candidateCompartments;
29423
30243
  const finalFacts = promoted?.facts ?? candidateFacts;
29424
30244
  if (deps.directory) {
@@ -29548,7 +30368,9 @@ async function runCompartmentPhase(args) {
29548
30368
  historyBudgetTokens: args.historyBudgetTokens,
29549
30369
  historianTimeoutMs: args.historianTimeoutMs,
29550
30370
  directory: args.compartmentDirectory,
29551
- getNotificationParams: args.getNotificationParams
30371
+ getNotificationParams: args.getNotificationParams,
30372
+ experimentalCompactionMarkers: args.experimentalCompactionMarkers,
30373
+ experimentalUserMemories: args.experimentalUserMemories
29552
30374
  });
29553
30375
  compartmentInProgress = true;
29554
30376
  }
@@ -29574,7 +30396,9 @@ async function runCompartmentPhase(args) {
29574
30396
  historyBudgetTokens: args.historyBudgetTokens,
29575
30397
  historianTimeoutMs: args.historianTimeoutMs,
29576
30398
  directory: args.compartmentDirectory,
29577
- getNotificationParams: args.getNotificationParams
30399
+ getNotificationParams: args.getNotificationParams,
30400
+ experimentalCompactionMarkers: args.experimentalCompactionMarkers,
30401
+ experimentalUserMemories: args.experimentalUserMemories
29578
30402
  });
29579
30403
  activeRun = getActiveCompartmentRun(args.sessionId);
29580
30404
  } else if (!activeRun && hasEligibleHistoryForCompartment()) {
@@ -29588,7 +30412,7 @@ async function runCompartmentPhase(args) {
29588
30412
  }
29589
30413
  if (args.cacheAlreadyBusting && args.historyBudgetTokens && args.historyBudgetTokens > 0 && args.client && !compartmentInProgress && !awaitedCompartmentRun) {
29590
30414
  try {
29591
- await runCompressionPassIfNeeded({
30415
+ const compressed = await runCompressionPassIfNeeded({
29592
30416
  client: args.client,
29593
30417
  db: args.db,
29594
30418
  sessionId: args.sessionId,
@@ -29596,6 +30420,9 @@ async function runCompartmentPhase(args) {
29596
30420
  historyBudgetTokens: args.historyBudgetTokens,
29597
30421
  historianTimeoutMs: args.historianTimeoutMs
29598
30422
  });
30423
+ if (compressed && args.projectPath !== undefined) {
30424
+ pendingCompartmentInjection = prepareCompartmentInjection(args.db, args.sessionId, args.messages, true, args.projectPath, args.injectionBudgetTokens);
30425
+ }
29599
30426
  } catch (error48) {
29600
30427
  sessionLog(args.sessionId, "transform: independent compressor check failed:", getErrorMessage(error48));
29601
30428
  }
@@ -30743,7 +31570,18 @@ function runPostTransformPhase(args) {
30743
31570
  if (pendingUserTurnReminder.messageId) {
30744
31571
  const reinjected = appendReminderToUserMessageById(args.messages, pendingUserTurnReminder.messageId, pendingUserTurnReminder.text);
30745
31572
  if (!reinjected) {
30746
- sessionLog(args.sessionId, `preserving sticky turn reminder anchor to avoid cache bust: messageId=${pendingUserTurnReminder.messageId}`);
31573
+ if (isCacheBustingPass) {
31574
+ const newAnchorId = appendReminderToLatestUserMessage(args.messages, pendingUserTurnReminder.text);
31575
+ if (newAnchorId) {
31576
+ setPersistedStickyTurnReminder(args.db, args.sessionId, pendingUserTurnReminder.text, newAnchorId);
31577
+ sessionLog(args.sessionId, `sticky turn reminder re-anchored: ${pendingUserTurnReminder.messageId} \u2192 ${newAnchorId}`);
31578
+ } else {
31579
+ clearPersistedStickyTurnReminder(args.db, args.sessionId);
31580
+ sessionLog(args.sessionId, `sticky turn reminder cleared \u2014 anchor ${pendingUserTurnReminder.messageId} gone and no user message visible`);
31581
+ }
31582
+ } else {
31583
+ sessionLog(args.sessionId, `preserving sticky turn reminder anchor to avoid cache bust: messageId=${pendingUserTurnReminder.messageId}`);
31584
+ }
30747
31585
  }
30748
31586
  } else {
30749
31587
  const anchoredMessageId = appendReminderToLatestUserMessage(args.messages, pendingUserTurnReminder.text);
@@ -30780,7 +31618,17 @@ function runPostTransformPhase(args) {
30780
31618
  if (stickyNoteNudge) {
30781
31619
  const reinjected = appendReminderToUserMessageById(args.messages, stickyNoteNudge.messageId, stickyNoteNudge.text);
30782
31620
  if (!reinjected) {
30783
- sessionLog(args.sessionId, `preserving sticky note nudge anchor to avoid cache bust: messageId=${stickyNoteNudge.messageId}`);
31621
+ if (isCacheBustingPass) {
31622
+ const newAnchorId = appendReminderToLatestUserMessage(args.messages, stickyNoteNudge.text);
31623
+ if (newAnchorId) {
31624
+ markNoteNudgeDelivered(args.db, args.sessionId, stickyNoteNudge.text, newAnchorId);
31625
+ sessionLog(args.sessionId, `sticky note nudge re-anchored: ${stickyNoteNudge.messageId} \u2192 ${newAnchorId}`);
31626
+ } else {
31627
+ sessionLog(args.sessionId, `sticky note nudge anchor ${stickyNoteNudge.messageId} gone \u2014 no user message visible to re-anchor`);
31628
+ }
31629
+ } else {
31630
+ sessionLog(args.sessionId, `preserving sticky note nudge anchor to avoid cache bust: messageId=${stickyNoteNudge.messageId}`);
31631
+ }
30784
31632
  }
30785
31633
  }
30786
31634
  const deferredNoteText = peekNoteNudgeText(args.db, args.sessionId, args.currentTurnId, args.projectPath);
@@ -30855,7 +31703,7 @@ function createTransform(deps) {
30855
31703
  const canRunCompartments = fullFeatureMode && deps.client !== undefined && compartmentDirectory.length > 0;
30856
31704
  const contextUsageEarly = loadContextUsage(deps.contextUsageMap, db, sessionId);
30857
31705
  const schedulerDecisionEarly = resolveSchedulerDecision(deps.scheduler, sessionMeta, contextUsageEarly, sessionId, deps.getModelKey?.(sessionId));
30858
- const isCacheBusting = deps.flushedSessions.has(sessionId) || schedulerDecisionEarly === "execute";
31706
+ const isCacheBusting = deps.flushedSessions.has(sessionId);
30859
31707
  let pendingCompartmentInjection = null;
30860
31708
  if (fullFeatureMode) {
30861
31709
  const projectPath = deps.memoryConfig?.enabled ? resolveProjectIdentity(deps.directory ?? process.cwd()) : undefined;
@@ -30946,7 +31794,9 @@ function createTransform(deps) {
30946
31794
  projectPath: deps.memoryConfig?.enabled ? resolveProjectIdentity(deps.directory ?? process.cwd()) : undefined,
30947
31795
  injectionBudgetTokens: deps.memoryConfig?.injectionBudgetTokens,
30948
31796
  getNotificationParams: rawGetNotifParams ? () => rawGetNotifParams(sessionId) : undefined,
30949
- cacheAlreadyBusting: isCacheBusting
31797
+ cacheAlreadyBusting: isCacheBusting || schedulerDecisionEarly === "execute",
31798
+ experimentalCompactionMarkers: deps.experimentalCompactionMarkers,
31799
+ experimentalUserMemories: deps.experimentalUserMemories
30950
31800
  });
30951
31801
  pendingCompartmentInjection = compartmentPhase.pendingCompartmentInjection;
30952
31802
  const awaitedCompartmentRun = compartmentPhase.awaitedCompartmentRun;
@@ -31136,7 +31986,7 @@ function createToolExecuteAfterHook(args) {
31136
31986
 
31137
31987
  // src/hooks/magic-context/system-prompt-hash.ts
31138
31988
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
31139
- import { join as join11 } from "path";
31989
+ import { join as join12 } from "path";
31140
31990
 
31141
31991
  // src/agents/magic-context-prompt.ts
31142
31992
  var BASE_INTRO = (protectedTags) => `Messages and tool outputs are tagged with \xA7N\xA7 identifiers (e.g., \xA71\xA7, \xA742\xA7).
@@ -31337,11 +32187,13 @@ Prefer many small targeted operations over one large blanket operation. Compress
31337
32187
  init_logger();
31338
32188
  var MAGIC_CONTEXT_MARKER = "## Magic Context";
31339
32189
  var PROJECT_DOCS_MARKER = "<project-docs>";
32190
+ var USER_PROFILE_MARKER = "<user-profile>";
32191
+ var cachedUserProfileBySession = new Map;
31340
32192
  var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
31341
32193
  function readProjectDocs(directory) {
31342
32194
  const sections = [];
31343
32195
  for (const filename of DOC_FILES) {
31344
- const filePath = join11(directory, filename);
32196
+ const filePath = join12(directory, filename);
31345
32197
  try {
31346
32198
  if (existsSync5(filePath)) {
31347
32199
  const content = readFileSync4(filePath, "utf-8").trim();
@@ -31396,6 +32248,28 @@ function createSystemPromptHashHandler(deps) {
31396
32248
  output.system.push(docsBlock);
31397
32249
  }
31398
32250
  }
32251
+ if (deps.experimentalUserMemories) {
32252
+ const hasCachedProfile = cachedUserProfileBySession.has(sessionId);
32253
+ if (!hasCachedProfile || isCacheBusting) {
32254
+ const memories = getActiveUserMemories(deps.db);
32255
+ if (memories.length > 0) {
32256
+ const items = memories.map((m) => `- ${m.content}`).join(`
32257
+ `);
32258
+ cachedUserProfileBySession.set(sessionId, `${USER_PROFILE_MARKER}
32259
+ ${items}
32260
+ </user-profile>`);
32261
+ if (!hasCachedProfile) {
32262
+ sessionLog(sessionId, `loaded ${memories.length} user profile memorie(s)`);
32263
+ }
32264
+ } else {
32265
+ cachedUserProfileBySession.set(sessionId, null);
32266
+ }
32267
+ }
32268
+ const profileBlock = cachedUserProfileBySession.get(sessionId);
32269
+ if (profileBlock && !fullPrompt.includes(USER_PROFILE_MARKER)) {
32270
+ output.system.push(profileBlock);
32271
+ }
32272
+ }
31399
32273
  const DATE_PATTERN = /Today's date: .+/;
31400
32274
  for (let i = 0;i < output.system.length; i++) {
31401
32275
  const match = output.system[i].match(DATE_PATTERN);
@@ -31529,7 +32403,9 @@ function createMagicContextHook(deps) {
31529
32403
  const model = liveModelBySession.get(sessionId);
31530
32404
  return resolveModelKey(model?.providerID, model?.modelID);
31531
32405
  },
31532
- projectPath
32406
+ projectPath,
32407
+ experimentalCompactionMarkers: deps.config.experimental?.compaction_markers,
32408
+ experimentalUserMemories: deps.config.experimental?.user_memories?.enabled
31533
32409
  });
31534
32410
  const eventHandler = createEventHandler2({
31535
32411
  contextUsageMap,
@@ -31564,7 +32440,11 @@ function createMagicContextHook(deps) {
31564
32440
  client: deps.client,
31565
32441
  tasks: dreaming.tasks,
31566
32442
  taskTimeoutMinutes: dreaming.task_timeout_minutes,
31567
- maxRuntimeMinutes: dreaming.max_runtime_minutes
32443
+ maxRuntimeMinutes: dreaming.max_runtime_minutes,
32444
+ experimentalUserMemories: deps.config.experimental?.user_memories?.enabled ? {
32445
+ enabled: true,
32446
+ promotionThreshold: deps.config.experimental.user_memories?.promotion_threshold
32447
+ } : undefined
31568
32448
  }).catch((error48) => {
31569
32449
  log("[dreamer] scheduled queue processing failed:", error48);
31570
32450
  });
@@ -31606,7 +32486,11 @@ function createMagicContextHook(deps) {
31606
32486
  config: deps.config.dreamer,
31607
32487
  projectPath,
31608
32488
  client: deps.client,
31609
- directory: deps.directory
32489
+ directory: deps.directory,
32490
+ experimentalUserMemories: deps.config.experimental?.user_memories?.enabled ? {
32491
+ enabled: true,
32492
+ promotionThreshold: deps.config.experimental.user_memories?.promotion_threshold
32493
+ } : undefined
31610
32494
  } : undefined
31611
32495
  });
31612
32496
  const emergencyNudgeFired = new Set;
@@ -31618,7 +32502,8 @@ function createMagicContextHook(deps) {
31618
32502
  injectDocs: deps.config.dreamer?.inject_docs !== false,
31619
32503
  directory: deps.directory,
31620
32504
  flushedSessions,
31621
- lastHeuristicsTurnId
32505
+ lastHeuristicsTurnId,
32506
+ experimentalUserMemories: deps.config.experimental?.user_memories?.enabled
31622
32507
  });
31623
32508
  const eventHook = createEventHook({
31624
32509
  eventHandler,
@@ -31695,7 +32580,8 @@ function createSessionHooks(args) {
31695
32580
  historian_timeout_ms: pluginConfig.historian_timeout_ms,
31696
32581
  memory: pluginConfig.memory,
31697
32582
  sidekick: pluginConfig.sidekick,
31698
- dreamer: pluginConfig.dreamer
32583
+ dreamer: pluginConfig.dreamer,
32584
+ experimental: pluginConfig.experimental
31699
32585
  }
31700
32586
  })
31701
32587
  };
@@ -32078,9 +32964,9 @@ Use this for short goals, constraints, decisions, or reminders worth carrying fo
32078
32964
 
32079
32965
  Actions:
32080
32966
  - \`write\`: Append one note. Optionally provide \`surface_condition\` to create a smart note.
32081
- - \`read\`: Show current notes (session notes + ready smart notes).
32082
- - \`clear\`: Remove all session notes.
32083
- - \`dismiss\`: Dismiss a ready smart note by \`note_id\`.
32967
+ - \`read\`: Show current notes. Defaults to active session notes + ready smart notes; use \`filter\` to inspect all, pending, ready, active, or dismissed notes.
32968
+ - \`dismiss\`: Dismiss a note by \`note_id\`.
32969
+ - \`update\`: Update a note by \`note_id\`.
32084
32970
 
32085
32971
  **Smart Notes**: When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
32086
32972
  The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
@@ -32089,14 +32975,79 @@ Example: \`ctx_note(action="write", content="Implement X because Y", surface_con
32089
32975
  Historian reads these notes, deduplicates them, and rewrites the remaining useful notes over time.`;
32090
32976
  // src/tools/ctx-note/tools.ts
32091
32977
  import { tool as tool3 } from "@opencode-ai/plugin";
32978
+ function formatNoteLine(note) {
32979
+ const statusSuffix = note.status === "active" ? "" : ` (${note.status})`;
32980
+ const dismissHint = note.status === "dismissed" ? "" : ` _(dismiss with \`ctx_note(action="dismiss", note_id=${note.id})\`)_`;
32981
+ if (note.type === "session") {
32982
+ return `- **#${note.id}**${statusSuffix}: ${note.content}${dismissHint}`;
32983
+ }
32984
+ const conditionText = note.status === "ready" ? note.readyReason ?? note.surfaceCondition ?? "Condition satisfied" : note.surfaceCondition ?? "No condition recorded";
32985
+ const conditionLabel = note.status === "ready" ? "Condition met" : "Condition";
32986
+ return `- **#${note.id}**${statusSuffix}: ${note.content}
32987
+ ${conditionLabel}: ${conditionText}${dismissHint}`;
32988
+ }
32989
+ function buildReadSections(args) {
32990
+ if (args.filter === undefined) {
32991
+ const sessionNotes2 = getSessionNotes(args.db, args.sessionId);
32992
+ const readySmartNotes = args.projectIdentity ? getReadySmartNotes(args.db, args.projectIdentity) : [];
32993
+ const sections2 = [];
32994
+ if (sessionNotes2.length > 0) {
32995
+ sections2.push(`## Session Notes
32996
+
32997
+ ${sessionNotes2.map((note) => formatNoteLine(note)).join(`
32998
+ `)}`);
32999
+ }
33000
+ if (readySmartNotes.length > 0) {
33001
+ sections2.push(`## \uD83D\uDD14 Ready Smart Notes
33002
+
33003
+ ${readySmartNotes.map((note) => formatNoteLine(note)).join(`
33004
+
33005
+ `)}`);
33006
+ }
33007
+ return sections2;
33008
+ }
33009
+ const statusByFilter = {
33010
+ active: "active",
33011
+ all: ["active", "pending", "ready", "dismissed"],
33012
+ dismissed: "dismissed",
33013
+ pending: "pending",
33014
+ ready: "ready"
33015
+ };
33016
+ const sessionNotes = getNotes(args.db, {
33017
+ sessionId: args.sessionId,
33018
+ type: "session",
33019
+ status: statusByFilter[args.filter]
33020
+ });
33021
+ const smartNotes = args.projectIdentity ? getNotes(args.db, {
33022
+ projectPath: args.projectIdentity,
33023
+ type: "smart",
33024
+ status: statusByFilter[args.filter]
33025
+ }) : [];
33026
+ const sections = [];
33027
+ if (sessionNotes.length > 0) {
33028
+ sections.push(`## Session Notes
33029
+
33030
+ ${sessionNotes.map((note) => formatNoteLine(note)).join(`
33031
+ `)}`);
33032
+ }
33033
+ if (smartNotes.length > 0) {
33034
+ sections.push(`## Smart Notes
33035
+
33036
+ ${smartNotes.map((note) => formatNoteLine(note)).join(`
33037
+
33038
+ `)}`);
33039
+ }
33040
+ return sections;
33041
+ }
32092
33042
  function createCtxNoteTool(deps) {
32093
33043
  return tool3({
32094
33044
  description: CTX_NOTE_DESCRIPTION,
32095
33045
  args: {
32096
- action: tool3.schema.enum(["write", "read", "clear", "dismiss"]).optional().describe("Operation to perform. Defaults to 'write' when content is provided, otherwise 'read'."),
33046
+ action: tool3.schema.enum(["write", "read", "dismiss", "update"]).optional().describe("Operation to perform. Defaults to 'write' when content is provided, otherwise 'read'."),
32097
33047
  content: tool3.schema.string().optional().describe("Note text to store when action is 'write'."),
32098
33048
  surface_condition: tool3.schema.string().optional().describe("Open-ended condition for smart notes. When provided, creates a project-scoped smart note that the dreamer evaluates nightly. The note surfaces when the condition is met."),
32099
- note_id: tool3.schema.number().optional().describe("Smart note ID to dismiss (required for 'dismiss' action).")
33049
+ filter: tool3.schema.enum(["all", "active", "pending", "ready", "dismissed"]).optional().describe("Optional read filter. Defaults to active session notes + ready smart notes. Use 'all' to inspect every status or 'pending' to inspect unsurfaced smart notes."),
33050
+ note_id: tool3.schema.number().optional().describe("Note ID (required for 'dismiss' and 'update' actions).")
32100
33051
  },
32101
33052
  async execute(args, toolContext) {
32102
33053
  const sessionId = toolContext.sessionID;
@@ -32113,48 +33064,59 @@ function createCtxNoteTool(deps) {
32113
33064
  if (!deps.projectIdentity) {
32114
33065
  return "Error: Could not resolve project identity for smart note.";
32115
33066
  }
32116
- const note = addSmartNote(deps.db, deps.projectIdentity, content, args.surface_condition.trim(), sessionId);
32117
- return `Created smart note #${note.id}. Dreamer will evaluate the condition during nightly runs:
33067
+ const note2 = addNote(deps.db, "smart", {
33068
+ content,
33069
+ projectPath: deps.projectIdentity,
33070
+ sessionId,
33071
+ surfaceCondition: args.surface_condition.trim()
33072
+ });
33073
+ return `Created smart note #${note2.id}. Dreamer will evaluate the condition during nightly runs:
32118
33074
  - Content: ${content}
32119
33075
  - Condition: ${args.surface_condition.trim()}`;
32120
33076
  }
32121
- addSessionNote(deps.db, sessionId, content);
32122
- const total = getSessionNotes(deps.db, sessionId).length;
32123
- return `Saved session note ${total}. Historian will rewrite or deduplicate notes as needed.`;
33077
+ const note = addNote(deps.db, "session", { sessionId, content });
33078
+ return `Saved session note #${note.id}. Historian will rewrite or deduplicate notes as needed.`;
32124
33079
  }
32125
33080
  if (action === "dismiss") {
32126
33081
  const noteId = args.note_id;
32127
33082
  if (typeof noteId !== "number") {
32128
33083
  return "Error: 'note_id' is required when action is 'dismiss'.";
32129
33084
  }
32130
- const dismissed = dismissSmartNote(deps.db, noteId);
32131
- return dismissed ? `Smart note #${noteId} dismissed.` : `Smart note #${noteId} not found or already dismissed.`;
32132
- }
32133
- if (action === "clear") {
32134
- const existing = getSessionNotes(deps.db, sessionId);
32135
- clearSessionNotes(deps.db, sessionId);
32136
- return existing.length === 0 ? "Session notes were already empty." : `Cleared ${existing.length} session note${existing.length === 1 ? "" : "s"}.`;
32137
- }
32138
- const notes = getSessionNotes(deps.db, sessionId);
32139
- const readySmartNotes = deps.projectIdentity ? getReadySmartNotes(deps.db, deps.projectIdentity) : [];
32140
- const sections = [];
32141
- if (notes.length > 0) {
32142
- const lines = notes.map((note, index) => `${index + 1}. ${note.content}`);
32143
- sections.push(`## Session Notes
32144
-
32145
- ${lines.join(`
32146
- `)}`);
32147
- }
32148
- if (readySmartNotes.length > 0) {
32149
- const lines = readySmartNotes.map((n) => `- **#${n.id}**: ${n.content}
32150
- Condition met: ${n.readyReason ?? n.surfaceCondition}
32151
- _(dismiss with \`ctx_note(action="dismiss", note_id=${n.id})\`)_`);
32152
- sections.push(`## \uD83D\uDD14 Ready Smart Notes
32153
-
32154
- ${lines.join(`
32155
-
32156
- `)}`);
33085
+ const dismissed = dismissNote(deps.db, noteId);
33086
+ return dismissed ? `Note #${noteId} dismissed.` : `Note #${noteId} not found or already dismissed.`;
32157
33087
  }
33088
+ if (action === "update") {
33089
+ const noteId = args.note_id;
33090
+ if (typeof noteId !== "number") {
33091
+ return "Error: 'note_id' is required when action is 'update'.";
33092
+ }
33093
+ const updates = {};
33094
+ if (args.content?.trim())
33095
+ updates.content = args.content.trim();
33096
+ if (args.surface_condition?.trim())
33097
+ updates.surfaceCondition = args.surface_condition.trim();
33098
+ if (!updates.content && !updates.surfaceCondition) {
33099
+ return "Error: Provide 'content' and/or 'surface_condition' to update.";
33100
+ }
33101
+ const updated = updateNote(deps.db, noteId, updates);
33102
+ if (!updated) {
33103
+ return `Note #${noteId} not found or has no compatible fields to update.`;
33104
+ }
33105
+ const parts = [];
33106
+ if (updates.content)
33107
+ parts.push(`Content: ${updates.content}`);
33108
+ if (updates.surfaceCondition)
33109
+ parts.push(`Condition: ${updates.surfaceCondition}`);
33110
+ return `Updated note #${noteId}:
33111
+ ${parts.join(`
33112
+ `)}`;
33113
+ }
33114
+ const sections = buildReadSections({
33115
+ db: deps.db,
33116
+ filter: args.filter,
33117
+ projectIdentity: deps.projectIdentity,
33118
+ sessionId
33119
+ });
32158
33120
  if (sections.length === 0) {
32159
33121
  return `## Notes
32160
33122
 
@@ -32788,6 +33750,115 @@ function createToolRegistry(args) {
32788
33750
  return allTools;
32789
33751
  }
32790
33752
 
33753
+ // src/features/magic-context/plugin-messages.ts
33754
+ function isPluginMessageRow(row) {
33755
+ if (row === null || typeof row !== "object")
33756
+ return false;
33757
+ const r = row;
33758
+ return typeof r.id === "number" && typeof r.direction === "string" && typeof r.type === "string" && typeof r.payload === "string" && typeof r.created_at === "number";
33759
+ }
33760
+ function toPluginMessage(row) {
33761
+ let payload = {};
33762
+ try {
33763
+ payload = JSON.parse(row.payload);
33764
+ } catch {}
33765
+ return {
33766
+ id: row.id,
33767
+ direction: row.direction,
33768
+ type: row.type,
33769
+ payload,
33770
+ sessionId: row.session_id,
33771
+ createdAt: row.created_at,
33772
+ consumedAt: row.consumed_at
33773
+ };
33774
+ }
33775
+ var CLEANUP_THRESHOLD_MS = 5 * 60 * 1000;
33776
+ function sendToTui(db, type, payload, sessionId) {
33777
+ const result = db.prepare("INSERT INTO plugin_messages (direction, type, payload, session_id, created_at) VALUES (?, ?, ?, ?, ?)").run("server_to_tui", type, JSON.stringify(payload), sessionId ?? null, Date.now());
33778
+ return Number(result.lastInsertRowid);
33779
+ }
33780
+ function consumeMessages(db, direction, options) {
33781
+ const now = Date.now();
33782
+ const conditions = ["direction = ?", "consumed_at IS NULL"];
33783
+ const params = [direction];
33784
+ if (options?.type) {
33785
+ conditions.push("type = ?");
33786
+ params.push(options.type);
33787
+ }
33788
+ if (options?.sessionId) {
33789
+ conditions.push("session_id = ?");
33790
+ params.push(options.sessionId);
33791
+ }
33792
+ const query = `SELECT * FROM plugin_messages WHERE ${conditions.join(" AND ")} ORDER BY created_at ASC`;
33793
+ const rows = db.prepare(query).all(...params);
33794
+ const messages = rows.filter(isPluginMessageRow).map(toPluginMessage);
33795
+ if (messages.length > 0) {
33796
+ const ids = messages.map((m) => m.id);
33797
+ db.prepare(`UPDATE plugin_messages SET consumed_at = ? WHERE id IN (${ids.map(() => "?").join(",")})`).run(now, ...ids);
33798
+ }
33799
+ db.prepare("DELETE FROM plugin_messages WHERE created_at < ?").run(now - CLEANUP_THRESHOLD_MS);
33800
+ return messages;
33801
+ }
33802
+ function sendTuiToast(db, message, options) {
33803
+ return sendToTui(db, "toast", {
33804
+ message,
33805
+ variant: options?.variant ?? "info",
33806
+ duration: options?.duration ?? 5000
33807
+ }, options?.sessionId);
33808
+ }
33809
+
33810
+ // src/plugin/tui-action-consumer.ts
33811
+ init_logger();
33812
+ var DEFAULT_COMPARTMENT_TOKEN_BUDGET2 = 20000;
33813
+ var DEFAULT_HISTORIAN_TIMEOUT_MS2 = 10 * 60 * 1000;
33814
+ var TUI_ACTION_POLL_INTERVAL_MS = 2000;
33815
+ function startTuiActionConsumer(args) {
33816
+ const { client, directory, config: config2 } = args;
33817
+ const timer = setInterval(() => {
33818
+ try {
33819
+ const db = openDatabase();
33820
+ const actions = consumeMessages(db, "tui_to_server", { type: "action" });
33821
+ for (const msg of actions) {
33822
+ const command = msg.payload.command;
33823
+ const sessionId = msg.sessionId;
33824
+ if (command === "recomp" && sessionId) {
33825
+ log(`[magic-context] TUI action: recomp requested for session ${sessionId}`);
33826
+ sendTuiToast(db, "Historian recomp started", {
33827
+ variant: "info",
33828
+ sessionId
33829
+ });
33830
+ executeContextRecomp({
33831
+ client,
33832
+ db,
33833
+ sessionId,
33834
+ tokenBudget: config2.compartment_token_budget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET2,
33835
+ historianTimeoutMs: config2.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS2,
33836
+ directory,
33837
+ getNotificationParams: () => ({})
33838
+ }).then((result) => {
33839
+ sendTuiToast(db, "Recomp completed", { variant: "success", sessionId });
33840
+ sendIgnoredMessage(client, sessionId, result, {}).catch(() => {});
33841
+ }).catch((error48) => {
33842
+ log("[magic-context] TUI recomp failed:", error48);
33843
+ sendTuiToast(db, `Recomp failed: ${error48 instanceof Error ? error48.message : "unknown error"}`, { variant: "error", sessionId });
33844
+ });
33845
+ } else {
33846
+ log(`[magic-context] TUI action: unknown command=${String(command)} session=${String(sessionId)}`);
33847
+ }
33848
+ }
33849
+ } catch (error48) {
33850
+ log("[magic-context] TUI action consumer error:", error48);
33851
+ }
33852
+ }, TUI_ACTION_POLL_INTERVAL_MS);
33853
+ if (typeof timer === "object" && "unref" in timer) {
33854
+ timer.unref();
33855
+ }
33856
+ log("[magic-context] started TUI action consumer (2s poll)");
33857
+ return () => {
33858
+ clearInterval(timer);
33859
+ };
33860
+ }
33861
+
32791
33862
  // src/index.ts
32792
33863
  init_conflict_detector();
32793
33864
  init_logger();
@@ -32817,7 +33888,16 @@ var plugin = async (ctx) => {
32817
33888
  client: ctx.client,
32818
33889
  dreamerConfig: pluginConfig.dreamer,
32819
33890
  embeddingConfig: pluginConfig.embedding,
32820
- memoryEnabled: pluginConfig.memory?.enabled === true
33891
+ memoryEnabled: pluginConfig.memory?.enabled === true,
33892
+ experimentalUserMemories: pluginConfig.experimental?.user_memories?.enabled ? {
33893
+ enabled: true,
33894
+ promotionThreshold: pluginConfig.experimental.user_memories?.promotion_threshold
33895
+ } : undefined
33896
+ });
33897
+ startTuiActionConsumer({
33898
+ client: ctx.client,
33899
+ directory: ctx.directory,
33900
+ config: pluginConfig
32821
33901
  });
32822
33902
  }
32823
33903
  if (conflictResult?.hasConflict) {
@@ -32897,7 +33977,7 @@ var plugin = async (ctx) => {
32897
33977
  config2.agent = {
32898
33978
  ...config2.agent ?? {},
32899
33979
  [DREAMER_AGENT]: buildHiddenAgentConfig(DREAMER_AGENT, DREAMER_SYSTEM_PROMPT, dreamerAgentOverrides),
32900
- [HISTORIAN_AGENT]: buildHiddenAgentConfig(HISTORIAN_AGENT, COMPARTMENT_AGENT_SYSTEM_PROMPT, pluginConfig.historian),
33980
+ [HISTORIAN_AGENT]: buildHiddenAgentConfig(HISTORIAN_AGENT, pluginConfig.experimental?.user_memories?.enabled ? COMPARTMENT_AGENT_SYSTEM_PROMPT + USER_OBSERVATIONS_APPENDIX : COMPARTMENT_AGENT_SYSTEM_PROMPT, pluginConfig.historian),
32901
33981
  [SIDEKICK_AGENT]: buildHiddenAgentConfig(SIDEKICK_AGENT, SIDEKICK_SYSTEM_PROMPT, sidekickAgentOverrides)
32902
33982
  };
32903
33983
  }