@cortexkit/opencode-magic-context 0.2.0 → 0.2.2

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 (56) hide show
  1. package/README.md +41 -6
  2. package/dist/cli/config-paths.d.ts +9 -0
  3. package/dist/cli/config-paths.d.ts.map +1 -0
  4. package/dist/cli/index.d.ts +3 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/opencode-helpers.d.ts +19 -0
  7. package/dist/cli/opencode-helpers.d.ts.map +1 -0
  8. package/dist/cli/prompts.d.ts +12 -0
  9. package/dist/cli/prompts.d.ts.map +1 -0
  10. package/dist/cli/setup.d.ts +2 -0
  11. package/dist/cli/setup.d.ts.map +1 -0
  12. package/dist/cli.js +442 -0
  13. package/dist/config/schema/magic-context.d.ts +0 -14
  14. package/dist/config/schema/magic-context.d.ts.map +1 -1
  15. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  16. package/dist/features/magic-context/dreamer/queue.d.ts +0 -2
  17. package/dist/features/magic-context/dreamer/queue.d.ts.map +1 -1
  18. package/dist/features/magic-context/memory/embedding-cache.d.ts +8 -0
  19. package/dist/features/magic-context/memory/embedding-cache.d.ts.map +1 -0
  20. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  21. package/dist/features/magic-context/memory/index.d.ts +1 -0
  22. package/dist/features/magic-context/memory/index.d.ts.map +1 -1
  23. package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
  24. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  25. package/dist/features/magic-context/message-index.d.ts +1 -0
  26. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  27. package/dist/features/magic-context/scheduler.d.ts +1 -1
  28. package/dist/features/magic-context/scheduler.d.ts.map +1 -1
  29. package/dist/features/magic-context/search.d.ts.map +1 -1
  30. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  31. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  32. package/dist/features/magic-context/storage-ops.d.ts +0 -1
  33. package/dist/features/magic-context/storage-ops.d.ts.map +1 -1
  34. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  35. package/dist/features/magic-context/storage.d.ts +1 -1
  36. package/dist/features/magic-context/storage.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/read-session-chunk.d.ts +1 -0
  41. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/tag-id-fallback.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/transform-context-state.d.ts +1 -1
  44. package/dist/hooks/magic-context/transform-context-state.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/transform.d.ts +1 -0
  46. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  47. package/dist/index.js +1206 -1052
  48. package/dist/plugin/dream-timer.d.ts +1 -1
  49. package/dist/plugin/dream-timer.d.ts.map +1 -1
  50. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  51. package/dist/shared/jsonc-parser.d.ts +0 -9
  52. package/dist/shared/jsonc-parser.d.ts.map +1 -1
  53. package/package.json +6 -2
  54. package/scripts/install.sh +35 -0
  55. package/dist/plugin/hooks/create-tag-content-resolver.d.ts +0 -7
  56. package/dist/plugin/hooks/create-tag-content-resolver.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -31,13 +31,6 @@ import { join } from "path";
31
31
 
32
32
  // src/shared/jsonc-parser.ts
33
33
  import { existsSync, readFileSync } from "fs";
34
-
35
- // src/shared/error-message.ts
36
- function getErrorMessage(error) {
37
- return error instanceof Error ? error.message : String(error);
38
- }
39
-
40
- // src/shared/jsonc-parser.ts
41
34
  function stripJsonComments(content) {
42
35
  let result = "";
43
36
  let inString = false;
@@ -13733,19 +13726,6 @@ var DEFAULT_DREAMER_TASKS = [
13733
13726
  "archive-stale",
13734
13727
  "improve"
13735
13728
  ];
13736
- var DreamingConfigSchema = exports_external.object({
13737
- enabled: exports_external.boolean().default(false),
13738
- schedule: exports_external.string().default("02:00-06:00"),
13739
- max_runtime_minutes: exports_external.number().min(10).default(120),
13740
- tasks: exports_external.array(DreamingTaskSchema).default(DEFAULT_DREAMER_TASKS),
13741
- task_timeout_minutes: exports_external.number().min(5).default(20)
13742
- }).default({
13743
- enabled: false,
13744
- schedule: "02:00-06:00",
13745
- max_runtime_minutes: 120,
13746
- tasks: DEFAULT_DREAMER_TASKS,
13747
- task_timeout_minutes: 20
13748
- });
13749
13729
  var DreamerConfigSchema = AgentOverrideConfigSchema.merge(exports_external.object({
13750
13730
  enabled: exports_external.boolean().default(false),
13751
13731
  schedule: exports_external.string().default("02:00-06:00"),
@@ -14618,6 +14598,24 @@ async function runSidekick(deps) {
14618
14598
  }
14619
14599
 
14620
14600
  // src/features/magic-context/compartment-storage.ts
14601
+ var insertCompartmentStatements = new WeakMap;
14602
+ var insertFactStatements = new WeakMap;
14603
+ function getInsertCompartmentStatement(db) {
14604
+ let stmt = insertCompartmentStatements.get(db);
14605
+ if (!stmt) {
14606
+ stmt = db.prepare("INSERT INTO compartments (session_id, sequence, start_message, end_message, start_message_id, end_message_id, title, content, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
14607
+ insertCompartmentStatements.set(db, stmt);
14608
+ }
14609
+ return stmt;
14610
+ }
14611
+ function getInsertFactStatement(db) {
14612
+ let stmt = insertFactStatements.get(db);
14613
+ if (!stmt) {
14614
+ stmt = db.prepare("INSERT INTO session_facts (session_id, category, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?)");
14615
+ insertFactStatements.set(db, stmt);
14616
+ }
14617
+ return stmt;
14618
+ }
14621
14619
  function isCompartmentRow(row) {
14622
14620
  if (row === null || typeof row !== "object")
14623
14621
  return false;
@@ -14630,6 +14628,18 @@ function isSessionFactRow(row) {
14630
14628
  const candidate = row;
14631
14629
  return typeof candidate.id === "number" && typeof candidate.session_id === "string" && typeof candidate.category === "string" && typeof candidate.content === "string" && typeof candidate.created_at === "number" && typeof candidate.updated_at === "number";
14632
14630
  }
14631
+ function insertCompartmentRows(db, sessionId, compartments, now) {
14632
+ const stmt = getInsertCompartmentStatement(db);
14633
+ for (const compartment of compartments) {
14634
+ stmt.run(sessionId, compartment.sequence, compartment.startMessage, compartment.endMessage, compartment.startMessageId, compartment.endMessageId, compartment.title, compartment.content, now);
14635
+ }
14636
+ }
14637
+ function insertFactRows(db, sessionId, facts, now) {
14638
+ const stmt = getInsertFactStatement(db);
14639
+ for (const fact of facts) {
14640
+ stmt.run(sessionId, fact.category, fact.content, now, now);
14641
+ }
14642
+ }
14633
14643
  function toCompartment(row) {
14634
14644
  return {
14635
14645
  id: row.id,
@@ -14667,20 +14677,14 @@ function appendCompartments(db, sessionId, compartments) {
14667
14677
  return;
14668
14678
  const now = Date.now();
14669
14679
  db.transaction(() => {
14670
- const stmt = db.prepare("INSERT INTO compartments (session_id, sequence, start_message, end_message, start_message_id, end_message_id, title, content, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
14671
- for (const c of compartments) {
14672
- stmt.run(sessionId, c.sequence, c.startMessage, c.endMessage, c.startMessageId, c.endMessageId, c.title, c.content, now);
14673
- }
14680
+ insertCompartmentRows(db, sessionId, compartments, now);
14674
14681
  })();
14675
14682
  }
14676
14683
  function replaceSessionFacts(db, sessionId, facts) {
14677
14684
  const now = Date.now();
14678
14685
  db.transaction(() => {
14679
14686
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
14680
- const stmt = db.prepare("INSERT INTO session_facts (session_id, category, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?)");
14681
- for (const f of facts) {
14682
- stmt.run(sessionId, f.category, f.content, now, now);
14683
- }
14687
+ insertFactRows(db, sessionId, facts, now);
14684
14688
  db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_count = 0 WHERE session_id = ?").run(sessionId);
14685
14689
  })();
14686
14690
  }
@@ -14693,14 +14697,8 @@ function replaceAllCompartmentState(db, sessionId, compartments, facts) {
14693
14697
  db.transaction(() => {
14694
14698
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
14695
14699
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
14696
- const compartmentStmt = db.prepare("INSERT INTO compartments (session_id, sequence, start_message, end_message, start_message_id, end_message_id, title, content, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
14697
- for (const c of compartments) {
14698
- compartmentStmt.run(sessionId, c.sequence, c.startMessage, c.endMessage, c.startMessageId, c.endMessageId, c.title, c.content, now);
14699
- }
14700
- const factStmt = db.prepare("INSERT INTO session_facts (session_id, category, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?)");
14701
- for (const f of facts) {
14702
- factStmt.run(sessionId, f.category, f.content, now, now);
14703
- }
14700
+ insertCompartmentRows(db, sessionId, compartments, now);
14701
+ insertFactRows(db, sessionId, facts, now);
14704
14702
  db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_count = 0 WHERE session_id = ?").run(sessionId);
14705
14703
  })();
14706
14704
  }
@@ -14777,14 +14775,8 @@ function promoteRecompStaging(db, sessionId) {
14777
14775
  return null;
14778
14776
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
14779
14777
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
14780
- const compartmentStmt = db.prepare("INSERT INTO compartments (session_id, sequence, start_message, end_message, start_message_id, end_message_id, title, content, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
14781
- for (const c of staging.compartments) {
14782
- compartmentStmt.run(sessionId, c.sequence, c.startMessage, c.endMessage, c.startMessageId, c.endMessageId, c.title, c.content, now);
14783
- }
14784
- const factStmt = db.prepare("INSERT INTO session_facts (session_id, category, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?)");
14785
- for (const f of staging.facts) {
14786
- factStmt.run(sessionId, f.category, f.content, now, now);
14787
- }
14778
+ insertCompartmentRows(db, sessionId, staging.compartments, now);
14779
+ insertFactRows(db, sessionId, staging.facts, now);
14788
14780
  db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
14789
14781
  db.prepare("DELETE FROM recomp_facts WHERE session_id = ?").run(sessionId);
14790
14782
  db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_count = 0 WHERE session_id = ?").run(sessionId);
@@ -15100,6 +15092,13 @@ function clearStaleEntries(db, maxAgeMs) {
15100
15092
  // src/features/magic-context/dreamer/runner.ts
15101
15093
  import { existsSync as existsSync3 } from "fs";
15102
15094
  import { join as join3 } from "path";
15095
+
15096
+ // src/shared/error-message.ts
15097
+ function getErrorMessage(error48) {
15098
+ return error48 instanceof Error ? error48.message : String(error48);
15099
+ }
15100
+
15101
+ // src/features/magic-context/dreamer/runner.ts
15103
15102
  var dreamProjectDirectories = new Map;
15104
15103
  function registerDreamProjectDirectory(projectIdentity, directory) {
15105
15104
  dreamProjectDirectories.set(projectIdentity, directory);
@@ -15494,6 +15493,7 @@ function initializeDatabase(db) {
15494
15493
  retry_count INTEGER DEFAULT 0
15495
15494
  );
15496
15495
  CREATE INDEX IF NOT EXISTS idx_dream_queue_project ON dream_queue(project_path);
15496
+ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, enqueued_at);
15497
15497
 
15498
15498
  CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
15499
15499
  content,
@@ -15805,892 +15805,1061 @@ function setPersistedStickyTurnReminder(db, sessionId, text, messageId = "") {
15805
15805
  function clearPersistedStickyTurnReminder(db, sessionId) {
15806
15806
  db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = '', sticky_turn_reminder_message_id = '' WHERE session_id = ?").run(sessionId);
15807
15807
  }
15808
- // src/features/magic-context/storage-meta-session.ts
15809
- function getOrCreateSessionMeta(db, sessionId) {
15810
- const result = db.prepare("SELECT session_id, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash FROM session_meta WHERE session_id = ?").get(sessionId);
15811
- if (isSessionMetaRow(result)) {
15812
- return toSessionMeta(result);
15813
- }
15814
- const defaults = getDefaultSessionMeta(sessionId);
15815
- ensureSessionMetaRow(db, sessionId);
15816
- return defaults;
15808
+ // src/shared/internal-initiator-marker.ts
15809
+ var OMO_INTERNAL_INITIATOR_MARKER = "<!-- OMO_INTERNAL_INITIATOR -->";
15810
+
15811
+ // src/shared/system-directive.ts
15812
+ var SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: MAGIC-CONTEXT";
15813
+ function isSystemDirective(text) {
15814
+ return text.trimStart().startsWith(SYSTEM_DIRECTIVE_PREFIX);
15817
15815
  }
15818
- function updateSessionMeta(db, sessionId, updates) {
15819
- const setClauses = [];
15820
- const values = [];
15821
- for (const [key, column] of Object.entries(META_COLUMNS)) {
15822
- const value = updates[key];
15823
- if (value === undefined)
15824
- continue;
15825
- if (value === null) {
15826
- setClauses.push(`${column} = ?`);
15827
- values.push("");
15828
- } else if (BOOLEAN_META_KEYS.has(key)) {
15829
- setClauses.push(`${column} = ?`);
15830
- values.push(value ? 1 : 0);
15831
- } else if (typeof value === "string" || typeof value === "number") {
15832
- setClauses.push(`${column} = ?`);
15833
- values.push(value);
15834
- }
15835
- }
15836
- if (setClauses.length === 0) {
15816
+ function removeSystemReminders(text) {
15817
+ return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, "").trim();
15818
+ }
15819
+
15820
+ // src/hooks/magic-context/read-session-db.ts
15821
+ import { Database as Database2 } from "bun:sqlite";
15822
+ import { join as join6 } from "path";
15823
+ function getOpenCodeDbPath() {
15824
+ return join6(getDataDir(), "opencode", "opencode.db");
15825
+ }
15826
+ var cachedReadOnlyDb = null;
15827
+ function closeCachedReadOnlyDb() {
15828
+ if (!cachedReadOnlyDb) {
15837
15829
  return;
15838
15830
  }
15839
- db.transaction(() => {
15840
- ensureSessionMetaRow(db, sessionId);
15841
- db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")} WHERE session_id = ?`).run(...values, sessionId);
15842
- })();
15831
+ try {
15832
+ cachedReadOnlyDb.db.close(false);
15833
+ } catch (error48) {
15834
+ log("[magic-context] failed to close cached OpenCode read-only DB:", error48);
15835
+ } finally {
15836
+ cachedReadOnlyDb = null;
15837
+ }
15843
15838
  }
15844
- function clearSession(db, sessionId) {
15845
- db.transaction(() => {
15846
- db.prepare("DELETE FROM pending_ops WHERE session_id = ?").run(sessionId);
15847
- db.prepare("DELETE FROM source_contents WHERE session_id = ?").run(sessionId);
15848
- db.prepare("DELETE FROM tags WHERE session_id = ?").run(sessionId);
15849
- db.prepare("DELETE FROM session_meta WHERE session_id = ?").run(sessionId);
15850
- db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
15851
- db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
15852
- db.prepare("DELETE FROM session_notes WHERE session_id = ?").run(sessionId);
15853
- db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
15854
- db.prepare("DELETE FROM recomp_facts WHERE session_id = ?").run(sessionId);
15855
- })();
15839
+ function getReadOnlySessionDb() {
15840
+ const dbPath = getOpenCodeDbPath();
15841
+ if (cachedReadOnlyDb?.path === dbPath) {
15842
+ return cachedReadOnlyDb.db;
15843
+ }
15844
+ closeCachedReadOnlyDb();
15845
+ const db = new Database2(dbPath, { readonly: true });
15846
+ cachedReadOnlyDb = { path: dbPath, db };
15847
+ return db;
15856
15848
  }
15857
- // src/features/magic-context/storage-notes.ts
15858
- function isSessionNoteRow(row) {
15859
- if (row === null || typeof row !== "object")
15860
- return false;
15861
- const candidate = row;
15862
- return typeof candidate.id === "number" && typeof candidate.session_id === "string" && typeof candidate.content === "string" && typeof candidate.created_at === "number";
15849
+ function withReadOnlySessionDb(fn) {
15850
+ return fn(getReadOnlySessionDb());
15863
15851
  }
15864
- function toSessionNote(row) {
15865
- return {
15866
- id: row.id,
15867
- sessionId: row.session_id,
15868
- content: row.content,
15869
- createdAt: row.created_at
15870
- };
15852
+ function getRawSessionMessageCountFromDb(db, sessionId) {
15853
+ const row = db.prepare("SELECT COUNT(*) as count FROM message WHERE session_id = ?").get(sessionId);
15854
+ return typeof row?.count === "number" ? row.count : 0;
15871
15855
  }
15872
- function getSessionNotes(db, sessionId) {
15873
- const rows = db.prepare("SELECT * FROM session_notes WHERE session_id = ? ORDER BY id ASC").all(sessionId).filter(isSessionNoteRow);
15874
- return rows.map(toSessionNote);
15856
+
15857
+ // src/hooks/magic-context/read-session-formatting.ts
15858
+ var COMMIT_HASH_PATTERN = /`?\b([0-9a-f]{6,12})\b`?/gi;
15859
+ var COMMIT_HINT_PATTERN = /\b(commit(?:ted)?|cherry-?pick(?:ed)?|hash(?:es)?|sha)\b/i;
15860
+ var MAX_COMMITS_PER_BLOCK = 5;
15861
+ function hasMeaningfulUserText(parts) {
15862
+ for (const part of parts) {
15863
+ if (part === null || typeof part !== "object")
15864
+ continue;
15865
+ const candidate = part;
15866
+ if (candidate.type !== "text" || typeof candidate.text !== "string")
15867
+ continue;
15868
+ if (candidate.ignored === true)
15869
+ continue;
15870
+ const cleaned = removeSystemReminders(candidate.text).replace(OMO_INTERNAL_INITIATOR_MARKER, "").trim();
15871
+ if (!cleaned)
15872
+ continue;
15873
+ if (isSystemDirective(cleaned))
15874
+ continue;
15875
+ return true;
15876
+ }
15877
+ return false;
15875
15878
  }
15876
- function addSessionNote(db, sessionId, content) {
15877
- db.prepare("INSERT INTO session_notes (session_id, content, created_at) VALUES (?, ?, ?)").run(sessionId, content, Date.now());
15879
+ function extractTexts(parts) {
15880
+ const texts = [];
15881
+ for (const part of parts) {
15882
+ if (part === null || typeof part !== "object")
15883
+ continue;
15884
+ const p = part;
15885
+ if (p.type === "text" && typeof p.text === "string" && p.text.trim().length > 0) {
15886
+ texts.push(p.text.trim());
15887
+ }
15888
+ }
15889
+ return texts;
15878
15890
  }
15879
- function clearSessionNotes(db, sessionId) {
15880
- db.prepare("DELETE FROM session_notes WHERE session_id = ?").run(sessionId);
15891
+ function estimateTokens(text) {
15892
+ return Math.ceil(text.length / 3.5);
15881
15893
  }
15882
- // src/features/magic-context/storage-ops.ts
15883
- function isPendingOpRow(row) {
15884
- if (row === null || typeof row !== "object")
15885
- return false;
15886
- const r = row;
15887
- return typeof r.id === "number" && typeof r.session_id === "string" && typeof r.tag_id === "number" && typeof r.operation === "string" && typeof r.queued_at === "number";
15894
+ function normalizeText(text) {
15895
+ return text.replace(/\s+/g, " ").trim();
15888
15896
  }
15889
- function toPendingOp(row) {
15890
- if (row.operation !== "drop") {
15891
- sessionLog(row.session_id, `unknown pending operation "${row.operation}"; ignoring`);
15892
- return null;
15897
+ function compactRole(role) {
15898
+ if (role === "assistant")
15899
+ return "A";
15900
+ if (role === "user")
15901
+ return "U";
15902
+ return role.slice(0, 1).toUpperCase() || "M";
15903
+ }
15904
+ function formatBlock(block) {
15905
+ const range = block.startOrdinal === block.endOrdinal ? `[${block.startOrdinal}]` : `[${block.startOrdinal}-${block.endOrdinal}]`;
15906
+ const commitSuffix = block.commitHashes.length > 0 ? ` commits: ${block.commitHashes.join(", ")}` : "";
15907
+ return `${range} ${block.role}:${commitSuffix} ${block.parts.join(" / ")}`;
15908
+ }
15909
+ function extractCommitHashes(text) {
15910
+ const hashes = [];
15911
+ const seen = new Set;
15912
+ for (const match of text.matchAll(COMMIT_HASH_PATTERN)) {
15913
+ const hash2 = match[1]?.toLowerCase();
15914
+ if (!hash2 || seen.has(hash2))
15915
+ continue;
15916
+ seen.add(hash2);
15917
+ hashes.push(hash2);
15918
+ if (hashes.length >= MAX_COMMITS_PER_BLOCK)
15919
+ break;
15920
+ }
15921
+ return hashes;
15922
+ }
15923
+ function compactTextForSummary(text, role) {
15924
+ const commitHashes = role === "assistant" ? extractCommitHashes(text) : [];
15925
+ if (commitHashes.length === 0 || !COMMIT_HINT_PATTERN.test(text)) {
15926
+ return { text, commitHashes };
15893
15927
  }
15928
+ const withoutHashes = text.replace(COMMIT_HASH_PATTERN, "").replace(/\(\s*\)/g, "").replace(/\s+,/g, ",").replace(/,\s*,+/g, ", ").replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1").trim();
15894
15929
  return {
15895
- id: row.id,
15896
- sessionId: row.session_id,
15897
- tagId: row.tag_id,
15898
- operation: row.operation,
15899
- queuedAt: row.queued_at
15930
+ text: withoutHashes.length > 0 ? withoutHashes : text,
15931
+ commitHashes
15900
15932
  };
15901
15933
  }
15902
- function queuePendingOp(db, sessionId, tagId, operation, queuedAt = Date.now()) {
15903
- db.prepare("INSERT INTO pending_ops (session_id, tag_id, operation, queued_at) VALUES (?, ?, ?, ?)").run(sessionId, tagId, operation, queuedAt);
15904
- }
15905
- function getPendingOps(db, sessionId) {
15906
- const rows = db.prepare("SELECT id, session_id, tag_id, operation, queued_at FROM pending_ops WHERE session_id = ? ORDER BY queued_at ASC, id ASC").all(sessionId).filter(isPendingOpRow);
15907
- return rows.map(toPendingOp).filter((op) => op !== null);
15908
- }
15909
- function removePendingOp(db, sessionId, tagId) {
15910
- db.prepare("DELETE FROM pending_ops WHERE session_id = ? AND tag_id = ?").run(sessionId, tagId);
15934
+ function mergeCommitHashes(existing, next) {
15935
+ if (next.length === 0)
15936
+ return existing;
15937
+ const merged = [...existing];
15938
+ for (const hash2 of next) {
15939
+ if (merged.includes(hash2))
15940
+ continue;
15941
+ merged.push(hash2);
15942
+ if (merged.length >= MAX_COMMITS_PER_BLOCK)
15943
+ break;
15944
+ }
15945
+ return merged;
15911
15946
  }
15912
- // src/features/magic-context/storage-source.ts
15913
- function isSourceContentRow(row) {
15947
+
15948
+ // src/hooks/magic-context/read-session-raw.ts
15949
+ function isRawMessageRow(row) {
15914
15950
  if (row === null || typeof row !== "object")
15915
15951
  return false;
15916
- const r = row;
15917
- return typeof r.tag_id === "number" && typeof r.content === "string";
15952
+ const candidate = row;
15953
+ return typeof candidate.id === "string" && typeof candidate.data === "string";
15918
15954
  }
15919
- function saveSourceContent(db, sessionId, tagId, content) {
15920
- db.prepare("INSERT OR IGNORE INTO source_contents (tag_id, session_id, content, created_at) VALUES (?, ?, ?, ?)").run(tagId, sessionId, content, Date.now());
15921
- }
15922
- function replaceSourceContent(db, sessionId, tagId, content) {
15923
- db.prepare(`INSERT INTO source_contents (tag_id, session_id, content, created_at)
15924
- VALUES (?, ?, ?, ?)
15925
- ON CONFLICT(session_id, tag_id)
15926
- DO UPDATE SET content = excluded.content, created_at = excluded.created_at`).run(tagId, sessionId, content, Date.now());
15927
- }
15928
- function getSourceContents(db, sessionId, tagIds) {
15929
- if (tagIds.length === 0) {
15930
- return new Map;
15931
- }
15932
- const placeholders = tagIds.map(() => "?").join(", ");
15933
- const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
15934
- const sources = new Map;
15935
- for (const row of rows) {
15936
- sources.set(row.tag_id, row.content);
15937
- }
15938
- return sources;
15939
- }
15940
- // src/features/magic-context/storage-tags.ts
15941
- var insertTagStatements = new WeakMap;
15942
- function getInsertTagStatement(db) {
15943
- let stmt = insertTagStatements.get(db);
15944
- if (!stmt) {
15945
- stmt = db.prepare("INSERT INTO tags (session_id, message_id, type, byte_size, tag_number) VALUES (?, ?, ?, ?, ?)");
15946
- insertTagStatements.set(db, stmt);
15947
- }
15948
- return stmt;
15949
- }
15950
- function isTagRow(row) {
15955
+ function isRawPartRow(row) {
15951
15956
  if (row === null || typeof row !== "object")
15952
15957
  return false;
15953
- const r = row;
15954
- return typeof r.id === "number" && typeof r.message_id === "string" && typeof r.type === "string" && typeof r.status === "string" && typeof r.byte_size === "number" && typeof r.session_id === "string" && typeof r.tag_number === "number";
15958
+ const candidate = row;
15959
+ return typeof candidate.message_id === "string" && typeof candidate.data === "string";
15955
15960
  }
15956
- function toTagEntry(row) {
15957
- const type = row.type === "tool" ? "tool" : row.type === "file" ? "file" : "message";
15958
- const status = row.status === "dropped" || row.status === "compacted" ? row.status : "active";
15959
- return {
15960
- tagNumber: row.tag_number,
15961
- messageId: row.message_id,
15962
- type,
15963
- status,
15964
- byteSize: row.byte_size,
15965
- sessionId: row.session_id
15966
- };
15961
+ function parseJsonRecord(value) {
15962
+ const parsed = JSON.parse(value);
15963
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
15964
+ throw new Error("Expected JSON object");
15965
+ }
15966
+ return parsed;
15967
15967
  }
15968
- function insertTag(db, sessionId, messageId, type, byteSize, tagNumber) {
15969
- getInsertTagStatement(db).run(sessionId, messageId, type, byteSize, tagNumber);
15970
- return tagNumber;
15968
+ function parseJsonUnknown(value) {
15969
+ return JSON.parse(value);
15971
15970
  }
15972
- function updateTagStatus(db, sessionId, tagId, status) {
15973
- db.prepare("UPDATE tags SET status = ? WHERE session_id = ? AND tag_number = ?").run(status, sessionId, tagId);
15971
+ function readRawSessionMessagesFromDb(db, sessionId) {
15972
+ const messageRows = db.prepare("SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
15973
+ const partRows = db.prepare("SELECT message_id, data FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
15974
+ const partsByMessageId = new Map;
15975
+ for (const part of partRows) {
15976
+ const list = partsByMessageId.get(part.message_id) ?? [];
15977
+ list.push(parseJsonUnknown(part.data));
15978
+ partsByMessageId.set(part.message_id, list);
15979
+ }
15980
+ return messageRows.map((row, index) => {
15981
+ const info = parseJsonRecord(row.data);
15982
+ const role = typeof info.role === "string" ? info.role : "unknown";
15983
+ return {
15984
+ ordinal: index + 1,
15985
+ id: row.id,
15986
+ role,
15987
+ parts: partsByMessageId.get(row.id) ?? []
15988
+ };
15989
+ });
15974
15990
  }
15975
- function updateTagMessageId(db, sessionId, tagId, messageId) {
15976
- db.prepare("UPDATE tags SET message_id = ? WHERE session_id = ? AND tag_number = ?").run(messageId, sessionId, tagId);
15991
+
15992
+ // src/hooks/magic-context/tag-content-primitives.ts
15993
+ var encoder = new TextEncoder;
15994
+ var TAG_PREFIX_REGEX = /^\u00A7\d+\u00A7\s*/;
15995
+ function byteSize(value) {
15996
+ return encoder.encode(value).length;
15977
15997
  }
15978
- function getTagsBySession(db, sessionId) {
15979
- const rows = db.prepare("SELECT id, message_id, type, status, byte_size, session_id, tag_number FROM tags WHERE session_id = ? ORDER BY tag_number ASC, id ASC").all(sessionId).filter(isTagRow);
15980
- return rows.map(toTagEntry);
15998
+ function stripTagPrefix(value) {
15999
+ return value.replace(TAG_PREFIX_REGEX, "");
15981
16000
  }
15982
- function getTopNBySize(db, sessionId, n) {
15983
- if (n <= 0) {
15984
- return [];
15985
- }
15986
- const rows = db.prepare("SELECT id, message_id, type, status, byte_size, session_id, tag_number FROM tags WHERE session_id = ? AND status = 'active' ORDER BY byte_size DESC, tag_number ASC LIMIT ?").all(sessionId, n).filter(isTagRow);
15987
- return rows.map(toTagEntry);
16001
+ function prependTag(tagId, value) {
16002
+ const stripped = stripTagPrefix(value);
16003
+ return `\xA7${tagId}\xA7 ${stripped}`;
15988
16004
  }
15989
- // src/plugin/dream-timer.ts
15990
- var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
15991
- function startDreamScheduleTimer(args) {
15992
- const { client, dreamerConfig } = args;
15993
- if (!dreamerConfig.enabled || !dreamerConfig.schedule?.trim()) {
15994
- return;
15995
- }
15996
- const timer = setInterval(() => {
15997
- try {
15998
- const db = openDatabase();
15999
- checkScheduleAndEnqueue(db, dreamerConfig.schedule);
16000
- processDreamQueue({
16001
- db,
16002
- client,
16003
- tasks: dreamerConfig.tasks,
16004
- taskTimeoutMinutes: dreamerConfig.task_timeout_minutes,
16005
- maxRuntimeMinutes: dreamerConfig.max_runtime_minutes
16006
- }).catch((error48) => {
16007
- log("[dreamer] timer-triggered queue processing failed:", error48);
16008
- });
16009
- } catch (error48) {
16010
- log("[dreamer] timer-triggered schedule check failed:", error48);
16011
- }
16012
- }, DREAM_TIMER_INTERVAL_MS);
16013
- if (typeof timer === "object" && "unref" in timer) {
16014
- timer.unref();
16015
- }
16016
- log(`[dreamer] started independent schedule timer (every ${DREAM_TIMER_INTERVAL_MS / 60000}m)`);
16005
+ function isThinkingPart(part) {
16006
+ if (part === null || typeof part !== "object")
16007
+ return false;
16008
+ const candidate = part;
16009
+ return candidate.type === "thinking" || candidate.type === "reasoning";
16017
16010
  }
16018
16011
 
16019
- // src/plugin/event.ts
16020
- function createEventHandler(args) {
16021
- return async (input) => {
16022
- await args.magicContext?.event?.(input);
16023
- };
16012
+ // src/hooks/magic-context/tag-part-guards.ts
16013
+ function isTextPart(part) {
16014
+ if (part === null || typeof part !== "object")
16015
+ return false;
16016
+ const p = part;
16017
+ return p.type === "text" && typeof p.text === "string";
16024
16018
  }
16025
-
16026
- // src/features/magic-context/compaction.ts
16027
- function createCompactionHandler() {
16028
- return {
16029
- onCompacted(sessionId, db) {
16030
- db.transaction(() => {
16031
- db.prepare("UPDATE tags SET status = 'compacted' WHERE session_id = ? AND status IN ('active', 'dropped')").run(sessionId);
16032
- db.prepare("DELETE FROM pending_ops WHERE session_id = ?").run(sessionId);
16033
- })();
16034
- updateSessionMeta(db, sessionId, { lastNudgeBand: null });
16035
- }
16036
- };
16019
+ function isToolPartWithOutput(part) {
16020
+ if (part === null || typeof part !== "object")
16021
+ return false;
16022
+ const p = part;
16023
+ if (p.type !== "tool" || typeof p.callID !== "string")
16024
+ return false;
16025
+ if (p.state === null || typeof p.state !== "object")
16026
+ return false;
16027
+ return typeof p.state.output === "string";
16037
16028
  }
16038
-
16039
- // src/hooks/is-anthropic-provider.ts
16040
- function isAnthropicProvider(providerID) {
16041
- return providerID === "anthropic" || providerID === "google-vertex-anthropic";
16029
+ function isFilePart(part) {
16030
+ if (part === null || typeof part !== "object")
16031
+ return false;
16032
+ const p = part;
16033
+ return p.type === "file" && typeof p.url === "string";
16034
+ }
16035
+ function buildFileSourceContent(parts) {
16036
+ const content = parts.filter(isTextPart).map((part) => stripTagPrefix(part.text)).join(`
16037
+ `).trim();
16038
+ return content.length > 0 ? content : null;
16042
16039
  }
16043
16040
 
16044
- // src/hooks/magic-context/event-resolvers.ts
16045
- var DEFAULT_CONTEXT_LIMIT = 200000;
16046
- function resolveContextLimit(providerID, modelID, config2) {
16047
- if (!providerID) {
16048
- return DEFAULT_CONTEXT_LIMIT;
16049
- }
16050
- if (modelID) {
16051
- const modelSpecific = config2.modelContextLimitsCache?.get(`${providerID}/${modelID}`);
16052
- if (typeof modelSpecific === "number" && modelSpecific > 0) {
16053
- return modelSpecific;
16054
- }
16055
- }
16056
- if (isAnthropicProvider(providerID)) {
16057
- return 1e6;
16058
- }
16059
- return DEFAULT_CONTEXT_LIMIT;
16041
+ // src/hooks/magic-context/read-session-chunk.ts
16042
+ var activeRawMessageCache = null;
16043
+ function cleanUserText(text) {
16044
+ return removeSystemReminders(text).replace(OMO_INTERNAL_INITIATOR_MARKER, "").trim();
16060
16045
  }
16061
- function resolveCacheTtl(cacheTtl, modelKey) {
16062
- if (typeof cacheTtl === "string") {
16063
- return cacheTtl;
16064
- }
16065
- if (modelKey && typeof cacheTtl[modelKey] === "string") {
16066
- return cacheTtl[modelKey];
16046
+ function withRawSessionMessageCache(fn) {
16047
+ const outerCache = activeRawMessageCache;
16048
+ if (!outerCache) {
16049
+ activeRawMessageCache = new Map;
16067
16050
  }
16068
- if (modelKey) {
16069
- const bareModelId = modelKey.split("/").slice(1).join("/");
16070
- if (bareModelId && typeof cacheTtl[bareModelId] === "string") {
16071
- return cacheTtl[bareModelId];
16051
+ try {
16052
+ return fn();
16053
+ } finally {
16054
+ if (!outerCache) {
16055
+ activeRawMessageCache = null;
16072
16056
  }
16073
16057
  }
16074
- return cacheTtl.default ?? "5m";
16075
16058
  }
16076
- function resolveExecuteThreshold(config2, modelKey, fallback) {
16077
- if (typeof config2 === "number") {
16078
- return config2;
16079
- }
16080
- if (modelKey && typeof config2[modelKey] === "number") {
16081
- return config2[modelKey];
16082
- }
16083
- if (modelKey) {
16084
- const bareModelId = modelKey.split("/").slice(1).join("/");
16085
- if (bareModelId && typeof config2[bareModelId] === "number") {
16086
- return config2[bareModelId];
16059
+ function readRawSessionMessages(sessionId) {
16060
+ if (activeRawMessageCache) {
16061
+ const cached2 = activeRawMessageCache.get(sessionId);
16062
+ if (cached2) {
16063
+ return cached2;
16087
16064
  }
16065
+ const messages = withReadOnlySessionDb((db) => readRawSessionMessagesFromDb(db, sessionId));
16066
+ activeRawMessageCache.set(sessionId, messages);
16067
+ return messages;
16088
16068
  }
16089
- return config2.default ?? fallback;
16069
+ return withReadOnlySessionDb((db) => readRawSessionMessagesFromDb(db, sessionId));
16090
16070
  }
16091
- function resolveModelKey(providerID, modelID) {
16092
- if (!providerID || !modelID) {
16093
- return;
16094
- }
16095
- return `${providerID}/${modelID}`;
16071
+ function getRawSessionMessageCount(sessionId) {
16072
+ return withReadOnlySessionDb((db) => getRawSessionMessageCountFromDb(db, sessionId));
16096
16073
  }
16097
- function resolveSessionId(properties) {
16098
- if (typeof properties?.sessionID === "string") {
16099
- return properties.sessionID;
16100
- }
16101
- const info = properties?.info;
16102
- if (info === null || typeof info !== "object") {
16103
- return;
16104
- }
16105
- const record2 = info;
16106
- if (typeof record2.sessionID === "string") {
16107
- return record2.sessionID;
16074
+ function getRawSessionTagKeysThrough(sessionId, upToMessageIndex) {
16075
+ const messages = readRawSessionMessages(sessionId);
16076
+ const keys = [];
16077
+ for (const message of messages) {
16078
+ if (message.ordinal > upToMessageIndex)
16079
+ break;
16080
+ for (const [partIndex, part] of message.parts.entries()) {
16081
+ if (isTextPart(part)) {
16082
+ keys.push(`${message.id}:p${partIndex}`);
16083
+ }
16084
+ if (isFilePart(part)) {
16085
+ keys.push(`${message.id}:file${partIndex}`);
16086
+ }
16087
+ if (isToolPartWithOutput(part)) {
16088
+ keys.push(part.callID);
16089
+ }
16090
+ }
16108
16091
  }
16109
- if (typeof record2.id === "string") {
16110
- return record2.id;
16092
+ return keys;
16093
+ }
16094
+ var PROTECTED_TAIL_USER_TURNS = 5;
16095
+ function getProtectedTailStartOrdinal(sessionId) {
16096
+ const messages = readRawSessionMessages(sessionId);
16097
+ const userOrdinals = messages.filter((m) => m.role === "user" && hasMeaningfulUserText(m.parts)).map((m) => m.ordinal);
16098
+ if (userOrdinals.length < PROTECTED_TAIL_USER_TURNS) {
16099
+ return 1;
16111
16100
  }
16112
- return;
16101
+ return userOrdinals[userOrdinals.length - PROTECTED_TAIL_USER_TURNS];
16113
16102
  }
16114
-
16115
- // src/features/magic-context/scheduler.ts
16116
- var TTL_PATTERN = /^(\d+)([smh])$/;
16117
- var NUMERIC_PATTERN = /^\d+$/;
16118
- var UNIT_TO_MS = {
16119
- s: 1000,
16120
- m: 60 * 1000,
16121
- h: 60 * 60 * 1000
16122
- };
16123
- function parseCacheTtl(ttl) {
16124
- const normalizedTtl = ttl.trim();
16125
- if (NUMERIC_PATTERN.test(normalizedTtl)) {
16126
- return Number(normalizedTtl);
16103
+ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal) {
16104
+ const messages = readRawSessionMessages(sessionId);
16105
+ const startOrdinal = Math.max(1, offset);
16106
+ const lines = [];
16107
+ const lineMeta = [];
16108
+ let totalTokens = 0;
16109
+ let messagesProcessed = 0;
16110
+ let lastOrdinal = startOrdinal - 1;
16111
+ let lastMessageId = "";
16112
+ let firstMessageId = "";
16113
+ let currentBlock = null;
16114
+ let pendingNoiseMeta = [];
16115
+ let commitClusters = 0;
16116
+ let lastFlushedRole = "";
16117
+ function flushCurrentBlock() {
16118
+ if (!currentBlock)
16119
+ return true;
16120
+ const blockText = formatBlock(currentBlock);
16121
+ const blockTokens = estimateTokens(blockText);
16122
+ if (totalTokens + blockTokens > tokenBudget && totalTokens > 0) {
16123
+ return false;
16124
+ }
16125
+ if (currentBlock.role === "A" && currentBlock.commitHashes.length > 0 && lastFlushedRole !== "A") {
16126
+ commitClusters++;
16127
+ }
16128
+ lastFlushedRole = currentBlock.role;
16129
+ if (!firstMessageId)
16130
+ firstMessageId = currentBlock.meta[0]?.messageId ?? "";
16131
+ lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
16132
+ lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
16133
+ messagesProcessed += currentBlock.meta.length;
16134
+ lines.push(blockText);
16135
+ lineMeta.push(...currentBlock.meta);
16136
+ totalTokens += blockTokens;
16137
+ currentBlock = null;
16138
+ return true;
16127
16139
  }
16128
- const match = normalizedTtl.match(TTL_PATTERN);
16129
- if (!match) {
16130
- throw new Error(`Invalid cache TTL format: ${ttl}`);
16140
+ for (const msg of messages) {
16141
+ if (eligibleEndOrdinal !== undefined && msg.ordinal >= eligibleEndOrdinal)
16142
+ break;
16143
+ if (msg.ordinal < startOrdinal)
16144
+ continue;
16145
+ const meta3 = { ordinal: msg.ordinal, messageId: msg.id };
16146
+ if (msg.role === "user" && !hasMeaningfulUserText(msg.parts)) {
16147
+ pendingNoiseMeta.push(meta3);
16148
+ continue;
16149
+ }
16150
+ const role = compactRole(msg.role);
16151
+ const compacted = compactTextForSummary(extractTexts(msg.parts).map((t) => msg.role === "user" ? cleanUserText(t) : t).map(normalizeText).filter((value) => value.length > 0).join(" / "), msg.role);
16152
+ const text = compacted.text;
16153
+ if (!text) {
16154
+ pendingNoiseMeta.push(meta3);
16155
+ continue;
16156
+ }
16157
+ if (currentBlock && currentBlock.role === role) {
16158
+ currentBlock.endOrdinal = msg.ordinal;
16159
+ currentBlock.parts.push(text);
16160
+ currentBlock.meta.push(...pendingNoiseMeta, meta3);
16161
+ currentBlock.commitHashes = mergeCommitHashes(currentBlock.commitHashes, compacted.commitHashes);
16162
+ pendingNoiseMeta = [];
16163
+ continue;
16164
+ }
16165
+ if (!flushCurrentBlock())
16166
+ break;
16167
+ currentBlock = {
16168
+ role,
16169
+ startOrdinal: pendingNoiseMeta[0]?.ordinal ?? msg.ordinal,
16170
+ endOrdinal: msg.ordinal,
16171
+ parts: [text],
16172
+ meta: [...pendingNoiseMeta, meta3],
16173
+ commitHashes: [...compacted.commitHashes]
16174
+ };
16175
+ pendingNoiseMeta = [];
16131
16176
  }
16132
- const value = Number(match[1]);
16133
- const unit = match[2];
16134
- return value * UNIT_TO_MS[unit];
16135
- }
16136
- function createScheduler(config2) {
16177
+ flushCurrentBlock();
16137
16178
  return {
16138
- shouldExecute(sessionMeta, contextUsage, currentTime = Date.now(), sessionId) {
16139
- const threshold = resolveExecuteThreshold(config2.executeThresholdPercentage, undefined, 65);
16140
- if (contextUsage.percentage >= threshold) {
16141
- return "execute";
16142
- }
16143
- let ttlMs;
16144
- try {
16145
- ttlMs = parseCacheTtl(sessionMeta.cacheTtl);
16146
- } catch (error48) {
16147
- if (sessionId) {
16148
- sessionLog(sessionId, `invalid cache_ttl "${sessionMeta.cacheTtl}"; falling back to default 5m`, error48);
16149
- } else {
16150
- log(`[magic-context] invalid cache_ttl "${sessionMeta.cacheTtl}"; falling back to default 5m`, error48);
16151
- }
16152
- ttlMs = parseCacheTtl("5m");
16153
- }
16154
- const elapsedTime = currentTime - sessionMeta.lastResponseTime;
16155
- if (elapsedTime > ttlMs) {
16156
- return "execute";
16157
- }
16158
- return "defer";
16159
- }
16179
+ startIndex: startOrdinal,
16180
+ endIndex: lastOrdinal,
16181
+ startMessageId: firstMessageId,
16182
+ endMessageId: lastMessageId,
16183
+ messageCount: messagesProcessed,
16184
+ tokenEstimate: totalTokens,
16185
+ hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, messages.length) : messages.length),
16186
+ text: lines.join(`
16187
+ `),
16188
+ lines: lineMeta,
16189
+ commitClusterCount: commitClusters
16160
16190
  };
16161
16191
  }
16162
16192
 
16163
- // src/features/magic-context/tagger.ts
16164
- var GET_COUNTER_SQL = `SELECT counter FROM session_meta WHERE session_id = ?`;
16165
- var GET_ASSIGNMENTS_SQL = "SELECT message_id, tag_number FROM tags WHERE session_id = ? ORDER BY tag_number ASC";
16166
- function isAssignmentRow(row) {
16167
- if (row === null || typeof row !== "object") {
16168
- return false;
16193
+ // src/features/magic-context/message-index.ts
16194
+ var lastIndexedStatements = new WeakMap;
16195
+ var insertMessageStatements = new WeakMap;
16196
+ var upsertIndexStatements = new WeakMap;
16197
+ var deleteFtsStatements = new WeakMap;
16198
+ var deleteIndexStatements = new WeakMap;
16199
+ function normalizeIndexText(text) {
16200
+ return text.replace(/\s+/g, " ").trim();
16201
+ }
16202
+ function getLastIndexedStatement(db) {
16203
+ let stmt = lastIndexedStatements.get(db);
16204
+ if (!stmt) {
16205
+ stmt = db.prepare("SELECT last_indexed_ordinal FROM message_history_index WHERE session_id = ?");
16206
+ lastIndexedStatements.set(db, stmt);
16169
16207
  }
16170
- const candidate = row;
16171
- return typeof candidate.message_id === "string" && typeof candidate.tag_number === "number";
16208
+ return stmt;
16172
16209
  }
16173
- var UPSERT_COUNTER_SQL = `
16174
- INSERT INTO session_meta (session_id, counter)
16175
- VALUES (?, ?)
16176
- ON CONFLICT(session_id) DO UPDATE SET counter = excluded.counter
16177
- `;
16178
- var upsertCounterStatements = new WeakMap;
16179
- function getUpsertCounterStatement(db) {
16180
- let stmt = upsertCounterStatements.get(db);
16210
+ function getInsertMessageStatement(db) {
16211
+ let stmt = insertMessageStatements.get(db);
16181
16212
  if (!stmt) {
16182
- stmt = db.prepare(UPSERT_COUNTER_SQL);
16183
- upsertCounterStatements.set(db, stmt);
16213
+ stmt = db.prepare("INSERT INTO message_history_fts (session_id, message_ordinal, message_id, role, content) VALUES (?, ?, ?, ?, ?)");
16214
+ insertMessageStatements.set(db, stmt);
16184
16215
  }
16185
16216
  return stmt;
16186
16217
  }
16187
- function createTagger() {
16188
- const counters = new Map;
16189
- const assignments = new Map;
16190
- function getSessionAssignments(sessionId) {
16191
- let map2 = assignments.get(sessionId);
16192
- if (!map2) {
16193
- map2 = new Map;
16194
- assignments.set(sessionId, map2);
16195
- }
16196
- return map2;
16218
+ function getUpsertIndexStatement(db) {
16219
+ let stmt = upsertIndexStatements.get(db);
16220
+ if (!stmt) {
16221
+ stmt = db.prepare("INSERT INTO message_history_index (session_id, last_indexed_ordinal, updated_at) VALUES (?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET last_indexed_ordinal = excluded.last_indexed_ordinal, updated_at = excluded.updated_at");
16222
+ upsertIndexStatements.set(db, stmt);
16197
16223
  }
16198
- function assignTag(sessionId, messageId, type, byteSize, db) {
16199
- const sessionAssignments = getSessionAssignments(sessionId);
16200
- const existing = sessionAssignments.get(messageId);
16201
- if (existing !== undefined) {
16202
- return existing;
16203
- }
16204
- const current = counters.get(sessionId) ?? 0;
16205
- const next = current + 1;
16206
- db.transaction(() => {
16207
- insertTag(db, sessionId, messageId, type, byteSize, next);
16208
- getUpsertCounterStatement(db).run(sessionId, next);
16209
- })();
16210
- counters.set(sessionId, next);
16211
- sessionAssignments.set(messageId, next);
16212
- return next;
16224
+ return stmt;
16225
+ }
16226
+ function getDeleteFtsStatement(db) {
16227
+ let stmt = deleteFtsStatements.get(db);
16228
+ if (!stmt) {
16229
+ stmt = db.prepare("DELETE FROM message_history_fts WHERE session_id = ?");
16230
+ deleteFtsStatements.set(db, stmt);
16213
16231
  }
16214
- function getTag(sessionId, messageId) {
16215
- return assignments.get(sessionId)?.get(messageId);
16232
+ return stmt;
16233
+ }
16234
+ function getDeleteIndexStatement(db) {
16235
+ let stmt = deleteIndexStatements.get(db);
16236
+ if (!stmt) {
16237
+ stmt = db.prepare("DELETE FROM message_history_index WHERE session_id = ?");
16238
+ deleteIndexStatements.set(db, stmt);
16216
16239
  }
16217
- function bindTag(sessionId, messageId, tagNumber) {
16218
- getSessionAssignments(sessionId).set(messageId, tagNumber);
16240
+ return stmt;
16241
+ }
16242
+ function getLastIndexedOrdinal(db, sessionId) {
16243
+ const row = getLastIndexedStatement(db).get(sessionId);
16244
+ return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
16245
+ }
16246
+ function clearIndexedMessages(db, sessionId) {
16247
+ getDeleteFtsStatement(db).run(sessionId);
16248
+ getDeleteIndexStatement(db).run(sessionId);
16249
+ }
16250
+ function getIndexableContent(role, parts) {
16251
+ if (role === "user") {
16252
+ if (!hasMeaningfulUserText(parts)) {
16253
+ return "";
16254
+ }
16255
+ return extractTexts(parts).map(cleanUserText).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
16219
16256
  }
16220
- function getAssignments(sessionId) {
16221
- return getSessionAssignments(sessionId);
16257
+ if (role === "assistant") {
16258
+ return extractTexts(parts).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
16222
16259
  }
16223
- function resetCounter(sessionId, db) {
16224
- counters.set(sessionId, 0);
16225
- assignments.delete(sessionId);
16226
- getUpsertCounterStatement(db).run(sessionId, 0);
16260
+ return "";
16261
+ }
16262
+ function ensureMessagesIndexed(db, sessionId, readMessages) {
16263
+ const messages = readMessages(sessionId);
16264
+ if (messages.length === 0) {
16265
+ db.transaction(() => clearIndexedMessages(db, sessionId))();
16266
+ return;
16227
16267
  }
16228
- function getCounter(sessionId) {
16229
- return counters.get(sessionId) ?? 0;
16268
+ let lastIndexedOrdinal = getLastIndexedOrdinal(db, sessionId);
16269
+ if (lastIndexedOrdinal > messages.length) {
16270
+ db.transaction(() => clearIndexedMessages(db, sessionId))();
16271
+ lastIndexedOrdinal = 0;
16230
16272
  }
16231
- function initFromDb(sessionId, db) {
16232
- if (counters.has(sessionId)) {
16233
- return;
16234
- }
16235
- const row = db.prepare(GET_COUNTER_SQL).get(sessionId);
16236
- const assignmentRows = db.prepare(GET_ASSIGNMENTS_SQL).all(sessionId).filter(isAssignmentRow);
16237
- const sessionAssignments = getSessionAssignments(sessionId);
16238
- sessionAssignments.clear();
16239
- let maxTagNumber = 0;
16240
- for (const assignment of assignmentRows) {
16241
- sessionAssignments.set(assignment.message_id, assignment.tag_number);
16242
- if (assignment.tag_number > maxTagNumber) {
16243
- maxTagNumber = assignment.tag_number;
16244
- }
16245
- }
16246
- const counter = Math.max(row?.counter ?? 0, maxTagNumber);
16247
- counters.set(sessionId, counter);
16248
- }
16249
- function cleanup(sessionId) {
16250
- counters.delete(sessionId);
16251
- assignments.delete(sessionId);
16273
+ if (lastIndexedOrdinal >= messages.length) {
16274
+ return;
16252
16275
  }
16253
- return {
16254
- assignTag,
16255
- getTag,
16256
- bindTag,
16257
- getAssignments,
16258
- resetCounter,
16259
- getCounter,
16260
- initFromDb,
16261
- cleanup
16262
- };
16276
+ const messagesToInsert = messages.filter((message) => message.ordinal > lastIndexedOrdinal).filter((message) => message.role === "user" || message.role === "assistant").map((message) => ({
16277
+ ordinal: message.ordinal,
16278
+ id: message.id,
16279
+ role: message.role,
16280
+ content: getIndexableContent(message.role, message.parts)
16281
+ })).filter((message) => message.content.length > 0);
16282
+ const now = Date.now();
16283
+ db.transaction(() => {
16284
+ const insertMessage = getInsertMessageStatement(db);
16285
+ for (const message of messagesToInsert) {
16286
+ insertMessage.run(sessionId, message.ordinal, message.id, message.role, message.content);
16287
+ }
16288
+ getUpsertIndexStatement(db).run(sessionId, messages.length, now);
16289
+ })();
16263
16290
  }
16264
16291
 
16265
- // src/features/magic-context/memory/project-identity.ts
16266
- import { execSync } from "child_process";
16267
- import path3 from "path";
16268
- var GIT_TIMEOUT_MS = 5000;
16269
- var resolvedCache = new Map;
16270
- function getRootCommitHash(directory) {
16271
- try {
16272
- const hash2 = execSync("git rev-list --max-parents=0 HEAD", {
16273
- cwd: directory,
16274
- encoding: "utf-8",
16275
- stdio: ["pipe", "pipe", "pipe"],
16276
- timeout: GIT_TIMEOUT_MS
16277
- }).trim();
16278
- const firstLine = hash2.split(`
16279
- `)[0]?.trim();
16280
- return firstLine && firstLine.length >= 7 ? firstLine : undefined;
16281
- } catch {
16292
+ // src/features/magic-context/storage-meta-session.ts
16293
+ function getOrCreateSessionMeta(db, sessionId) {
16294
+ const result = db.prepare("SELECT session_id, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash FROM session_meta WHERE session_id = ?").get(sessionId);
16295
+ if (isSessionMetaRow(result)) {
16296
+ return toSessionMeta(result);
16297
+ }
16298
+ const defaults = getDefaultSessionMeta(sessionId);
16299
+ ensureSessionMetaRow(db, sessionId);
16300
+ return defaults;
16301
+ }
16302
+ function updateSessionMeta(db, sessionId, updates) {
16303
+ const setClauses = [];
16304
+ const values = [];
16305
+ for (const [key, column] of Object.entries(META_COLUMNS)) {
16306
+ const value = updates[key];
16307
+ if (value === undefined)
16308
+ continue;
16309
+ if (value === null) {
16310
+ setClauses.push(`${column} = ?`);
16311
+ values.push("");
16312
+ } else if (BOOLEAN_META_KEYS.has(key)) {
16313
+ setClauses.push(`${column} = ?`);
16314
+ values.push(value ? 1 : 0);
16315
+ } else if (typeof value === "string" || typeof value === "number") {
16316
+ setClauses.push(`${column} = ?`);
16317
+ values.push(value);
16318
+ }
16319
+ }
16320
+ if (setClauses.length === 0) {
16282
16321
  return;
16283
16322
  }
16323
+ db.transaction(() => {
16324
+ ensureSessionMetaRow(db, sessionId);
16325
+ db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")} WHERE session_id = ?`).run(...values, sessionId);
16326
+ })();
16284
16327
  }
16285
- function directoryFallback(directory) {
16286
- const canonical = path3.resolve(directory);
16287
- const hash2 = Bun.hash(canonical).toString(16).slice(0, 12);
16288
- return `dir:${hash2}`;
16328
+ function clearSession(db, sessionId) {
16329
+ db.transaction(() => {
16330
+ db.prepare("DELETE FROM pending_ops WHERE session_id = ?").run(sessionId);
16331
+ db.prepare("DELETE FROM source_contents WHERE session_id = ?").run(sessionId);
16332
+ db.prepare("DELETE FROM tags WHERE session_id = ?").run(sessionId);
16333
+ db.prepare("DELETE FROM session_meta WHERE session_id = ?").run(sessionId);
16334
+ db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
16335
+ db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
16336
+ db.prepare("DELETE FROM session_notes WHERE session_id = ?").run(sessionId);
16337
+ db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
16338
+ db.prepare("DELETE FROM recomp_facts WHERE session_id = ?").run(sessionId);
16339
+ clearIndexedMessages(db, sessionId);
16340
+ })();
16289
16341
  }
16290
- function resolveProjectIdentity(directory) {
16291
- const resolved = path3.resolve(directory);
16292
- const cached2 = resolvedCache.get(resolved);
16293
- if (cached2 !== undefined) {
16294
- return cached2;
16295
- }
16296
- const rootHash = getRootCommitHash(resolved);
16297
- const identity = rootHash ? `git:${rootHash}` : directoryFallback(resolved);
16298
- resolvedCache.set(resolved, identity);
16299
- return identity;
16342
+ // src/features/magic-context/storage-notes.ts
16343
+ function isSessionNoteRow(row) {
16344
+ if (row === null || typeof row !== "object")
16345
+ return false;
16346
+ const candidate = row;
16347
+ return typeof candidate.id === "number" && typeof candidate.session_id === "string" && typeof candidate.content === "string" && typeof candidate.created_at === "number";
16300
16348
  }
16301
-
16302
- // src/hooks/magic-context/execute-flush.ts
16303
- function executeFlush(db, sessionId) {
16304
- try {
16305
- const pendingOps = getPendingOps(db, sessionId);
16306
- if (pendingOps.length === 0) {
16307
- return "No pending operations to flush.";
16308
- }
16309
- let dropped = 0;
16310
- db.transaction(() => {
16311
- for (const op of pendingOps) {
16312
- updateTagStatus(db, sessionId, op.tagId, "dropped");
16313
- removePendingOp(db, sessionId, op.tagId);
16314
- dropped++;
16315
- }
16316
- })();
16317
- const parts = [];
16318
- if (dropped > 0)
16319
- parts.push(`${dropped} dropped`);
16320
- if (dropped > 0) {
16321
- clearPersistedStickyTurnReminder(db, sessionId);
16322
- }
16323
- return `Flushed: ${parts.join(", ")}. Changes take effect on next message.`;
16324
- } catch (error48) {
16325
- sessionLog(sessionId, "ctx-flush failed:", error48);
16326
- return `Error: Failed to flush context operations. ${getErrorMessage(error48)}`;
16327
- }
16349
+ function toSessionNote(row) {
16350
+ return {
16351
+ id: row.id,
16352
+ sessionId: row.session_id,
16353
+ content: row.content,
16354
+ createdAt: row.created_at
16355
+ };
16328
16356
  }
16329
-
16330
- // src/shared/internal-initiator-marker.ts
16331
- var OMO_INTERNAL_INITIATOR_MARKER = "<!-- OMO_INTERNAL_INITIATOR -->";
16332
-
16333
- // src/shared/system-directive.ts
16334
- var SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: MAGIC-CONTEXT";
16335
- function isSystemDirective(text) {
16336
- return text.trimStart().startsWith(SYSTEM_DIRECTIVE_PREFIX);
16357
+ function getSessionNotes(db, sessionId) {
16358
+ const rows = db.prepare("SELECT * FROM session_notes WHERE session_id = ? ORDER BY id ASC").all(sessionId).filter(isSessionNoteRow);
16359
+ return rows.map(toSessionNote);
16337
16360
  }
16338
- function removeSystemReminders(text) {
16339
- return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, "").trim();
16361
+ function addSessionNote(db, sessionId, content) {
16362
+ db.prepare("INSERT INTO session_notes (session_id, content, created_at) VALUES (?, ?, ?)").run(sessionId, content, Date.now());
16340
16363
  }
16341
-
16342
- // src/hooks/magic-context/read-session-db.ts
16343
- import { Database as Database2 } from "bun:sqlite";
16344
- import { join as join6 } from "path";
16345
- function getOpenCodeDbPath() {
16346
- return join6(getDataDir(), "opencode", "opencode.db");
16364
+ function clearSessionNotes(db, sessionId) {
16365
+ db.prepare("DELETE FROM session_notes WHERE session_id = ?").run(sessionId);
16347
16366
  }
16348
- var cachedReadOnlyDb = null;
16349
- function closeCachedReadOnlyDb() {
16350
- if (!cachedReadOnlyDb) {
16351
- return;
16352
- }
16353
- try {
16354
- cachedReadOnlyDb.db.close(false);
16355
- } catch (error48) {
16356
- log("[magic-context] failed to close cached OpenCode read-only DB:", error48);
16357
- } finally {
16358
- cachedReadOnlyDb = null;
16367
+ // src/features/magic-context/storage-ops.ts
16368
+ var queuePendingOpStatements = new WeakMap;
16369
+ var getPendingOpsStatements = new WeakMap;
16370
+ var clearPendingOpsStatements = new WeakMap;
16371
+ var removePendingOpStatements = new WeakMap;
16372
+ function getQueuePendingOpStatement(db) {
16373
+ let stmt = queuePendingOpStatements.get(db);
16374
+ if (!stmt) {
16375
+ stmt = db.prepare("INSERT INTO pending_ops (session_id, tag_id, operation, queued_at) VALUES (?, ?, ?, ?)");
16376
+ queuePendingOpStatements.set(db, stmt);
16359
16377
  }
16378
+ return stmt;
16360
16379
  }
16361
- function getReadOnlySessionDb() {
16362
- const dbPath = getOpenCodeDbPath();
16363
- if (cachedReadOnlyDb?.path === dbPath) {
16364
- return cachedReadOnlyDb.db;
16380
+ function getPendingOpsStatement(db) {
16381
+ let stmt = getPendingOpsStatements.get(db);
16382
+ if (!stmt) {
16383
+ stmt = db.prepare("SELECT id, session_id, tag_id, operation, queued_at FROM pending_ops WHERE session_id = ? ORDER BY queued_at ASC, id ASC");
16384
+ getPendingOpsStatements.set(db, stmt);
16365
16385
  }
16366
- closeCachedReadOnlyDb();
16367
- const db = new Database2(dbPath, { readonly: true });
16368
- cachedReadOnlyDb = { path: dbPath, db };
16369
- return db;
16386
+ return stmt;
16370
16387
  }
16371
- function withReadOnlySessionDb(fn) {
16372
- return fn(getReadOnlySessionDb());
16388
+ function getRemovePendingOpStatement(db) {
16389
+ let stmt = removePendingOpStatements.get(db);
16390
+ if (!stmt) {
16391
+ stmt = db.prepare("DELETE FROM pending_ops WHERE session_id = ? AND tag_id = ?");
16392
+ removePendingOpStatements.set(db, stmt);
16393
+ }
16394
+ return stmt;
16373
16395
  }
16374
- function getRawSessionMessageCountFromDb(db, sessionId) {
16375
- const row = db.prepare("SELECT COUNT(*) as count FROM message WHERE session_id = ?").get(sessionId);
16376
- return typeof row?.count === "number" ? row.count : 0;
16396
+ function isPendingOpRow(row) {
16397
+ if (row === null || typeof row !== "object")
16398
+ return false;
16399
+ const r = row;
16400
+ return typeof r.id === "number" && typeof r.session_id === "string" && typeof r.tag_id === "number" && typeof r.operation === "string" && typeof r.queued_at === "number";
16377
16401
  }
16378
-
16379
- // src/hooks/magic-context/read-session-formatting.ts
16380
- var COMMIT_HASH_PATTERN = /`?\b([0-9a-f]{6,12})\b`?/gi;
16381
- var COMMIT_HINT_PATTERN = /\b(commit(?:ted)?|cherry-?pick(?:ed)?|hash(?:es)?|sha)\b/i;
16382
- var MAX_COMMITS_PER_BLOCK = 5;
16383
- function hasMeaningfulUserText(parts) {
16384
- for (const part of parts) {
16385
- if (part === null || typeof part !== "object")
16386
- continue;
16387
- const candidate = part;
16388
- if (candidate.type !== "text" || typeof candidate.text !== "string")
16389
- continue;
16390
- if (candidate.ignored === true)
16391
- continue;
16392
- const cleaned = removeSystemReminders(candidate.text).replace(OMO_INTERNAL_INITIATOR_MARKER, "").trim();
16393
- if (!cleaned)
16394
- continue;
16395
- if (isSystemDirective(cleaned))
16396
- continue;
16397
- return true;
16402
+ function toPendingOp(row) {
16403
+ if (row.operation !== "drop") {
16404
+ sessionLog(row.session_id, `unknown pending operation "${row.operation}"; ignoring`);
16405
+ return null;
16398
16406
  }
16399
- return false;
16407
+ return {
16408
+ id: row.id,
16409
+ sessionId: row.session_id,
16410
+ tagId: row.tag_id,
16411
+ operation: row.operation,
16412
+ queuedAt: row.queued_at
16413
+ };
16400
16414
  }
16401
- function extractTexts(parts) {
16402
- const texts = [];
16403
- for (const part of parts) {
16404
- if (part === null || typeof part !== "object")
16405
- continue;
16406
- const p = part;
16407
- if (p.type === "text" && typeof p.text === "string" && p.text.trim().length > 0) {
16408
- texts.push(p.text.trim());
16409
- }
16410
- }
16411
- return texts;
16415
+ function queuePendingOp(db, sessionId, tagId, operation, queuedAt = Date.now()) {
16416
+ getQueuePendingOpStatement(db).run(sessionId, tagId, operation, queuedAt);
16412
16417
  }
16413
- function estimateTokens(text) {
16414
- return Math.ceil(text.length / 3.5);
16418
+ function getPendingOps(db, sessionId) {
16419
+ const rows = getPendingOpsStatement(db).all(sessionId).filter(isPendingOpRow);
16420
+ return rows.map(toPendingOp).filter((op) => op !== null);
16415
16421
  }
16416
- function normalizeText(text) {
16417
- return text.replace(/\s+/g, " ").trim();
16422
+ function removePendingOp(db, sessionId, tagId) {
16423
+ getRemovePendingOpStatement(db).run(sessionId, tagId);
16418
16424
  }
16419
- function compactRole(role) {
16420
- if (role === "assistant")
16421
- return "A";
16422
- if (role === "user")
16423
- return "U";
16424
- return role.slice(0, 1).toUpperCase() || "M";
16425
+ // src/features/magic-context/storage-source.ts
16426
+ function isSourceContentRow(row) {
16427
+ if (row === null || typeof row !== "object")
16428
+ return false;
16429
+ const r = row;
16430
+ return typeof r.tag_id === "number" && typeof r.content === "string";
16425
16431
  }
16426
- function formatBlock(block) {
16427
- const range = block.startOrdinal === block.endOrdinal ? `[${block.startOrdinal}]` : `[${block.startOrdinal}-${block.endOrdinal}]`;
16428
- const commitSuffix = block.commitHashes.length > 0 ? ` commits: ${block.commitHashes.join(", ")}` : "";
16429
- return `${range} ${block.role}:${commitSuffix} ${block.parts.join(" / ")}`;
16432
+ function saveSourceContent(db, sessionId, tagId, content) {
16433
+ db.prepare("INSERT OR IGNORE INTO source_contents (tag_id, session_id, content, created_at) VALUES (?, ?, ?, ?)").run(tagId, sessionId, content, Date.now());
16430
16434
  }
16431
- function extractCommitHashes(text) {
16432
- const hashes = [];
16433
- const seen = new Set;
16434
- for (const match of text.matchAll(COMMIT_HASH_PATTERN)) {
16435
- const hash2 = match[1]?.toLowerCase();
16436
- if (!hash2 || seen.has(hash2))
16437
- continue;
16438
- seen.add(hash2);
16439
- hashes.push(hash2);
16440
- if (hashes.length >= MAX_COMMITS_PER_BLOCK)
16441
- break;
16435
+ function replaceSourceContent(db, sessionId, tagId, content) {
16436
+ db.prepare(`INSERT INTO source_contents (tag_id, session_id, content, created_at)
16437
+ VALUES (?, ?, ?, ?)
16438
+ ON CONFLICT(session_id, tag_id)
16439
+ DO UPDATE SET content = excluded.content, created_at = excluded.created_at`).run(tagId, sessionId, content, Date.now());
16440
+ }
16441
+ function getSourceContents(db, sessionId, tagIds) {
16442
+ if (tagIds.length === 0) {
16443
+ return new Map;
16442
16444
  }
16443
- return hashes;
16445
+ const placeholders = tagIds.map(() => "?").join(", ");
16446
+ const rows = db.prepare(`SELECT tag_id, content FROM source_contents WHERE session_id = ? AND tag_id IN (${placeholders})`).all(sessionId, ...tagIds).filter(isSourceContentRow);
16447
+ const sources = new Map;
16448
+ for (const row of rows) {
16449
+ sources.set(row.tag_id, row.content);
16450
+ }
16451
+ return sources;
16444
16452
  }
16445
- function compactTextForSummary(text, role) {
16446
- const commitHashes = role === "assistant" ? extractCommitHashes(text) : [];
16447
- if (commitHashes.length === 0 || !COMMIT_HINT_PATTERN.test(text)) {
16448
- return { text, commitHashes };
16453
+ // src/features/magic-context/storage-tags.ts
16454
+ var insertTagStatements = new WeakMap;
16455
+ var updateTagStatusStatements = new WeakMap;
16456
+ var updateTagMessageIdStatements = new WeakMap;
16457
+ function getInsertTagStatement(db) {
16458
+ let stmt = insertTagStatements.get(db);
16459
+ if (!stmt) {
16460
+ stmt = db.prepare("INSERT INTO tags (session_id, message_id, type, byte_size, tag_number) VALUES (?, ?, ?, ?, ?)");
16461
+ insertTagStatements.set(db, stmt);
16449
16462
  }
16450
- const withoutHashes = text.replace(COMMIT_HASH_PATTERN, "").replace(/\(\s*\)/g, "").replace(/\s+,/g, ",").replace(/,\s*,+/g, ", ").replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1").trim();
16451
- return {
16452
- text: withoutHashes.length > 0 ? withoutHashes : text,
16453
- commitHashes
16454
- };
16463
+ return stmt;
16455
16464
  }
16456
- function mergeCommitHashes(existing, next) {
16457
- if (next.length === 0)
16458
- return existing;
16459
- const merged = [...existing];
16460
- for (const hash2 of next) {
16461
- if (merged.includes(hash2))
16462
- continue;
16463
- merged.push(hash2);
16464
- if (merged.length >= MAX_COMMITS_PER_BLOCK)
16465
- break;
16465
+ function getUpdateTagStatusStatement(db) {
16466
+ let stmt = updateTagStatusStatements.get(db);
16467
+ if (!stmt) {
16468
+ stmt = db.prepare("UPDATE tags SET status = ? WHERE session_id = ? AND tag_number = ?");
16469
+ updateTagStatusStatements.set(db, stmt);
16466
16470
  }
16467
- return merged;
16471
+ return stmt;
16468
16472
  }
16469
-
16470
- // src/hooks/magic-context/read-session-raw.ts
16471
- function isRawMessageRow(row) {
16472
- if (row === null || typeof row !== "object")
16473
- return false;
16474
- const candidate = row;
16475
- return typeof candidate.id === "string" && typeof candidate.data === "string";
16473
+ function getUpdateTagMessageIdStatement(db) {
16474
+ let stmt = updateTagMessageIdStatements.get(db);
16475
+ if (!stmt) {
16476
+ stmt = db.prepare("UPDATE tags SET message_id = ? WHERE session_id = ? AND tag_number = ?");
16477
+ updateTagMessageIdStatements.set(db, stmt);
16478
+ }
16479
+ return stmt;
16476
16480
  }
16477
- function isRawPartRow(row) {
16481
+ function isTagRow(row) {
16478
16482
  if (row === null || typeof row !== "object")
16479
16483
  return false;
16480
- const candidate = row;
16481
- return typeof candidate.message_id === "string" && typeof candidate.data === "string";
16484
+ const r = row;
16485
+ return typeof r.id === "number" && typeof r.message_id === "string" && typeof r.type === "string" && typeof r.status === "string" && typeof r.byte_size === "number" && typeof r.session_id === "string" && typeof r.tag_number === "number";
16482
16486
  }
16483
- function parseJsonRecord(value) {
16484
- const parsed = JSON.parse(value);
16485
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
16486
- throw new Error("Expected JSON object");
16487
- }
16488
- return parsed;
16487
+ function toTagEntry(row) {
16488
+ const type = row.type === "tool" ? "tool" : row.type === "file" ? "file" : "message";
16489
+ const status = row.status === "dropped" || row.status === "compacted" ? row.status : "active";
16490
+ return {
16491
+ tagNumber: row.tag_number,
16492
+ messageId: row.message_id,
16493
+ type,
16494
+ status,
16495
+ byteSize: row.byte_size,
16496
+ sessionId: row.session_id
16497
+ };
16489
16498
  }
16490
- function parseJsonUnknown(value) {
16491
- return JSON.parse(value);
16499
+ function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber) {
16500
+ getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, tagNumber);
16501
+ return tagNumber;
16492
16502
  }
16493
- function readRawSessionMessagesFromDb(db, sessionId) {
16494
- const messageRows = db.prepare("SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
16495
- const partRows = db.prepare("SELECT message_id, data FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
16496
- const partsByMessageId = new Map;
16497
- for (const part of partRows) {
16498
- const list = partsByMessageId.get(part.message_id) ?? [];
16499
- list.push(parseJsonUnknown(part.data));
16500
- partsByMessageId.set(part.message_id, list);
16503
+ function updateTagStatus(db, sessionId, tagId, status) {
16504
+ getUpdateTagStatusStatement(db).run(status, sessionId, tagId);
16505
+ }
16506
+ function updateTagMessageId(db, sessionId, tagId, messageId) {
16507
+ getUpdateTagMessageIdStatement(db).run(messageId, sessionId, tagId);
16508
+ }
16509
+ function getTagsBySession(db, sessionId) {
16510
+ const rows = db.prepare("SELECT id, message_id, type, status, byte_size, session_id, tag_number FROM tags WHERE session_id = ? ORDER BY tag_number ASC, id ASC").all(sessionId).filter(isTagRow);
16511
+ return rows.map(toTagEntry);
16512
+ }
16513
+ function getTopNBySize(db, sessionId, n) {
16514
+ if (n <= 0) {
16515
+ return [];
16501
16516
  }
16502
- return messageRows.map((row, index) => {
16503
- const info = parseJsonRecord(row.data);
16504
- const role = typeof info.role === "string" ? info.role : "unknown";
16505
- return {
16506
- ordinal: index + 1,
16507
- id: row.id,
16508
- role,
16509
- parts: partsByMessageId.get(row.id) ?? []
16510
- };
16511
- });
16517
+ const rows = db.prepare("SELECT id, message_id, type, status, byte_size, session_id, tag_number FROM tags WHERE session_id = ? AND status = 'active' ORDER BY byte_size DESC, tag_number ASC LIMIT ?").all(sessionId, n).filter(isTagRow);
16518
+ return rows.map(toTagEntry);
16512
16519
  }
16513
-
16514
- // src/hooks/magic-context/tag-content-primitives.ts
16515
- var encoder = new TextEncoder;
16516
- var TAG_PREFIX_REGEX = /^\u00A7\d+\u00A7\s*/;
16517
- function byteSize(value) {
16518
- return encoder.encode(value).length;
16520
+ // src/plugin/dream-timer.ts
16521
+ var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
16522
+ function startDreamScheduleTimer(args) {
16523
+ const { client, dreamerConfig } = args;
16524
+ if (!dreamerConfig.enabled || !dreamerConfig.schedule?.trim()) {
16525
+ return;
16526
+ }
16527
+ const timer = setInterval(() => {
16528
+ try {
16529
+ const db = openDatabase();
16530
+ checkScheduleAndEnqueue(db, dreamerConfig.schedule);
16531
+ processDreamQueue({
16532
+ db,
16533
+ client,
16534
+ tasks: dreamerConfig.tasks,
16535
+ taskTimeoutMinutes: dreamerConfig.task_timeout_minutes,
16536
+ maxRuntimeMinutes: dreamerConfig.max_runtime_minutes
16537
+ }).catch((error48) => {
16538
+ log("[dreamer] timer-triggered queue processing failed:", error48);
16539
+ });
16540
+ } catch (error48) {
16541
+ log("[dreamer] timer-triggered schedule check failed:", error48);
16542
+ }
16543
+ }, DREAM_TIMER_INTERVAL_MS);
16544
+ if (typeof timer === "object" && "unref" in timer) {
16545
+ timer.unref();
16546
+ }
16547
+ log(`[dreamer] started independent schedule timer (every ${DREAM_TIMER_INTERVAL_MS / 60000}m)`);
16548
+ return () => {
16549
+ clearInterval(timer);
16550
+ log("[dreamer] stopped dream schedule timer");
16551
+ };
16519
16552
  }
16520
- function stripTagPrefix(value) {
16521
- return value.replace(TAG_PREFIX_REGEX, "");
16553
+
16554
+ // src/plugin/event.ts
16555
+ function createEventHandler(args) {
16556
+ return async (input) => {
16557
+ await args.magicContext?.event?.(input);
16558
+ };
16522
16559
  }
16523
- function prependTag(tagId, value) {
16524
- const stripped = stripTagPrefix(value);
16525
- return `\xA7${tagId}\xA7 ${stripped}`;
16560
+
16561
+ // src/features/magic-context/compaction.ts
16562
+ function createCompactionHandler() {
16563
+ return {
16564
+ onCompacted(sessionId, db) {
16565
+ db.transaction(() => {
16566
+ db.prepare("UPDATE tags SET status = 'compacted' WHERE session_id = ? AND status IN ('active', 'dropped')").run(sessionId);
16567
+ db.prepare("DELETE FROM pending_ops WHERE session_id = ?").run(sessionId);
16568
+ updateSessionMeta(db, sessionId, { lastNudgeBand: null });
16569
+ })();
16570
+ }
16571
+ };
16526
16572
  }
16527
- function isThinkingPart(part) {
16528
- if (part === null || typeof part !== "object")
16529
- return false;
16530
- const candidate = part;
16531
- return candidate.type === "thinking" || candidate.type === "reasoning";
16573
+
16574
+ // src/hooks/is-anthropic-provider.ts
16575
+ function isAnthropicProvider(providerID) {
16576
+ return providerID === "anthropic" || providerID === "google-vertex-anthropic";
16532
16577
  }
16533
16578
 
16534
- // src/hooks/magic-context/tag-part-guards.ts
16535
- function isTextPart(part) {
16536
- if (part === null || typeof part !== "object")
16537
- return false;
16538
- const p = part;
16539
- return p.type === "text" && typeof p.text === "string";
16579
+ // src/hooks/magic-context/event-resolvers.ts
16580
+ var DEFAULT_CONTEXT_LIMIT = 200000;
16581
+ function resolveContextLimit(providerID, modelID, config2) {
16582
+ if (!providerID) {
16583
+ return DEFAULT_CONTEXT_LIMIT;
16584
+ }
16585
+ if (modelID) {
16586
+ const modelSpecific = config2.modelContextLimitsCache?.get(`${providerID}/${modelID}`);
16587
+ if (typeof modelSpecific === "number" && modelSpecific > 0) {
16588
+ return modelSpecific;
16589
+ }
16590
+ }
16591
+ if (isAnthropicProvider(providerID)) {
16592
+ return 1e6;
16593
+ }
16594
+ return DEFAULT_CONTEXT_LIMIT;
16540
16595
  }
16541
- function isToolPartWithOutput(part) {
16542
- if (part === null || typeof part !== "object")
16543
- return false;
16544
- const p = part;
16545
- if (p.type !== "tool" || typeof p.callID !== "string")
16546
- return false;
16547
- if (p.state === null || typeof p.state !== "object")
16548
- return false;
16549
- return typeof p.state.output === "string";
16596
+ function resolveCacheTtl(cacheTtl, modelKey) {
16597
+ if (typeof cacheTtl === "string") {
16598
+ return cacheTtl;
16599
+ }
16600
+ if (modelKey && typeof cacheTtl[modelKey] === "string") {
16601
+ return cacheTtl[modelKey];
16602
+ }
16603
+ if (modelKey) {
16604
+ const bareModelId = modelKey.split("/").slice(1).join("/");
16605
+ if (bareModelId && typeof cacheTtl[bareModelId] === "string") {
16606
+ return cacheTtl[bareModelId];
16607
+ }
16608
+ }
16609
+ return cacheTtl.default ?? "5m";
16550
16610
  }
16551
- function isFilePart(part) {
16552
- if (part === null || typeof part !== "object")
16553
- return false;
16554
- const p = part;
16555
- return p.type === "file" && typeof p.url === "string";
16611
+ function resolveExecuteThreshold(config2, modelKey, fallback) {
16612
+ if (typeof config2 === "number") {
16613
+ return config2;
16614
+ }
16615
+ if (modelKey && typeof config2[modelKey] === "number") {
16616
+ return config2[modelKey];
16617
+ }
16618
+ if (modelKey) {
16619
+ const bareModelId = modelKey.split("/").slice(1).join("/");
16620
+ if (bareModelId && typeof config2[bareModelId] === "number") {
16621
+ return config2[bareModelId];
16622
+ }
16623
+ }
16624
+ return config2.default ?? fallback;
16556
16625
  }
16557
- function buildFileSourceContent(parts) {
16558
- const content = parts.filter(isTextPart).map((part) => stripTagPrefix(part.text)).join(`
16559
- `).trim();
16560
- return content.length > 0 ? content : null;
16626
+ function resolveModelKey(providerID, modelID) {
16627
+ if (!providerID || !modelID) {
16628
+ return;
16629
+ }
16630
+ return `${providerID}/${modelID}`;
16631
+ }
16632
+ function resolveSessionId(properties) {
16633
+ if (typeof properties?.sessionID === "string") {
16634
+ return properties.sessionID;
16635
+ }
16636
+ const info = properties?.info;
16637
+ if (info === null || typeof info !== "object") {
16638
+ return;
16639
+ }
16640
+ const record2 = info;
16641
+ if (typeof record2.sessionID === "string") {
16642
+ return record2.sessionID;
16643
+ }
16644
+ if (typeof record2.id === "string") {
16645
+ return record2.id;
16646
+ }
16647
+ return;
16561
16648
  }
16562
16649
 
16563
- // src/hooks/magic-context/read-session-chunk.ts
16564
- function cleanUserText(text) {
16565
- return removeSystemReminders(text).replace(OMO_INTERNAL_INITIATOR_MARKER, "").trim();
16650
+ // src/features/magic-context/scheduler.ts
16651
+ var TTL_PATTERN = /^(\d+)([smh])$/;
16652
+ var NUMERIC_PATTERN = /^\d+$/;
16653
+ var UNIT_TO_MS = {
16654
+ s: 1000,
16655
+ m: 60 * 1000,
16656
+ h: 60 * 60 * 1000
16657
+ };
16658
+ function parseCacheTtl(ttl) {
16659
+ const normalizedTtl = ttl.trim();
16660
+ if (NUMERIC_PATTERN.test(normalizedTtl)) {
16661
+ return Number(normalizedTtl);
16662
+ }
16663
+ const match = normalizedTtl.match(TTL_PATTERN);
16664
+ if (!match) {
16665
+ throw new Error(`Invalid cache TTL format: ${ttl}`);
16666
+ }
16667
+ const value = Number(match[1]);
16668
+ const unit = match[2];
16669
+ return value * UNIT_TO_MS[unit];
16566
16670
  }
16567
- function readRawSessionMessages(sessionId) {
16568
- return withReadOnlySessionDb((db) => readRawSessionMessagesFromDb(db, sessionId));
16671
+ function createScheduler(config2) {
16672
+ return {
16673
+ shouldExecute(sessionMeta, contextUsage, currentTime = Date.now(), sessionId, modelKey) {
16674
+ const threshold = resolveExecuteThreshold(config2.executeThresholdPercentage, modelKey, 65);
16675
+ if (contextUsage.percentage >= threshold) {
16676
+ return "execute";
16677
+ }
16678
+ let ttlMs;
16679
+ try {
16680
+ ttlMs = parseCacheTtl(sessionMeta.cacheTtl);
16681
+ } catch (error48) {
16682
+ if (sessionId) {
16683
+ sessionLog(sessionId, `invalid cache_ttl "${sessionMeta.cacheTtl}"; falling back to default 5m`, error48);
16684
+ } else {
16685
+ log(`[magic-context] invalid cache_ttl "${sessionMeta.cacheTtl}"; falling back to default 5m`, error48);
16686
+ }
16687
+ ttlMs = parseCacheTtl("5m");
16688
+ }
16689
+ const elapsedTime = currentTime - sessionMeta.lastResponseTime;
16690
+ if (elapsedTime > ttlMs) {
16691
+ return "execute";
16692
+ }
16693
+ return "defer";
16694
+ }
16695
+ };
16569
16696
  }
16570
- function getRawSessionMessageCount(sessionId) {
16571
- return withReadOnlySessionDb((db) => getRawSessionMessageCountFromDb(db, sessionId));
16697
+
16698
+ // src/features/magic-context/tagger.ts
16699
+ var GET_COUNTER_SQL = `SELECT counter FROM session_meta WHERE session_id = ?`;
16700
+ var GET_ASSIGNMENTS_SQL = "SELECT message_id, tag_number FROM tags WHERE session_id = ? ORDER BY tag_number ASC";
16701
+ function isAssignmentRow(row) {
16702
+ if (row === null || typeof row !== "object") {
16703
+ return false;
16704
+ }
16705
+ const candidate = row;
16706
+ return typeof candidate.message_id === "string" && typeof candidate.tag_number === "number";
16572
16707
  }
16573
- function getRawSessionTagKeysThrough(sessionId, upToMessageIndex) {
16574
- return withReadOnlySessionDb((db) => {
16575
- const messages = readRawSessionMessagesFromDb(db, sessionId);
16576
- const keys = [];
16577
- for (const message of messages) {
16578
- if (message.ordinal > upToMessageIndex)
16579
- break;
16580
- for (const [partIndex, part] of message.parts.entries()) {
16581
- if (isTextPart(part)) {
16582
- keys.push(`${message.id}:p${partIndex}`);
16583
- }
16584
- if (isFilePart(part)) {
16585
- keys.push(`${message.id}:file${partIndex}`);
16586
- }
16587
- if (isToolPartWithOutput(part)) {
16588
- keys.push(part.callID);
16589
- }
16708
+ var UPSERT_COUNTER_SQL = `
16709
+ INSERT INTO session_meta (session_id, counter)
16710
+ VALUES (?, ?)
16711
+ ON CONFLICT(session_id) DO UPDATE SET counter = excluded.counter
16712
+ `;
16713
+ var upsertCounterStatements = new WeakMap;
16714
+ function getUpsertCounterStatement(db) {
16715
+ let stmt = upsertCounterStatements.get(db);
16716
+ if (!stmt) {
16717
+ stmt = db.prepare(UPSERT_COUNTER_SQL);
16718
+ upsertCounterStatements.set(db, stmt);
16719
+ }
16720
+ return stmt;
16721
+ }
16722
+ function createTagger() {
16723
+ const counters = new Map;
16724
+ const assignments = new Map;
16725
+ function getSessionAssignments(sessionId) {
16726
+ let map2 = assignments.get(sessionId);
16727
+ if (!map2) {
16728
+ map2 = new Map;
16729
+ assignments.set(sessionId, map2);
16730
+ }
16731
+ return map2;
16732
+ }
16733
+ function assignTag(sessionId, messageId, type, byteSize2, db) {
16734
+ const sessionAssignments = getSessionAssignments(sessionId);
16735
+ const existing = sessionAssignments.get(messageId);
16736
+ if (existing !== undefined) {
16737
+ return existing;
16738
+ }
16739
+ const current = counters.get(sessionId) ?? 0;
16740
+ const next = current + 1;
16741
+ db.transaction(() => {
16742
+ insertTag(db, sessionId, messageId, type, byteSize2, next);
16743
+ getUpsertCounterStatement(db).run(sessionId, next);
16744
+ })();
16745
+ counters.set(sessionId, next);
16746
+ sessionAssignments.set(messageId, next);
16747
+ return next;
16748
+ }
16749
+ function getTag(sessionId, messageId) {
16750
+ return assignments.get(sessionId)?.get(messageId);
16751
+ }
16752
+ function bindTag(sessionId, messageId, tagNumber) {
16753
+ getSessionAssignments(sessionId).set(messageId, tagNumber);
16754
+ }
16755
+ function getAssignments(sessionId) {
16756
+ return getSessionAssignments(sessionId);
16757
+ }
16758
+ function resetCounter(sessionId, db) {
16759
+ counters.set(sessionId, 0);
16760
+ assignments.delete(sessionId);
16761
+ getUpsertCounterStatement(db).run(sessionId, 0);
16762
+ }
16763
+ function getCounter(sessionId) {
16764
+ return counters.get(sessionId) ?? 0;
16765
+ }
16766
+ function initFromDb(sessionId, db) {
16767
+ if (counters.has(sessionId)) {
16768
+ return;
16769
+ }
16770
+ const row = db.prepare(GET_COUNTER_SQL).get(sessionId);
16771
+ const assignmentRows = db.prepare(GET_ASSIGNMENTS_SQL).all(sessionId).filter(isAssignmentRow);
16772
+ const sessionAssignments = getSessionAssignments(sessionId);
16773
+ sessionAssignments.clear();
16774
+ let maxTagNumber = 0;
16775
+ for (const assignment of assignmentRows) {
16776
+ sessionAssignments.set(assignment.message_id, assignment.tag_number);
16777
+ if (assignment.tag_number > maxTagNumber) {
16778
+ maxTagNumber = assignment.tag_number;
16590
16779
  }
16591
16780
  }
16592
- return keys;
16593
- });
16781
+ const counter = Math.max(row?.counter ?? 0, maxTagNumber);
16782
+ counters.set(sessionId, counter);
16783
+ }
16784
+ function cleanup(sessionId) {
16785
+ counters.delete(sessionId);
16786
+ assignments.delete(sessionId);
16787
+ }
16788
+ return {
16789
+ assignTag,
16790
+ getTag,
16791
+ bindTag,
16792
+ getAssignments,
16793
+ resetCounter,
16794
+ getCounter,
16795
+ initFromDb,
16796
+ cleanup
16797
+ };
16594
16798
  }
16595
- var PROTECTED_TAIL_USER_TURNS = 5;
16596
- function getProtectedTailStartOrdinal(sessionId) {
16597
- return withReadOnlySessionDb((db) => {
16598
- const messages = readRawSessionMessagesFromDb(db, sessionId);
16599
- const userOrdinals = messages.filter((m) => m.role === "user" && hasMeaningfulUserText(m.parts)).map((m) => m.ordinal);
16600
- if (userOrdinals.length < PROTECTED_TAIL_USER_TURNS) {
16601
- return 1;
16602
- }
16603
- return userOrdinals[userOrdinals.length - PROTECTED_TAIL_USER_TURNS];
16604
- });
16799
+
16800
+ // src/features/magic-context/memory/project-identity.ts
16801
+ import { execSync } from "child_process";
16802
+ import path3 from "path";
16803
+ var GIT_TIMEOUT_MS = 5000;
16804
+ var resolvedCache = new Map;
16805
+ function getRootCommitHash(directory) {
16806
+ try {
16807
+ const hash2 = execSync("git rev-list --max-parents=0 HEAD", {
16808
+ cwd: directory,
16809
+ encoding: "utf-8",
16810
+ stdio: ["pipe", "pipe", "pipe"],
16811
+ timeout: GIT_TIMEOUT_MS
16812
+ }).trim();
16813
+ const firstLine = hash2.split(`
16814
+ `)[0]?.trim();
16815
+ return firstLine && firstLine.length >= 7 ? firstLine : undefined;
16816
+ } catch {
16817
+ return;
16818
+ }
16605
16819
  }
16606
- function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal) {
16607
- const messages = readRawSessionMessages(sessionId);
16608
- const startOrdinal = Math.max(1, offset);
16609
- const lines = [];
16610
- const lineMeta = [];
16611
- let totalTokens = 0;
16612
- let messagesProcessed = 0;
16613
- let lastOrdinal = startOrdinal - 1;
16614
- let lastMessageId = "";
16615
- let firstMessageId = "";
16616
- let currentBlock = null;
16617
- let pendingNoiseMeta = [];
16618
- let commitClusters = 0;
16619
- let lastFlushedRole = "";
16620
- function flushCurrentBlock() {
16621
- if (!currentBlock)
16622
- return true;
16623
- const blockText = formatBlock(currentBlock);
16624
- const blockTokens = estimateTokens(blockText);
16625
- if (totalTokens + blockTokens > tokenBudget && totalTokens > 0) {
16626
- return false;
16627
- }
16628
- if (currentBlock.role === "A" && currentBlock.commitHashes.length > 0 && lastFlushedRole !== "A") {
16629
- commitClusters++;
16630
- }
16631
- lastFlushedRole = currentBlock.role;
16632
- if (!firstMessageId)
16633
- firstMessageId = currentBlock.meta[0]?.messageId ?? "";
16634
- lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
16635
- lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
16636
- messagesProcessed += currentBlock.meta.length;
16637
- lines.push(blockText);
16638
- lineMeta.push(...currentBlock.meta);
16639
- totalTokens += blockTokens;
16640
- currentBlock = null;
16641
- return true;
16820
+ function directoryFallback(directory) {
16821
+ const canonical = path3.resolve(directory);
16822
+ const hash2 = Bun.hash(canonical).toString(16).slice(0, 12);
16823
+ return `dir:${hash2}`;
16824
+ }
16825
+ function resolveProjectIdentity(directory) {
16826
+ const resolved = path3.resolve(directory);
16827
+ const cached2 = resolvedCache.get(resolved);
16828
+ if (cached2 !== undefined) {
16829
+ return cached2;
16642
16830
  }
16643
- for (const msg of messages) {
16644
- if (eligibleEndOrdinal !== undefined && msg.ordinal >= eligibleEndOrdinal)
16645
- break;
16646
- if (msg.ordinal < startOrdinal)
16647
- continue;
16648
- const meta3 = { ordinal: msg.ordinal, messageId: msg.id };
16649
- if (msg.role === "user" && !hasMeaningfulUserText(msg.parts)) {
16650
- pendingNoiseMeta.push(meta3);
16651
- continue;
16652
- }
16653
- const role = compactRole(msg.role);
16654
- const compacted = compactTextForSummary(extractTexts(msg.parts).map((t) => msg.role === "user" ? cleanUserText(t) : t).map(normalizeText).filter((value) => value.length > 0).join(" / "), msg.role);
16655
- const text = compacted.text;
16656
- if (!text) {
16657
- pendingNoiseMeta.push(meta3);
16658
- continue;
16831
+ const rootHash = getRootCommitHash(resolved);
16832
+ const identity = rootHash ? `git:${rootHash}` : directoryFallback(resolved);
16833
+ resolvedCache.set(resolved, identity);
16834
+ return identity;
16835
+ }
16836
+
16837
+ // src/hooks/magic-context/execute-flush.ts
16838
+ function executeFlush(db, sessionId) {
16839
+ try {
16840
+ const pendingOps = getPendingOps(db, sessionId);
16841
+ if (pendingOps.length === 0) {
16842
+ return "No pending operations to flush.";
16659
16843
  }
16660
- if (currentBlock && currentBlock.role === role) {
16661
- currentBlock.endOrdinal = msg.ordinal;
16662
- currentBlock.parts.push(text);
16663
- currentBlock.meta.push(...pendingNoiseMeta, meta3);
16664
- currentBlock.commitHashes = mergeCommitHashes(currentBlock.commitHashes, compacted.commitHashes);
16665
- pendingNoiseMeta = [];
16666
- continue;
16844
+ let dropped = 0;
16845
+ db.transaction(() => {
16846
+ for (const op of pendingOps) {
16847
+ updateTagStatus(db, sessionId, op.tagId, "dropped");
16848
+ removePendingOp(db, sessionId, op.tagId);
16849
+ dropped++;
16850
+ }
16851
+ })();
16852
+ const parts = [];
16853
+ if (dropped > 0)
16854
+ parts.push(`${dropped} dropped`);
16855
+ if (dropped > 0) {
16856
+ clearPersistedStickyTurnReminder(db, sessionId);
16667
16857
  }
16668
- if (!flushCurrentBlock())
16669
- break;
16670
- currentBlock = {
16671
- role,
16672
- startOrdinal: pendingNoiseMeta[0]?.ordinal ?? msg.ordinal,
16673
- endOrdinal: msg.ordinal,
16674
- parts: [text],
16675
- meta: [...pendingNoiseMeta, meta3],
16676
- commitHashes: [...compacted.commitHashes]
16677
- };
16678
- pendingNoiseMeta = [];
16858
+ return `Flushed: ${parts.join(", ")}. Changes take effect on next message.`;
16859
+ } catch (error48) {
16860
+ sessionLog(sessionId, "ctx-flush failed:", error48);
16861
+ return `Error: Failed to flush context operations. ${getErrorMessage(error48)}`;
16679
16862
  }
16680
- flushCurrentBlock();
16681
- return {
16682
- startIndex: startOrdinal,
16683
- endIndex: lastOrdinal,
16684
- startMessageId: firstMessageId,
16685
- endMessageId: lastMessageId,
16686
- messageCount: messagesProcessed,
16687
- tokenEstimate: totalTokens,
16688
- hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, messages.length) : messages.length),
16689
- text: lines.join(`
16690
- `),
16691
- lines: lineMeta,
16692
- commitClusterCount: commitClusters
16693
- };
16694
16863
  }
16695
16864
 
16696
16865
  // src/hooks/magic-context/compartment-trigger.ts
@@ -16727,29 +16896,31 @@ var TAIL_INFO_DEFAULTS = {
16727
16896
  commitClusterCount: 0
16728
16897
  };
16729
16898
  function getUnsummarizedTailInfo(db, sessionId, compartmentTokenBudget) {
16730
- try {
16731
- const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
16732
- const nextStartOrdinal = Math.max(1, lastCompartmentEnd + 1);
16733
- const rawMessageCount = getRawSessionMessageCount(sessionId);
16734
- const protectedTailStart = getProtectedTailStartOrdinal(sessionId);
16735
- const hasEligibleHistory = rawMessageCount >= nextStartOrdinal && nextStartOrdinal < protectedTailStart;
16736
- if (!hasEligibleHistory) {
16737
- return { ...TAIL_INFO_DEFAULTS, nextStartOrdinal };
16899
+ return withRawSessionMessageCache(() => {
16900
+ try {
16901
+ const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
16902
+ const nextStartOrdinal = Math.max(1, lastCompartmentEnd + 1);
16903
+ const rawMessageCount = getRawSessionMessageCount(sessionId);
16904
+ const protectedTailStart = getProtectedTailStartOrdinal(sessionId);
16905
+ const hasEligibleHistory = rawMessageCount >= nextStartOrdinal && nextStartOrdinal < protectedTailStart;
16906
+ if (!hasEligibleHistory) {
16907
+ return { ...TAIL_INFO_DEFAULTS, nextStartOrdinal };
16908
+ }
16909
+ const scanBudget = Math.max(MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE, compartmentTokenBudget * TAIL_SIZE_TRIGGER_MULTIPLIER);
16910
+ const chunk = readSessionChunk(sessionId, scanBudget, nextStartOrdinal, protectedTailStart);
16911
+ const isMeaningful = chunk.hasMore || chunk.tokenEstimate >= MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE || chunk.messageCount >= MIN_PROACTIVE_TAIL_MESSAGE_COUNT;
16912
+ return {
16913
+ nextStartOrdinal,
16914
+ hasNewRawHistory: true,
16915
+ isMeaningful,
16916
+ tokenEstimate: chunk.tokenEstimate,
16917
+ commitClusterCount: chunk.commitClusterCount
16918
+ };
16919
+ } catch (error48) {
16920
+ sessionLog(sessionId, "compartment trigger: raw tail inspection failed:", error48);
16921
+ return TAIL_INFO_DEFAULTS;
16738
16922
  }
16739
- const scanBudget = Math.max(MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE, compartmentTokenBudget * TAIL_SIZE_TRIGGER_MULTIPLIER);
16740
- const chunk = readSessionChunk(sessionId, scanBudget, nextStartOrdinal, protectedTailStart);
16741
- const isMeaningful = chunk.hasMore || chunk.tokenEstimate >= MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE || chunk.messageCount >= MIN_PROACTIVE_TAIL_MESSAGE_COUNT;
16742
- return {
16743
- nextStartOrdinal,
16744
- hasNewRawHistory: true,
16745
- isMeaningful,
16746
- tokenEstimate: chunk.tokenEstimate,
16747
- commitClusterCount: chunk.commitClusterCount
16748
- };
16749
- } catch (error48) {
16750
- sessionLog(sessionId, "compartment trigger: raw tail inspection failed:", error48);
16751
- return TAIL_INFO_DEFAULTS;
16752
- }
16923
+ });
16753
16924
  }
16754
16925
  function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, compartmentTokenBudget = DEFAULT_COMPARTMENT_TOKEN_BUDGET) {
16755
16926
  if (sessionMeta.compartmentInProgress) {
@@ -17334,33 +17505,145 @@ function createEventHandler2(deps) {
17334
17505
  };
17335
17506
  }
17336
17507
 
17337
- // src/features/magic-context/memory/constants.ts
17338
- var PROMOTABLE_CATEGORIES = [
17339
- "ARCHITECTURE_DECISIONS",
17340
- "CONSTRAINTS",
17341
- "CONFIG_DEFAULTS",
17342
- "NAMING",
17343
- "USER_PREFERENCES",
17344
- "USER_DIRECTIVES",
17345
- "ENVIRONMENT",
17346
- "WORKFLOW_RULES",
17347
- "KNOWN_ISSUES"
17348
- ];
17349
- var CATEGORY_PRIORITY = [
17350
- "USER_DIRECTIVES",
17351
- "USER_PREFERENCES",
17352
- "NAMING",
17353
- "CONFIG_DEFAULTS",
17354
- "CONSTRAINTS",
17355
- "ARCHITECTURE_DECISIONS",
17356
- "ENVIRONMENT",
17357
- "WORKFLOW_RULES",
17358
- "KNOWN_ISSUES"
17359
- ];
17360
- var CATEGORY_DEFAULT_TTL = {
17361
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
17362
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
17363
- };
17508
+ // src/features/magic-context/memory/constants.ts
17509
+ var PROMOTABLE_CATEGORIES = [
17510
+ "ARCHITECTURE_DECISIONS",
17511
+ "CONSTRAINTS",
17512
+ "CONFIG_DEFAULTS",
17513
+ "NAMING",
17514
+ "USER_PREFERENCES",
17515
+ "USER_DIRECTIVES",
17516
+ "ENVIRONMENT",
17517
+ "WORKFLOW_RULES",
17518
+ "KNOWN_ISSUES"
17519
+ ];
17520
+ var CATEGORY_PRIORITY = [
17521
+ "USER_DIRECTIVES",
17522
+ "USER_PREFERENCES",
17523
+ "NAMING",
17524
+ "CONFIG_DEFAULTS",
17525
+ "CONSTRAINTS",
17526
+ "ARCHITECTURE_DECISIONS",
17527
+ "ENVIRONMENT",
17528
+ "WORKFLOW_RULES",
17529
+ "KNOWN_ISSUES"
17530
+ ];
17531
+ var CATEGORY_DEFAULT_TTL = {
17532
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
17533
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
17534
+ };
17535
+
17536
+ // src/features/magic-context/memory/storage-memory-embeddings.ts
17537
+ var saveEmbeddingStatements = new WeakMap;
17538
+ var loadAllEmbeddingsStatements = new WeakMap;
17539
+ var deleteEmbeddingStatements = new WeakMap;
17540
+ var getStoredModelIdStatements = new WeakMap;
17541
+ var clearAllEmbeddingsStatements = new WeakMap;
17542
+ function isEmbeddingBlob(value) {
17543
+ return value instanceof Uint8Array || value instanceof ArrayBuffer;
17544
+ }
17545
+ function isEmbeddingRow(row) {
17546
+ if (row === null || typeof row !== "object")
17547
+ return false;
17548
+ const candidate = row;
17549
+ return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
17550
+ }
17551
+ function toFloat32Array(blob) {
17552
+ if (blob instanceof Uint8Array) {
17553
+ const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
17554
+ return new Float32Array(buffer2);
17555
+ }
17556
+ return new Float32Array(blob.slice(0));
17557
+ }
17558
+ function getSaveEmbeddingStatement(db) {
17559
+ let stmt = saveEmbeddingStatements.get(db);
17560
+ if (!stmt) {
17561
+ stmt = db.prepare("INSERT INTO memory_embeddings (memory_id, embedding, model_id) VALUES (?, ?, ?) ON CONFLICT(memory_id) DO UPDATE SET embedding = excluded.embedding, model_id = excluded.model_id");
17562
+ saveEmbeddingStatements.set(db, stmt);
17563
+ }
17564
+ return stmt;
17565
+ }
17566
+ function getLoadAllEmbeddingsStatement(db) {
17567
+ let stmt = loadAllEmbeddingsStatements.get(db);
17568
+ if (!stmt) {
17569
+ stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
17570
+ loadAllEmbeddingsStatements.set(db, stmt);
17571
+ }
17572
+ return stmt;
17573
+ }
17574
+ function getStoredModelIdStatement(db) {
17575
+ let stmt = getStoredModelIdStatements.get(db);
17576
+ if (!stmt) {
17577
+ stmt = db.prepare("SELECT memory_embeddings.model_id AS modelId FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? AND memory_embeddings.model_id IS NOT NULL LIMIT 1");
17578
+ getStoredModelIdStatements.set(db, stmt);
17579
+ }
17580
+ return stmt;
17581
+ }
17582
+ function getClearAllEmbeddingsStatement(db) {
17583
+ let stmt = clearAllEmbeddingsStatements.get(db);
17584
+ if (!stmt) {
17585
+ stmt = db.prepare("DELETE FROM memory_embeddings WHERE memory_id IN (SELECT id FROM memories WHERE project_path = ?)");
17586
+ clearAllEmbeddingsStatements.set(db, stmt);
17587
+ }
17588
+ return stmt;
17589
+ }
17590
+ function saveEmbedding(db, memoryId, embedding, modelId) {
17591
+ const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
17592
+ getSaveEmbeddingStatement(db).run(memoryId, blob, modelId);
17593
+ }
17594
+ function loadAllEmbeddings(db, projectPath) {
17595
+ const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
17596
+ const embeddings = new Map;
17597
+ for (const row of rows) {
17598
+ embeddings.set(row.memoryId, toFloat32Array(row.embedding));
17599
+ }
17600
+ return embeddings;
17601
+ }
17602
+ function getStoredModelId(db, projectPath) {
17603
+ const row = getStoredModelIdStatement(db).get(projectPath);
17604
+ return typeof row?.modelId === "string" ? row.modelId : null;
17605
+ }
17606
+ function clearEmbeddingsForProject(db, projectPath) {
17607
+ getClearAllEmbeddingsStatement(db).run(projectPath);
17608
+ }
17609
+
17610
+ // src/features/magic-context/memory/embedding-cache.ts
17611
+ var DEFAULT_EMBEDDING_CACHE_TTL_MS = 60000;
17612
+ var projectEmbeddingCache = new Map;
17613
+ var embeddingCacheTtlMs = DEFAULT_EMBEDDING_CACHE_TTL_MS;
17614
+ function getValidCacheEntry(projectPath) {
17615
+ const entry = projectEmbeddingCache.get(projectPath);
17616
+ if (!entry) {
17617
+ return null;
17618
+ }
17619
+ if (entry.expiresAt <= Date.now()) {
17620
+ projectEmbeddingCache.delete(projectPath);
17621
+ return null;
17622
+ }
17623
+ return entry;
17624
+ }
17625
+ function getProjectEmbeddings(db, projectPath) {
17626
+ const cached2 = getValidCacheEntry(projectPath);
17627
+ if (cached2) {
17628
+ return cached2.embeddings;
17629
+ }
17630
+ const embeddings = loadAllEmbeddings(db, projectPath);
17631
+ projectEmbeddingCache.set(projectPath, {
17632
+ embeddings,
17633
+ expiresAt: Date.now() + embeddingCacheTtlMs
17634
+ });
17635
+ return embeddings;
17636
+ }
17637
+ function peekProjectEmbeddings(projectPath) {
17638
+ return getValidCacheEntry(projectPath)?.embeddings ?? null;
17639
+ }
17640
+ function invalidateProject(projectPath) {
17641
+ projectEmbeddingCache.delete(projectPath);
17642
+ }
17643
+ function invalidateMemory(projectPath, memoryId) {
17644
+ const cached2 = getValidCacheEntry(projectPath);
17645
+ cached2?.embeddings.delete(memoryId);
17646
+ }
17364
17647
 
17365
17648
  // src/features/magic-context/memory/normalize-hash.ts
17366
17649
  function normalizeMemoryContent(content) {
@@ -17526,7 +17809,7 @@ function getMemoriesByProjectStatement(db, statuses) {
17526
17809
  let stmt = statements.get(db);
17527
17810
  if (!stmt) {
17528
17811
  const placeholders = statuses.map(() => "?").join(", ");
17529
- stmt = db.prepare(`SELECT ${getMemorySelectColumns()} FROM memories WHERE project_path = ? AND status IN (${placeholders}) ORDER BY category ASC, updated_at DESC, id ASC`);
17812
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns()} FROM memories WHERE project_path = ? AND status IN (${placeholders}) AND (expires_at IS NULL OR expires_at > ?) ORDER BY category ASC, updated_at DESC, id ASC`);
17530
17813
  statements.set(db, stmt);
17531
17814
  }
17532
17815
  return stmt;
@@ -17596,6 +17879,7 @@ function insertMemory(db, input) {
17596
17879
  if (!inserted) {
17597
17880
  throw new Error("Failed to load inserted memory row");
17598
17881
  }
17882
+ invalidateProject(input.projectPath);
17599
17883
  return inserted;
17600
17884
  }
17601
17885
  function getMemoryByHash(db, projectPath, category, normalizedHash) {
@@ -17609,7 +17893,7 @@ function getMemoriesByProject(db, projectPath, statuses = ["active", "permanent"
17609
17893
  if (statuses.length === 0) {
17610
17894
  return [];
17611
17895
  }
17612
- const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses).filter(isMemoryRow);
17896
+ const rows = getMemoriesByProjectStatement(db, statuses).all(projectPath, ...statuses, Date.now()).filter(isMemoryRow);
17613
17897
  return rows.map(toMemory);
17614
17898
  }
17615
17899
  function getMemoryById(db, id) {
@@ -17645,6 +17929,7 @@ function mergeMetadataJson(existing, patch) {
17645
17929
  return JSON.stringify({ ...base, ...patch });
17646
17930
  }
17647
17931
  function updateMemoryContent(db, id, content, normalizedHash) {
17932
+ const memory = getMemoryById(db, id);
17648
17933
  db.transaction(() => {
17649
17934
  getUpdateMemoryContentStatement(db).run(content, normalizedHash, Date.now(), id);
17650
17935
  let stmt = deleteEmbeddingOnContentUpdateStatements.get(db);
@@ -17654,6 +17939,9 @@ function updateMemoryContent(db, id, content, normalizedHash) {
17654
17939
  }
17655
17940
  stmt.run(id);
17656
17941
  })();
17942
+ if (memory) {
17943
+ invalidateMemory(memory.projectPath, id);
17944
+ }
17657
17945
  }
17658
17946
  function supersededMemory(db, id, supersededById) {
17659
17947
  getSupersededMemoryStatement(db).run(supersededById, Date.now(), id);
@@ -18335,19 +18623,19 @@ function cosineSimilarity(a, b) {
18335
18623
  function isArrayLikeNumber(value) {
18336
18624
  return typeof value === "object" && value !== null && "length" in value;
18337
18625
  }
18338
- function toFloat32Array(values) {
18626
+ function toFloat32Array2(values) {
18339
18627
  return values instanceof Float32Array ? new Float32Array(values) : Float32Array.from(Array.from(values));
18340
18628
  }
18341
18629
  function extractBatchEmbeddings(result, expectedCount) {
18342
18630
  const { data } = result;
18343
18631
  if (Array.isArray(data) && data.length === expectedCount && data.every((entry) => typeof entry !== "number" && isArrayLikeNumber(entry))) {
18344
- return data.map((entry) => toFloat32Array(entry));
18632
+ return data.map((entry) => toFloat32Array2(entry));
18345
18633
  }
18346
18634
  if (!isArrayLikeNumber(data)) {
18347
18635
  log("[magic-context] embedding batch returned unexpected data shape");
18348
18636
  return Array.from({ length: expectedCount }, () => null);
18349
18637
  }
18350
- const flatData = toFloat32Array(data);
18638
+ const flatData = toFloat32Array2(data);
18351
18639
  const dimension = result.dims?.at(-1) ?? flatData.length / expectedCount;
18352
18640
  if (!Number.isInteger(dimension) || dimension <= 0 || flatData.length !== expectedCount * dimension) {
18353
18641
  log("[magic-context] embedding batch returned invalid dimensions");
@@ -18563,7 +18851,8 @@ function resolveModelId(config2) {
18563
18851
  if (config2.provider === "openai-compatible") {
18564
18852
  const endpoint = config2.endpoint.trim();
18565
18853
  const model = config2.model.trim();
18566
- return `openai-compat:${endpoint}:${model}`;
18854
+ const keyHash = config2.api_key ? computeNormalizedHash(config2.api_key) : "nokey";
18855
+ return `openai-compat:${endpoint}:${model}:${keyHash}`;
18567
18856
  }
18568
18857
  return config2.model.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL;
18569
18858
  }
@@ -18633,80 +18922,6 @@ async function embedBatch(texts) {
18633
18922
  function getEmbeddingModelId() {
18634
18923
  return getOrCreateProvider()?.modelId ?? "off";
18635
18924
  }
18636
- // src/features/magic-context/memory/storage-memory-embeddings.ts
18637
- var saveEmbeddingStatements = new WeakMap;
18638
- var loadAllEmbeddingsStatements = new WeakMap;
18639
- var deleteEmbeddingStatements = new WeakMap;
18640
- var getStoredModelIdStatements = new WeakMap;
18641
- var clearAllEmbeddingsStatements = new WeakMap;
18642
- function isEmbeddingBlob(value) {
18643
- return value instanceof Uint8Array || value instanceof ArrayBuffer;
18644
- }
18645
- function isEmbeddingRow(row) {
18646
- if (row === null || typeof row !== "object")
18647
- return false;
18648
- const candidate = row;
18649
- return typeof candidate.memoryId === "number" && isEmbeddingBlob(candidate.embedding);
18650
- }
18651
- function toFloat32Array2(blob) {
18652
- if (blob instanceof Uint8Array) {
18653
- const buffer2 = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
18654
- return new Float32Array(buffer2);
18655
- }
18656
- return new Float32Array(blob.slice(0));
18657
- }
18658
- function getSaveEmbeddingStatement(db) {
18659
- let stmt = saveEmbeddingStatements.get(db);
18660
- if (!stmt) {
18661
- stmt = db.prepare("INSERT INTO memory_embeddings (memory_id, embedding, model_id) VALUES (?, ?, ?) ON CONFLICT(memory_id) DO UPDATE SET embedding = excluded.embedding, model_id = excluded.model_id");
18662
- saveEmbeddingStatements.set(db, stmt);
18663
- }
18664
- return stmt;
18665
- }
18666
- function getLoadAllEmbeddingsStatement(db) {
18667
- let stmt = loadAllEmbeddingsStatements.get(db);
18668
- if (!stmt) {
18669
- stmt = db.prepare("SELECT memory_embeddings.memory_id AS memoryId, memory_embeddings.embedding AS embedding FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? ORDER BY memory_embeddings.memory_id ASC");
18670
- loadAllEmbeddingsStatements.set(db, stmt);
18671
- }
18672
- return stmt;
18673
- }
18674
- function getStoredModelIdStatement(db) {
18675
- let stmt = getStoredModelIdStatements.get(db);
18676
- if (!stmt) {
18677
- stmt = db.prepare("SELECT memory_embeddings.model_id AS modelId FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? AND memory_embeddings.model_id IS NOT NULL LIMIT 1");
18678
- getStoredModelIdStatements.set(db, stmt);
18679
- }
18680
- return stmt;
18681
- }
18682
- function getClearAllEmbeddingsStatement(db) {
18683
- let stmt = clearAllEmbeddingsStatements.get(db);
18684
- if (!stmt) {
18685
- stmt = db.prepare("DELETE FROM memory_embeddings WHERE memory_id IN (SELECT id FROM memories WHERE project_path = ?)");
18686
- clearAllEmbeddingsStatements.set(db, stmt);
18687
- }
18688
- return stmt;
18689
- }
18690
- function saveEmbedding(db, memoryId, embedding, modelId) {
18691
- const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
18692
- getSaveEmbeddingStatement(db).run(memoryId, blob, modelId);
18693
- }
18694
- function loadAllEmbeddings(db, projectPath) {
18695
- const rows = getLoadAllEmbeddingsStatement(db).all(projectPath).filter(isEmbeddingRow);
18696
- const embeddings = new Map;
18697
- for (const row of rows) {
18698
- embeddings.set(row.memoryId, toFloat32Array2(row.embedding));
18699
- }
18700
- return embeddings;
18701
- }
18702
- function getStoredModelId(db, projectPath) {
18703
- const row = getStoredModelIdStatement(db).get(projectPath);
18704
- return typeof row?.modelId === "string" ? row.modelId : null;
18705
- }
18706
- function clearEmbeddingsForProject(db, projectPath) {
18707
- getClearAllEmbeddingsStatement(db).run(projectPath);
18708
- }
18709
-
18710
18925
  // src/features/magic-context/memory/embedding-backfill.ts
18711
18926
  async function ensureMemoryEmbeddings(args) {
18712
18927
  if (!isEmbeddingEnabled()) {
@@ -18790,7 +19005,7 @@ function getSearchStatement(db) {
18790
19005
  let stmt = searchStatements.get(db);
18791
19006
  if (!stmt) {
18792
19007
  const selectColumns = Object.entries(COLUMN_MAP).map(([property, column]) => `memories.${column} AS ${property}`).join(", ");
18793
- stmt = db.prepare(`SELECT ${selectColumns} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path = ? AND memories.status IN ('active', 'permanent') AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
19008
+ stmt = db.prepare(`SELECT ${selectColumns} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path = ? AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
18794
19009
  searchStatements.set(db, stmt);
18795
19010
  }
18796
19011
  return stmt;
@@ -18810,7 +19025,7 @@ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT)
18810
19025
  if (sanitized.length === 0) {
18811
19026
  return [];
18812
19027
  }
18813
- const rows = getSearchStatement(db).all(projectPath, sanitized, limit).filter(isMemoryRow);
19028
+ const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
18814
19029
  return rows.map((row) => ({ ...row }));
18815
19030
  }
18816
19031
  // src/hooks/magic-context/compartment-parser.ts
@@ -19885,9 +20100,9 @@ function loadContextUsage(contextUsageMap, db, sessionId) {
19885
20100
  }
19886
20101
  return contextUsageEntry?.usage ?? { percentage: 0, inputTokens: 0 };
19887
20102
  }
19888
- function resolveSchedulerDecision(scheduler2, sessionMeta, contextUsage, sessionId) {
20103
+ function resolveSchedulerDecision(scheduler2, sessionMeta, contextUsage, sessionId, modelKey) {
19889
20104
  try {
19890
- const schedulerDecision = scheduler2.shouldExecute(sessionMeta, contextUsage, undefined, sessionId);
20105
+ const schedulerDecision = scheduler2.shouldExecute(sessionMeta, contextUsage, undefined, sessionId, modelKey);
19891
20106
  sessionLog(sessionId, `transform scheduler: percentage=${contextUsage.percentage.toFixed(1)}% inputTokens=${contextUsage.inputTokens} cacheTtl=${sessionMeta.cacheTtl} lastResponseTime=${sessionMeta.lastResponseTime} decision=${schedulerDecision}`);
19892
20107
  return schedulerDecision;
19893
20108
  } catch (error48) {
@@ -20129,9 +20344,17 @@ function createScopedAssignments(assignments) {
20129
20344
  return scoped;
20130
20345
  }
20131
20346
  function createExistingTagResolver(sessionId, tagger, db) {
20132
- const assignments = new Map(tagger.getAssignments(sessionId));
20133
- const scopedAssignments = createScopedAssignments(assignments);
20347
+ const assignments = tagger.getAssignments(sessionId);
20348
+ let cachedAssignmentSize = -1;
20349
+ let cachedScopedAssignments = null;
20134
20350
  const usedTagNumbers = new Set;
20351
+ function getScopedAssignments() {
20352
+ if (!cachedScopedAssignments || cachedAssignmentSize !== assignments.size) {
20353
+ cachedScopedAssignments = createScopedAssignments(assignments);
20354
+ cachedAssignmentSize = assignments.size;
20355
+ }
20356
+ return cachedScopedAssignments;
20357
+ }
20135
20358
  return {
20136
20359
  resolve(messageId, type, currentContentId, ordinal) {
20137
20360
  const exactTagId = assignments.get(currentContentId);
@@ -20139,13 +20362,12 @@ function createExistingTagResolver(sessionId, tagger, db) {
20139
20362
  usedTagNumbers.add(exactTagId);
20140
20363
  return exactTagId;
20141
20364
  }
20142
- const fallback = scopedAssignments.get(messageId)?.[type][ordinal];
20365
+ const fallback = getScopedAssignments().get(messageId)?.[type][ordinal];
20143
20366
  if (!fallback || usedTagNumbers.has(fallback.tagNumber)) {
20144
20367
  return;
20145
20368
  }
20146
20369
  updateTagMessageId(db, sessionId, fallback.tagNumber, currentContentId);
20147
20370
  tagger.bindTag(sessionId, currentContentId, fallback.tagNumber);
20148
- assignments.set(currentContentId, fallback.tagNumber);
20149
20371
  usedTagNumbers.add(fallback.tagNumber);
20150
20372
  return fallback.tagNumber;
20151
20373
  }
@@ -21027,7 +21249,7 @@ function createTransform(deps) {
21027
21249
  const compartmentDirectory = deps.directory ?? "";
21028
21250
  const canRunCompartments = fullFeatureMode && deps.client !== undefined && compartmentDirectory.length > 0;
21029
21251
  const contextUsageEarly = loadContextUsage(deps.contextUsageMap, db, sessionId);
21030
- const schedulerDecisionEarly = resolveSchedulerDecision(deps.scheduler, sessionMeta, contextUsageEarly, sessionId);
21252
+ const schedulerDecisionEarly = resolveSchedulerDecision(deps.scheduler, sessionMeta, contextUsageEarly, sessionId, deps.getModelKey?.(sessionId));
21031
21253
  const isCacheBusting = deps.flushedSessions.has(sessionId) || schedulerDecisionEarly === "execute";
21032
21254
  let pendingCompartmentInjection = null;
21033
21255
  if (fullFeatureMode) {
@@ -21572,7 +21794,11 @@ function createMagicContextHook(deps) {
21572
21794
  compartmentTokenBudget: deps.config.compartment_token_budget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
21573
21795
  historyBudgetPercentage: deps.config.history_budget_percentage,
21574
21796
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
21575
- getNotificationParams: (sessionId) => getLiveNotificationParams(sessionId, liveModelBySession, variantBySession)
21797
+ getNotificationParams: (sessionId) => getLiveNotificationParams(sessionId, liveModelBySession, variantBySession),
21798
+ getModelKey: (sessionId) => {
21799
+ const model = liveModelBySession.get(sessionId);
21800
+ return resolveModelKey(model?.providerID, model?.modelID);
21801
+ }
21576
21802
  });
21577
21803
  const eventHandler = createEventHandler2({
21578
21804
  contextUsageMap,
@@ -22212,8 +22438,7 @@ function createCtxReduceTool(deps) {
22212
22438
  }
22213
22439
  let dropIds = [];
22214
22440
  try {
22215
- if (args.drop)
22216
- dropIds = parseRangeString(args.drop);
22441
+ dropIds = parseRangeString(args.drop);
22217
22442
  } catch (e) {
22218
22443
  return `Error: Invalid range syntax. ${e.message}`;
22219
22444
  }
@@ -22285,107 +22510,9 @@ var DEFAULT_CTX_SEARCH_LIMIT = 10;
22285
22510
  // src/tools/ctx-search/tools.ts
22286
22511
  import { tool as tool5 } from "@opencode-ai/plugin";
22287
22512
 
22288
- // src/features/magic-context/message-index.ts
22289
- var lastIndexedStatements = new WeakMap;
22290
- var insertMessageStatements = new WeakMap;
22291
- var upsertIndexStatements = new WeakMap;
22292
- var deleteFtsStatements = new WeakMap;
22293
- var deleteIndexStatements = new WeakMap;
22294
- function normalizeIndexText(text) {
22295
- return text.replace(/\s+/g, " ").trim();
22296
- }
22297
- function getLastIndexedStatement(db) {
22298
- let stmt = lastIndexedStatements.get(db);
22299
- if (!stmt) {
22300
- stmt = db.prepare("SELECT last_indexed_ordinal FROM message_history_index WHERE session_id = ?");
22301
- lastIndexedStatements.set(db, stmt);
22302
- }
22303
- return stmt;
22304
- }
22305
- function getInsertMessageStatement(db) {
22306
- let stmt = insertMessageStatements.get(db);
22307
- if (!stmt) {
22308
- stmt = db.prepare("INSERT INTO message_history_fts (session_id, message_ordinal, message_id, role, content) VALUES (?, ?, ?, ?, ?)");
22309
- insertMessageStatements.set(db, stmt);
22310
- }
22311
- return stmt;
22312
- }
22313
- function getUpsertIndexStatement(db) {
22314
- let stmt = upsertIndexStatements.get(db);
22315
- if (!stmt) {
22316
- stmt = db.prepare("INSERT INTO message_history_index (session_id, last_indexed_ordinal, updated_at) VALUES (?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET last_indexed_ordinal = excluded.last_indexed_ordinal, updated_at = excluded.updated_at");
22317
- upsertIndexStatements.set(db, stmt);
22318
- }
22319
- return stmt;
22320
- }
22321
- function getDeleteFtsStatement(db) {
22322
- let stmt = deleteFtsStatements.get(db);
22323
- if (!stmt) {
22324
- stmt = db.prepare("DELETE FROM message_history_fts WHERE session_id = ?");
22325
- deleteFtsStatements.set(db, stmt);
22326
- }
22327
- return stmt;
22328
- }
22329
- function getDeleteIndexStatement(db) {
22330
- let stmt = deleteIndexStatements.get(db);
22331
- if (!stmt) {
22332
- stmt = db.prepare("DELETE FROM message_history_index WHERE session_id = ?");
22333
- deleteIndexStatements.set(db, stmt);
22334
- }
22335
- return stmt;
22336
- }
22337
- function getLastIndexedOrdinal(db, sessionId) {
22338
- const row = getLastIndexedStatement(db).get(sessionId);
22339
- return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
22340
- }
22341
- function clearIndexedMessages(db, sessionId) {
22342
- getDeleteFtsStatement(db).run(sessionId);
22343
- getDeleteIndexStatement(db).run(sessionId);
22344
- }
22345
- function getIndexableContent(role, parts) {
22346
- if (role === "user") {
22347
- if (!hasMeaningfulUserText(parts)) {
22348
- return "";
22349
- }
22350
- return extractTexts(parts).map(cleanUserText).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
22351
- }
22352
- if (role === "assistant") {
22353
- return extractTexts(parts).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
22354
- }
22355
- return "";
22356
- }
22357
- function ensureMessagesIndexed(db, sessionId, readMessages) {
22358
- const messages = readMessages(sessionId);
22359
- if (messages.length === 0) {
22360
- db.transaction(() => clearIndexedMessages(db, sessionId))();
22361
- return;
22362
- }
22363
- let lastIndexedOrdinal = getLastIndexedOrdinal(db, sessionId);
22364
- if (lastIndexedOrdinal > messages.length) {
22365
- db.transaction(() => clearIndexedMessages(db, sessionId))();
22366
- lastIndexedOrdinal = 0;
22367
- }
22368
- if (lastIndexedOrdinal >= messages.length) {
22369
- return;
22370
- }
22371
- const messagesToInsert = messages.filter((message) => message.ordinal > lastIndexedOrdinal).filter((message) => message.role === "user" || message.role === "assistant").map((message) => ({
22372
- ordinal: message.ordinal,
22373
- id: message.id,
22374
- role: message.role,
22375
- content: getIndexableContent(message.role, message.parts)
22376
- })).filter((message) => message.content.length > 0);
22377
- const now = Date.now();
22378
- db.transaction(() => {
22379
- const insertMessage = getInsertMessageStatement(db);
22380
- for (const message of messagesToInsert) {
22381
- insertMessage.run(sessionId, message.ordinal, message.id, message.role, message.content);
22382
- }
22383
- getUpsertIndexStatement(db).run(sessionId, messages.length, now);
22384
- })();
22385
- }
22386
-
22387
22513
  // src/features/magic-context/search.ts
22388
22514
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
22515
+ var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
22389
22516
  var SEMANTIC_WEIGHT = 0.7;
22390
22517
  var FTS_WEIGHT = 0.3;
22391
22518
  var SINGLE_SOURCE_PENALTY = 0.8;
@@ -22465,10 +22592,11 @@ async function getSemanticScores(args) {
22465
22592
  if (!queryEmbedding) {
22466
22593
  return semanticScores;
22467
22594
  }
22595
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
22468
22596
  const embeddings = await ensureMemoryEmbeddings({
22469
22597
  db: args.db,
22470
22598
  memories: args.memories,
22471
- existingEmbeddings: loadAllEmbeddings(args.db, args.projectPath)
22599
+ existingEmbeddings: cachedEmbeddings
22472
22600
  });
22473
22601
  for (const memory of args.memories) {
22474
22602
  const memoryEmbedding = embeddings.get(memory.id);
@@ -22479,13 +22607,28 @@ async function getSemanticScores(args) {
22479
22607
  }
22480
22608
  return semanticScores;
22481
22609
  }
22482
- function getFtsScores(args) {
22610
+ function getFtsMatches(args) {
22483
22611
  try {
22484
- const matches = searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
22485
- return new Map(matches.map((memory, rank) => [memory.id, 1 / (rank + 1)]));
22612
+ return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
22486
22613
  } catch {
22487
- return new Map;
22614
+ return [];
22615
+ }
22616
+ }
22617
+ function getFtsScores(matches) {
22618
+ return new Map(matches.map((memory, rank) => [memory.id, 1 / (rank + 1)]));
22619
+ }
22620
+ function selectSemanticCandidates(args) {
22621
+ if (args.ftsMatches.length === 0) {
22622
+ return args.memories;
22623
+ }
22624
+ const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
22625
+ const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
22626
+ if (cachedEmbeddings) {
22627
+ for (const memoryId of cachedEmbeddings.keys()) {
22628
+ candidateIds.add(memoryId);
22629
+ }
22488
22630
  }
22631
+ return args.memories.filter((memory) => candidateIds.has(memory.id));
22489
22632
  }
22490
22633
  function mergeMemoryResults(args) {
22491
22634
  const memoryById = new Map(args.memories.map((memory) => [memory.id, memory]));
@@ -22537,16 +22680,27 @@ async function searchMemories(args) {
22537
22680
  if (memories.length === 0) {
22538
22681
  return [];
22539
22682
  }
22540
- const semanticScores = await getSemanticScores({
22683
+ const ftsMatches = getFtsMatches({
22541
22684
  db: args.db,
22542
22685
  projectPath: args.projectPath,
22543
22686
  query: args.query,
22687
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT
22688
+ });
22689
+ const ftsScores = getFtsScores(ftsMatches);
22690
+ const semanticCandidates = selectSemanticCandidates({
22544
22691
  memories,
22692
+ projectPath: args.projectPath,
22693
+ ftsMatches
22694
+ });
22695
+ const semanticScores = await getSemanticScores({
22696
+ db: args.db,
22697
+ projectPath: args.projectPath,
22698
+ query: args.query,
22699
+ memories: semanticCandidates,
22545
22700
  embeddingEnabled: args.embeddingEnabled,
22546
22701
  embedQuery: args.embedQuery,
22547
22702
  isEmbeddingRuntimeEnabled: args.isEmbeddingRuntimeEnabled
22548
22703
  });
22549
- const ftsScores = getFtsScores(args);
22550
22704
  return mergeMemoryResults({
22551
22705
  memories,
22552
22706
  semanticScores,