@wolfx/pi-magic-context 0.22.4 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,6 +46,111 @@ var __export = (target, all) => {
46
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
47
47
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
48
48
 
49
+ // ../plugin/src/shared/harness.ts
50
+ function setHarness(value) {
51
+ if (harnessLocked && currentHarness !== value) {
52
+ throw new Error(`Magic Context: harness already locked to "${currentHarness}"; cannot change to "${value}"`);
53
+ }
54
+ currentHarness = value;
55
+ harnessLocked = true;
56
+ }
57
+ function getHarness() {
58
+ return currentHarness;
59
+ }
60
+ var currentHarness = "opencode", harnessLocked = false;
61
+
62
+ // ../plugin/src/shared/data-path.ts
63
+ import * as os from "node:os";
64
+ import * as path from "node:path";
65
+ function getDataDir() {
66
+ return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
67
+ }
68
+ function getMagicContextTempDir(harness = getHarness()) {
69
+ return path.join(os.tmpdir(), harness, "magic-context");
70
+ }
71
+ function getMagicContextLogPath(harness = getHarness()) {
72
+ return path.join(getMagicContextTempDir(harness), "magic-context.log");
73
+ }
74
+ function getProjectMagicContextDir(directory) {
75
+ return path.join(directory, ".magic-context");
76
+ }
77
+ function getProjectMagicContextHistorianDir(directory) {
78
+ return path.join(getProjectMagicContextDir(directory), "historian");
79
+ }
80
+ function getOpenCodeStorageDir() {
81
+ return path.join(getDataDir(), "opencode", "storage");
82
+ }
83
+ function getMagicContextStorageDir() {
84
+ return path.join(getDataDir(), "cortexkit", "magic-context");
85
+ }
86
+ function getLegacyOpenCodeMagicContextStorageDir() {
87
+ return path.join(getOpenCodeStorageDir(), "plugin", "magic-context");
88
+ }
89
+ var init_data_path = () => {};
90
+
91
+ // ../plugin/src/shared/logger.ts
92
+ import * as fs from "node:fs";
93
+ import * as path2 from "node:path";
94
+ function ensureDir(filePath) {
95
+ const dir = path2.dirname(filePath);
96
+ if (dir === lastEnsuredDir)
97
+ return;
98
+ try {
99
+ fs.mkdirSync(dir, { recursive: true });
100
+ lastEnsuredDir = dir;
101
+ } catch {}
102
+ }
103
+ function flush() {
104
+ if (flushTimer) {
105
+ clearTimeout(flushTimer);
106
+ flushTimer = null;
107
+ }
108
+ if (buffer.length === 0)
109
+ return;
110
+ const data = buffer.join("");
111
+ buffer = [];
112
+ try {
113
+ const logFile = getMagicContextLogPath();
114
+ ensureDir(logFile);
115
+ fs.appendFileSync(logFile, data);
116
+ } catch {}
117
+ }
118
+ function scheduleFlush() {
119
+ if (flushTimer)
120
+ return;
121
+ flushTimer = setTimeout(() => {
122
+ flushTimer = null;
123
+ flush();
124
+ }, FLUSH_INTERVAL_MS);
125
+ }
126
+ function log(message, data) {
127
+ if (isTestEnv)
128
+ return;
129
+ try {
130
+ const timestamp = new Date().toISOString();
131
+ const serialized = data === undefined ? "" : data instanceof Error ? ` ${data.message}${data.stack ? `
132
+ ${data.stack}` : ""}` : ` ${JSON.stringify(data)}`;
133
+ buffer.push(`[${timestamp}] ${message}${serialized}
134
+ `);
135
+ if (buffer.length >= BUFFER_SIZE_LIMIT) {
136
+ flush();
137
+ } else {
138
+ scheduleFlush();
139
+ }
140
+ } catch {}
141
+ }
142
+ function sessionLog(sessionId, message, data) {
143
+ log(`[magic-context][${sessionId}] ${message}`, data);
144
+ }
145
+ var isTestEnv = false, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50, lastEnsuredDir = null;
146
+ var init_logger = __esm(() => {
147
+ init_data_path();
148
+ buffer = [];
149
+ if (!isTestEnv) {
150
+ process.on("exit", flush);
151
+ }
152
+ });
153
+
49
154
  // ../../node_modules/.bun/esprima@4.0.1/node_modules/esprima/dist/esprima.js
50
155
  var require_esprima = __commonJS((exports, module) => {
51
156
  (function webpackUniversalModuleDefinition(root, factory) {
@@ -7804,53 +7909,10 @@ var require_src2 = __commonJS((exports, module) => {
7804
7909
  });
7805
7910
 
7806
7911
  // ../plugin/src/features/magic-context/storage-db.ts
7912
+ init_data_path();
7807
7913
  import { copyFileSync, cpSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
7808
7914
  import { dirname as dirname2, join as join4 } from "node:path";
7809
7915
 
7810
- // ../plugin/src/shared/data-path.ts
7811
- import * as os from "node:os";
7812
- import * as path from "node:path";
7813
-
7814
- // ../plugin/src/shared/harness.ts
7815
- var currentHarness = "opencode";
7816
- var harnessLocked = false;
7817
- function setHarness(value) {
7818
- if (harnessLocked && currentHarness !== value) {
7819
- throw new Error(`Magic Context: harness already locked to "${currentHarness}"; cannot change to "${value}"`);
7820
- }
7821
- currentHarness = value;
7822
- harnessLocked = true;
7823
- }
7824
- function getHarness() {
7825
- return currentHarness;
7826
- }
7827
-
7828
- // ../plugin/src/shared/data-path.ts
7829
- function getDataDir() {
7830
- return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
7831
- }
7832
- function getMagicContextTempDir(harness = getHarness()) {
7833
- return path.join(os.tmpdir(), harness, "magic-context");
7834
- }
7835
- function getMagicContextLogPath(harness = getHarness()) {
7836
- return path.join(getMagicContextTempDir(harness), "magic-context.log");
7837
- }
7838
- function getProjectMagicContextDir(directory) {
7839
- return path.join(directory, ".magic-context");
7840
- }
7841
- function getProjectMagicContextHistorianDir(directory) {
7842
- return path.join(getProjectMagicContextDir(directory), "historian");
7843
- }
7844
- function getOpenCodeStorageDir() {
7845
- return path.join(getDataDir(), "opencode", "storage");
7846
- }
7847
- function getMagicContextStorageDir() {
7848
- return path.join(getDataDir(), "cortexkit", "magic-context");
7849
- }
7850
- function getLegacyOpenCodeMagicContextStorageDir() {
7851
- return path.join(getOpenCodeStorageDir(), "plugin", "magic-context");
7852
- }
7853
-
7854
7916
  // ../plugin/src/shared/error-message.ts
7855
7917
  function getErrorMessage(error) {
7856
7918
  return error instanceof Error ? error.message : String(error);
@@ -7927,69 +7989,8 @@ function safeString(value) {
7927
7989
  }
7928
7990
  }
7929
7991
 
7930
- // ../plugin/src/shared/logger.ts
7931
- import * as fs from "node:fs";
7932
- import * as path2 from "node:path";
7933
- var isTestEnv = false;
7934
- var buffer = [];
7935
- var flushTimer = null;
7936
- var FLUSH_INTERVAL_MS = 500;
7937
- var BUFFER_SIZE_LIMIT = 50;
7938
- var lastEnsuredDir = null;
7939
- function ensureDir(filePath) {
7940
- const dir = path2.dirname(filePath);
7941
- if (dir === lastEnsuredDir)
7942
- return;
7943
- try {
7944
- fs.mkdirSync(dir, { recursive: true });
7945
- lastEnsuredDir = dir;
7946
- } catch {}
7947
- }
7948
- function flush() {
7949
- if (flushTimer) {
7950
- clearTimeout(flushTimer);
7951
- flushTimer = null;
7952
- }
7953
- if (buffer.length === 0)
7954
- return;
7955
- const data = buffer.join("");
7956
- buffer = [];
7957
- try {
7958
- const logFile = getMagicContextLogPath();
7959
- ensureDir(logFile);
7960
- fs.appendFileSync(logFile, data);
7961
- } catch {}
7962
- }
7963
- function scheduleFlush() {
7964
- if (flushTimer)
7965
- return;
7966
- flushTimer = setTimeout(() => {
7967
- flushTimer = null;
7968
- flush();
7969
- }, FLUSH_INTERVAL_MS);
7970
- }
7971
- function log(message, data) {
7972
- if (isTestEnv)
7973
- return;
7974
- try {
7975
- const timestamp = new Date().toISOString();
7976
- const serialized = data === undefined ? "" : data instanceof Error ? ` ${data.message}${data.stack ? `
7977
- ${data.stack}` : ""}` : ` ${JSON.stringify(data)}`;
7978
- buffer.push(`[${timestamp}] ${message}${serialized}
7979
- `);
7980
- if (buffer.length >= BUFFER_SIZE_LIMIT) {
7981
- flush();
7982
- } else {
7983
- scheduleFlush();
7984
- }
7985
- } catch {}
7986
- }
7987
- function sessionLog(sessionId, message, data) {
7988
- log(`[magic-context][${sessionId}] ${message}`, data);
7989
- }
7990
- if (!isTestEnv) {
7991
- process.on("exit", flush);
7992
- }
7992
+ // ../plugin/src/features/magic-context/storage-db.ts
7993
+ init_logger();
7993
7994
 
7994
7995
  // ../plugin/src/shared/sqlite.ts
7995
7996
  var isBun = typeof process !== "undefined" && typeof process.versions?.bun === "string";
@@ -8045,6 +8046,7 @@ function closeQuietly(db) {
8045
8046
  }
8046
8047
 
8047
8048
  // ../plugin/src/features/magic-context/key-files/project-key-files.ts
8049
+ init_logger();
8048
8050
  import { createHash } from "node:crypto";
8049
8051
  import { existsSync, readFileSync, realpathSync } from "node:fs";
8050
8052
  import { join as join2, resolve, sep } from "node:path";
@@ -8161,6 +8163,7 @@ function isRelativeProjectFile(projectPath, relativePath) {
8161
8163
  }
8162
8164
 
8163
8165
  // ../plugin/src/features/magic-context/migrations.ts
8166
+ init_logger();
8164
8167
  function tableExists(db, name) {
8165
8168
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
8166
8169
  }
@@ -8916,6 +8919,50 @@ var MIGRATIONS = [
8916
8919
  cached_m0_model_key = NULL`).run();
8917
8920
  }
8918
8921
  }
8922
+ },
8923
+ {
8924
+ version: 31,
8925
+ description: "Nudge redesign: Channel 1 cadence (last_nudge_undropped) + Channel 2 ceiling lease " + "(channel2_nudge_state); zero legacy ctx_reduce-nudge sticky/anchor state (startup heal)",
8926
+ up: (db) => {
8927
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
8928
+ if (!hasSessionMeta)
8929
+ return;
8930
+ ensureColumn(db, "session_meta", "last_nudge_undropped", "INTEGER DEFAULT 0");
8931
+ ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
8932
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
8933
+ if (columns.has("sticky_turn_reminder_text")) {
8934
+ db.prepare(`UPDATE session_meta SET
8935
+ sticky_turn_reminder_text = '',
8936
+ sticky_turn_reminder_message_id = '',
8937
+ nudge_anchor_message_id = '',
8938
+ nudge_anchor_text = ''`).run();
8939
+ }
8940
+ }
8941
+ },
8942
+ {
8943
+ version: 32,
8944
+ description: "Protected tail boundary state, usage resolver fields, recovery escape, and drain quota",
8945
+ up: (db) => {
8946
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
8947
+ if (!hasSessionMeta)
8948
+ return;
8949
+ ensureColumn(db, "session_meta", "prior_boundary_ordinal", "INTEGER NOT NULL DEFAULT 1");
8950
+ ensureColumn(db, "session_meta", "protected_tail_policy_version", "INTEGER NOT NULL DEFAULT 0");
8951
+ ensureColumn(db, "session_meta", "protected_tail_drain_window_started_at", "INTEGER NOT NULL DEFAULT 0");
8952
+ ensureColumn(db, "session_meta", "protected_tail_drain_tokens", "INTEGER NOT NULL DEFAULT 0");
8953
+ ensureColumn(db, "session_meta", "recovery_no_eligible_head_count", "INTEGER NOT NULL DEFAULT 0");
8954
+ ensureColumn(db, "session_meta", "force_emergency_bypass_window_start", "INTEGER NOT NULL DEFAULT 0");
8955
+ ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
8956
+ ensureColumn(db, "session_meta", "last_usage_context_limit", "INTEGER NOT NULL DEFAULT 0");
8957
+ db.prepare("UPDATE session_meta SET prior_boundary_ordinal = 1 WHERE prior_boundary_ordinal IS NULL OR prior_boundary_ordinal < 1").run();
8958
+ db.prepare("UPDATE session_meta SET protected_tail_policy_version = 0 WHERE protected_tail_policy_version IS NULL").run();
8959
+ db.prepare("UPDATE session_meta SET protected_tail_drain_window_started_at = 0 WHERE protected_tail_drain_window_started_at IS NULL").run();
8960
+ db.prepare("UPDATE session_meta SET protected_tail_drain_tokens = 0 WHERE protected_tail_drain_tokens IS NULL").run();
8961
+ db.prepare("UPDATE session_meta SET recovery_no_eligible_head_count = 0 WHERE recovery_no_eligible_head_count IS NULL").run();
8962
+ db.prepare("UPDATE session_meta SET force_emergency_bypass_window_start = 0 WHERE force_emergency_bypass_window_start IS NULL").run();
8963
+ db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
8964
+ db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
8965
+ }
8919
8966
  }
8920
8967
  ];
8921
8968
  var LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -141521,7 +141568,7 @@ var tokenizer = new src_default(exports_claude);
141521
141568
  function estimateTokens(text) {
141522
141569
  if (!text)
141523
141570
  return 0;
141524
- return tokenizer.count(text);
141571
+ return tokenizer.encode(text, "all").length;
141525
141572
  }
141526
141573
  function normalizeText(text) {
141527
141574
  return text.replace(/\s+/g, " ").trim();
@@ -141631,6 +141678,8 @@ function loadToolDefinitionMeasurements(db) {
141631
141678
  }
141632
141679
 
141633
141680
  // ../plugin/src/features/magic-context/tool-owner-backfill.ts
141681
+ init_data_path();
141682
+ init_logger();
141634
141683
  import { existsSync as existsSync2 } from "node:fs";
141635
141684
  import { join as join3 } from "node:path";
141636
141685
  var LEASE_DURATION_MS = 5 * 60 * 1000;
@@ -141746,6 +141795,11 @@ function markSessionCompleted(db, sessionId, now) {
141746
141795
  SET status = 'completed', completed_at = ?, lease_expires_at = NULL, last_error = NULL
141747
141796
  WHERE session_id = ?`).run(now, sessionId);
141748
141797
  }
141798
+ function markSessionPendingRetry(db, sessionId) {
141799
+ db.prepare(`UPDATE tool_owner_backfill_state
141800
+ SET status = 'pending', completed_at = NULL, lease_expires_at = NULL, last_error = NULL
141801
+ WHERE session_id = ?`).run(sessionId);
141802
+ }
141749
141803
  function markSessionSkipped(db, sessionId, now, reason) {
141750
141804
  db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, completed_at, last_error)
141751
141805
  VALUES (?, 'skipped', ?, ?)
@@ -141822,12 +141876,18 @@ function applyOwnersForSession(db, sessionId, ownersByCallId) {
141822
141876
  const updateRowStmt = db.prepare(`UPDATE tags
141823
141877
  SET tool_owner_message_id = ?
141824
141878
  WHERE id = ? AND tool_owner_message_id IS NULL`);
141879
+ const existingOwnerStmt = db.prepare(`SELECT 1 AS hit FROM tags
141880
+ WHERE session_id = ? AND message_id = ? AND type = 'tool'
141881
+ AND tool_owner_message_id = ?
141882
+ LIMIT 1`);
141825
141883
  let rowsUpdated = 0;
141826
141884
  db.transaction(() => {
141827
141885
  for (const [callId, ownerId] of ownersByCallId) {
141828
141886
  const orphan = findOrphanStmt.get(sessionId, callId);
141829
141887
  if (!orphan)
141830
141888
  continue;
141889
+ if (existingOwnerStmt.get(sessionId, callId, ownerId))
141890
+ continue;
141831
141891
  const result = updateRowStmt.run(ownerId, orphan.id);
141832
141892
  rowsUpdated += result.changes ?? 0;
141833
141893
  }
@@ -141856,6 +141916,8 @@ function backfillToolOwnersInChunks(db, result) {
141856
141916
  if (owners.size === 0) {
141857
141917
  markSessionSkipped(db, sessionId, Date.now(), "no_oc_matches");
141858
141918
  result.sessionsSkippedNoMatches += 1;
141919
+ } else if (rowsLeftNull > 0) {
141920
+ markSessionPendingRetry(db, sessionId);
141859
141921
  } else {
141860
141922
  markSessionCompleted(db, sessionId, Date.now());
141861
141923
  result.sessionsCompleted += 1;
@@ -141878,7 +141940,7 @@ var databases = new Map;
141878
141940
  var persistenceByDatabase = new WeakMap;
141879
141941
  var persistenceErrorByDatabase = new WeakMap;
141880
141942
  var lastSchemaFenceRejection = null;
141881
- var LATEST_SUPPORTED_VERSION = 30;
141943
+ var LATEST_SUPPORTED_VERSION = 32;
141882
141944
  function resolveDatabasePath(dbPathOverride) {
141883
141945
  if (dbPathOverride) {
141884
141946
  return { dbDir: dirname2(dbPathOverride), dbPath: dbPathOverride };
@@ -141980,9 +142042,9 @@ function runSqliteOptimize(db) {
141980
142042
  } catch {}
141981
142043
  }
141982
142044
  function initializeDatabase(db) {
142045
+ db.exec("PRAGMA busy_timeout=5000");
141983
142046
  db.exec("PRAGMA foreign_keys=ON");
141984
142047
  db.exec("PRAGMA journal_mode=WAL");
141985
- db.exec("PRAGMA busy_timeout=5000");
141986
142048
  applySqliteTuningPragmas(db);
141987
142049
  db.exec(`
141988
142050
  CREATE TABLE IF NOT EXISTS tags (
@@ -141995,6 +142057,9 @@ function initializeDatabase(db) {
141995
142057
  tag_number INTEGER,
141996
142058
  harness TEXT NOT NULL DEFAULT 'opencode',
141997
142059
  entry_fingerprint TEXT,
142060
+ token_count INTEGER,
142061
+ input_token_count INTEGER,
142062
+ reasoning_token_count INTEGER,
141998
142063
  UNIQUE(session_id, tag_number)
141999
142064
  );
142000
142065
 
@@ -142284,6 +142349,11 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142284
142349
  counter INTEGER DEFAULT 0,
142285
142350
  last_nudge_tokens INTEGER DEFAULT 0,
142286
142351
  last_nudge_band TEXT DEFAULT '',
142352
+ last_nudge_undropped INTEGER DEFAULT 0,
142353
+ last_nudge_level TEXT DEFAULT '',
142354
+ channel2_nudge_state TEXT DEFAULT '',
142355
+ channel2_nudge_claimed_at INTEGER DEFAULT 0,
142356
+ last_emergency_input_sample INTEGER DEFAULT 0,
142287
142357
  last_transform_error TEXT DEFAULT '',
142288
142358
  nudge_anchor_message_id TEXT DEFAULT '',
142289
142359
  nudge_anchor_text TEXT DEFAULT '',
@@ -142340,6 +142410,14 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142340
142410
  cached_m0_project_docs_hash TEXT,
142341
142411
  cached_m1_bytes BLOB,
142342
142412
  last_observed_model_key TEXT,
142413
+ last_usage_context_limit INTEGER NOT NULL DEFAULT 0,
142414
+ prior_boundary_ordinal INTEGER NOT NULL DEFAULT 1,
142415
+ protected_tail_policy_version INTEGER NOT NULL DEFAULT 0,
142416
+ protected_tail_drain_window_started_at INTEGER NOT NULL DEFAULT 0,
142417
+ protected_tail_drain_tokens INTEGER NOT NULL DEFAULT 0,
142418
+ recovery_no_eligible_head_count INTEGER NOT NULL DEFAULT 0,
142419
+ force_emergency_bypass_window_start INTEGER NOT NULL DEFAULT 0,
142420
+ force_emergency_bypass_used INTEGER NOT NULL DEFAULT 0,
142343
142421
  cached_m0_materialized_at INTEGER,
142344
142422
  cached_m0_session_facts_version INTEGER,
142345
142423
  cached_m0_upgrade_state TEXT,
@@ -142450,6 +142528,11 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142450
142528
  CREATE INDEX IF NOT EXISTS idx_message_history_index_updated_at ON message_history_index(updated_at);
142451
142529
  `);
142452
142530
  ensureColumn(db, "session_meta", "last_nudge_band", "TEXT DEFAULT ''");
142531
+ ensureColumn(db, "session_meta", "last_nudge_undropped", "INTEGER DEFAULT 0");
142532
+ ensureColumn(db, "session_meta", "last_nudge_level", "TEXT DEFAULT ''");
142533
+ ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
142534
+ ensureColumn(db, "session_meta", "channel2_nudge_claimed_at", "INTEGER DEFAULT 0");
142535
+ ensureColumn(db, "session_meta", "last_emergency_input_sample", "INTEGER DEFAULT 0");
142453
142536
  ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
142454
142537
  ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
142455
142538
  ensureColumn(db, "session_meta", "nudge_anchor_text", "TEXT DEFAULT ''");
@@ -142476,6 +142559,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142476
142559
  ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
142477
142560
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
142478
142561
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
142562
+ ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
142479
142563
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
142480
142564
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
142481
142565
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -142494,6 +142578,9 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142494
142578
  db.exec(`CREATE INDEX IF NOT EXISTS idx_tags_pi_adopt
142495
142579
  ON tags(session_id, entry_fingerprint)
142496
142580
  WHERE type='message' AND entry_fingerprint IS NOT NULL`);
142581
+ ensureColumn(db, "tags", "token_count", "INTEGER");
142582
+ ensureColumn(db, "tags", "input_token_count", "INTEGER");
142583
+ ensureColumn(db, "tags", "reasoning_token_count", "INTEGER");
142497
142584
  ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
142498
142585
  ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
142499
142586
  ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
@@ -142534,6 +142621,14 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142534
142621
  ensureColumn(db, "session_meta", "cached_m0_project_docs_hash", "TEXT");
142535
142622
  ensureColumn(db, "session_meta", "cached_m1_bytes", "BLOB");
142536
142623
  ensureColumn(db, "session_meta", "last_observed_model_key", "TEXT");
142624
+ ensureColumn(db, "session_meta", "last_usage_context_limit", "INTEGER NOT NULL DEFAULT 0");
142625
+ ensureColumn(db, "session_meta", "prior_boundary_ordinal", "INTEGER NOT NULL DEFAULT 1");
142626
+ ensureColumn(db, "session_meta", "protected_tail_policy_version", "INTEGER NOT NULL DEFAULT 0");
142627
+ ensureColumn(db, "session_meta", "protected_tail_drain_window_started_at", "INTEGER NOT NULL DEFAULT 0");
142628
+ ensureColumn(db, "session_meta", "protected_tail_drain_tokens", "INTEGER NOT NULL DEFAULT 0");
142629
+ ensureColumn(db, "session_meta", "recovery_no_eligible_head_count", "INTEGER NOT NULL DEFAULT 0");
142630
+ ensureColumn(db, "session_meta", "force_emergency_bypass_window_start", "INTEGER NOT NULL DEFAULT 0");
142631
+ ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
142537
142632
  ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
142538
142633
  ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
142539
142634
  ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
@@ -142603,6 +142698,13 @@ function healAllNullColumns(db) {
142603
142698
  healNullIntegerColumns(db);
142604
142699
  healMissingMemoryBlockIds(db);
142605
142700
  }
142701
+ var CHANNEL2_CLAIM_TTL_MS = 120000;
142702
+ function healWedgedChannel2Claims(db) {
142703
+ try {
142704
+ const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
142705
+ db.prepare("UPDATE session_meta SET channel2_nudge_state = 'pending', channel2_nudge_claimed_at = 0 WHERE channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)").run(staleBefore);
142706
+ } catch {}
142707
+ }
142606
142708
  function healMissingMemoryBlockIds(db) {
142607
142709
  try {
142608
142710
  db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != '' AND (memory_block_ids IS NULL OR memory_block_ids = '') AND memory_block_count > 0").run();
@@ -142612,6 +142714,7 @@ function healNullTextColumns(db) {
142612
142714
  const columns = [
142613
142715
  ["cache_ttl", ""],
142614
142716
  ["last_nudge_band", ""],
142717
+ ["last_nudge_level", ""],
142615
142718
  ["last_transform_error", ""],
142616
142719
  ["nudge_anchor_message_id", ""],
142617
142720
  ["nudge_anchor_text", ""],
@@ -142626,6 +142729,7 @@ function healNullTextColumns(db) {
142626
142729
  ["todo_synthetic_state_json", ""],
142627
142730
  ["system_prompt_hash", ""],
142628
142731
  ["stripped_placeholder_ids", ""],
142732
+ ["stale_reduce_stripped_ids", ""],
142629
142733
  ["memory_block_cache", ""],
142630
142734
  ["memory_block_ids", ""],
142631
142735
  ["compaction_marker_state", ""],
@@ -142651,7 +142755,17 @@ function healNullIntegerColumns(db) {
142651
142755
  ["observed_safe_input_tokens", 0],
142652
142756
  ["cache_alert_sent", 0],
142653
142757
  ["new_work_tokens", 0],
142654
- ["total_input_tokens", 0]
142758
+ ["total_input_tokens", 0],
142759
+ ["last_emergency_input_sample", 0],
142760
+ ["channel2_nudge_claimed_at", 0],
142761
+ ["last_usage_context_limit", 0],
142762
+ ["prior_boundary_ordinal", 1],
142763
+ ["protected_tail_policy_version", 0],
142764
+ ["protected_tail_drain_window_started_at", 0],
142765
+ ["protected_tail_drain_tokens", 0],
142766
+ ["recovery_no_eligible_head_count", 0],
142767
+ ["force_emergency_bypass_window_start", 0],
142768
+ ["force_emergency_bypass_used", 0]
142655
142769
  ];
142656
142770
  for (const [column, fallback] of columns) {
142657
142771
  try {
@@ -142690,6 +142804,7 @@ function openDatabase(dbPathOrOptions) {
142690
142804
  if (!persistenceByDatabase.has(existing)) {
142691
142805
  persistenceByDatabase.set(existing, true);
142692
142806
  }
142807
+ healWedgedChannel2Claims(existing);
142693
142808
  return existing;
142694
142809
  }
142695
142810
  try {
@@ -142715,6 +142830,7 @@ function openDatabase(dbPathOrOptions) {
142715
142830
  log(`[magic-context] key-files orphan GC failed: ${getErrorMessage(error)}`);
142716
142831
  }
142717
142832
  }
142833
+ healWedgedChannel2Claims(db);
142718
142834
  if (!explicitDbPath) {
142719
142835
  try {
142720
142836
  runToolOwnerBackfill(db);
@@ -142735,6 +142851,34 @@ function openDatabase(dbPathOrOptions) {
142735
142851
  }
142736
142852
  }
142737
142853
 
142854
+ // src/subagent-entry.ts
142855
+ init_logger();
142856
+
142857
+ // ../plugin/src/config/prune-config-leaf.ts
142858
+ function isPlainObject(value) {
142859
+ return typeof value === "object" && value !== null && !Array.isArray(value);
142860
+ }
142861
+ function pruneNestedConfigLeaf(block, relativePath) {
142862
+ if (relativePath.length === 0)
142863
+ return null;
142864
+ const result = { ...block };
142865
+ let cursor = result;
142866
+ for (let i = 0;i < relativePath.length - 1; i++) {
142867
+ const seg = String(relativePath[i]);
142868
+ const child = cursor[seg];
142869
+ if (!isPlainObject(child))
142870
+ return null;
142871
+ const clonedChild = { ...child };
142872
+ cursor[seg] = clonedChild;
142873
+ cursor = clonedChild;
142874
+ }
142875
+ const leaf = String(relativePath[relativePath.length - 1]);
142876
+ if (!(leaf in cursor))
142877
+ return null;
142878
+ delete cursor[leaf];
142879
+ return { block: result, removed: relativePath.map(String).join(".") };
142880
+ }
142881
+
142738
142882
  // src/config/index.ts
142739
142883
  import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
142740
142884
  import { homedir as homedir3 } from "node:os";
@@ -142880,6 +143024,74 @@ function migrateLegacyExperimental(rawConfig, warnings) {
142880
143024
  return patched;
142881
143025
  }
142882
143026
 
143027
+ // ../plugin/src/config/project-security.ts
143028
+ var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
143029
+ var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
143030
+ function isPlainObject2(value) {
143031
+ return typeof value === "object" && value !== null && !Array.isArray(value);
143032
+ }
143033
+ function stripUnsafeProjectConfigFields(projectRaw) {
143034
+ const warnings = [];
143035
+ if ("auto_update" in projectRaw) {
143036
+ delete projectRaw.auto_update;
143037
+ warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
143038
+ }
143039
+ if ("sqlite" in projectRaw) {
143040
+ delete projectRaw.sqlite;
143041
+ warnings.push("Ignoring sqlite.* from project config (security: SQLite cache/mmap PRAGMAs apply to the " + "process-global shared database handle; only user-level config may set them).");
143042
+ }
143043
+ for (const agentKey of HIDDEN_AGENT_KEYS) {
143044
+ const block = projectRaw[agentKey];
143045
+ if (!isPlainObject2(block))
143046
+ continue;
143047
+ const removed = [];
143048
+ for (const field of AGENT_ESCALATION_FIELDS) {
143049
+ if (field in block) {
143050
+ delete block[field];
143051
+ removed.push(field);
143052
+ }
143053
+ }
143054
+ if (removed.length > 0) {
143055
+ warnings.push(`Ignoring ${agentKey}.${removed.join("/")} from project config ` + "(security: a repository cannot reprogram or re-permission hidden agents).");
143056
+ }
143057
+ }
143058
+ return warnings;
143059
+ }
143060
+ function normalizeEndpoint(value) {
143061
+ if (typeof value !== "string")
143062
+ return;
143063
+ const trimmed = value.trim().replace(/\/+$/, "");
143064
+ return trimmed.length > 0 ? trimmed.toLowerCase() : undefined;
143065
+ }
143066
+ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
143067
+ const projectEmbedding = projectRaw.embedding;
143068
+ if (!isPlainObject2(projectEmbedding))
143069
+ return [];
143070
+ const redirectsEndpoint = "endpoint" in projectEmbedding;
143071
+ if (!redirectsEndpoint)
143072
+ return [];
143073
+ const userEmbedding = userRaw?.embedding;
143074
+ if (isPlainObject2(userEmbedding)) {
143075
+ const projectEndpoint = normalizeEndpoint(projectEmbedding.endpoint);
143076
+ const userEndpoint = normalizeEndpoint(userEmbedding.endpoint);
143077
+ if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
143078
+ return [];
143079
+ }
143080
+ }
143081
+ const providesOwnKey = typeof projectEmbedding.api_key === "string" && projectEmbedding.api_key.length > 0;
143082
+ if (providesOwnKey)
143083
+ return [];
143084
+ const mergedEmbedding = mergedRaw.embedding;
143085
+ if (!isPlainObject2(mergedEmbedding))
143086
+ return [];
143087
+ if (!("api_key" in mergedEmbedding))
143088
+ return [];
143089
+ delete mergedEmbedding.api_key;
143090
+ return [
143091
+ "Dropped inherited user embedding api_key because project config redirected " + "embedding.endpoint without supplying its own key (security: prevents key " + "exfiltration to a repository-chosen endpoint)."
143092
+ ];
143093
+ }
143094
+
142883
143095
  // ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
142884
143096
  var exports_external = {};
142885
143097
  __export(exports_external, {
@@ -143511,7 +143723,7 @@ __export(exports_util, {
143511
143723
  jsonStringifyReplacer: () => jsonStringifyReplacer,
143512
143724
  joinValues: () => joinValues,
143513
143725
  issue: () => issue,
143514
- isPlainObject: () => isPlainObject,
143726
+ isPlainObject: () => isPlainObject3,
143515
143727
  isObject: () => isObject,
143516
143728
  hexToUint8Array: () => hexToUint8Array,
143517
143729
  getSizableOrigin: () => getSizableOrigin,
@@ -143693,7 +143905,7 @@ var allowsEval = /* @__PURE__ */ cached(() => {
143693
143905
  return false;
143694
143906
  }
143695
143907
  });
143696
- function isPlainObject(o) {
143908
+ function isPlainObject3(o) {
143697
143909
  if (isObject(o) === false)
143698
143910
  return false;
143699
143911
  const ctor = o.constructor;
@@ -143710,7 +143922,7 @@ function isPlainObject(o) {
143710
143922
  return true;
143711
143923
  }
143712
143924
  function shallowClone(o) {
143713
- if (isPlainObject(o))
143925
+ if (isPlainObject3(o))
143714
143926
  return { ...o };
143715
143927
  if (Array.isArray(o))
143716
143928
  return [...o];
@@ -143914,7 +144126,7 @@ function omit(schema, mask) {
143914
144126
  return clone(schema, def);
143915
144127
  }
143916
144128
  function extend(schema, shape) {
143917
- if (!isPlainObject(shape)) {
144129
+ if (!isPlainObject3(shape)) {
143918
144130
  throw new Error("Invalid input to extend: expected a plain object");
143919
144131
  }
143920
144132
  const checks = schema._zod.def.checks;
@@ -143937,7 +144149,7 @@ function extend(schema, shape) {
143937
144149
  return clone(schema, def);
143938
144150
  }
143939
144151
  function safeExtend(schema, shape) {
143940
- if (!isPlainObject(shape)) {
144152
+ if (!isPlainObject3(shape)) {
143941
144153
  throw new Error("Invalid input to safeExtend: expected a plain object");
143942
144154
  }
143943
144155
  const def = mergeDefs(schema._zod.def, {
@@ -146280,7 +146492,7 @@ function mergeValues(a, b) {
146280
146492
  if (a instanceof Date && b instanceof Date && +a === +b) {
146281
146493
  return { valid: true, data: a };
146282
146494
  }
146283
- if (isPlainObject(a) && isPlainObject(b)) {
146495
+ if (isPlainObject3(a) && isPlainObject3(b)) {
146284
146496
  const bKeys = Object.keys(b);
146285
146497
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
146286
146498
  const newObj = { ...a, ...b };
@@ -146466,7 +146678,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
146466
146678
  $ZodType.init(inst, def);
146467
146679
  inst._zod.parse = (payload, ctx) => {
146468
146680
  const input = payload.value;
146469
- if (!isPlainObject(input)) {
146681
+ if (!isPlainObject3(input)) {
146470
146682
  payload.issues.push({
146471
146683
  expected: "record",
146472
146684
  code: "invalid_type",
@@ -157186,7 +157398,6 @@ var AgentOverrideConfigSchema = exports_external.object({
157186
157398
  });
157187
157399
 
157188
157400
  // ../plugin/src/config/schema/magic-context.ts
157189
- var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4;
157190
157401
  var DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65;
157191
157402
  var EXECUTE_THRESHOLD_CAP_MESSAGE = "execute_threshold is capped at 80% for cache safety: a single large agent step can overflow the context window before Magic Context can compact between turns, forcing OpenCode's native compaction (hard to recover from). 80% also leaves headroom below the 85%/95% emergency bands. Use a value between 20 and 80.";
157192
157403
  var DEFAULT_HISTORIAN_TIMEOUT_MS = 300000;
@@ -157284,7 +157495,6 @@ var MagicContextConfigSchema = exports_external.object({
157284
157495
  historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
157285
157496
  dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
157286
157497
  cache_ttl: exports_external.union([exports_external.string(), exports_external.object({ default: exports_external.string() }).catchall(exports_external.string())]).default("5m").describe('Cache TTL: string (e.g. "5m") or per-model object ({ default: "5m", "model-id": "10m" })'),
157287
- nudge_interval_tokens: exports_external.number().min(1000).default(DEFAULT_NUDGE_INTERVAL_TOKENS).describe("Minimum token growth between low-priority rolling nudges (default: DEFAULT_NUDGE_INTERVAL_TOKENS)"),
157288
157498
  execute_threshold_percentage: exports_external.union([
157289
157499
  exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE),
157290
157500
  exports_external.object({ default: exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE) }).catchall(exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE))
@@ -157293,10 +157503,7 @@ var MagicContextConfigSchema = exports_external.object({
157293
157503
  default: exports_external.number().min(5000).max(2000000).optional()
157294
157504
  }).catchall(exports_external.number().min(5000).max(2000000)).optional().describe("Absolute token thresholds per model. When matched, overrides execute_threshold_percentage for that model. Accepts `default` for all models or per-model keys. Values above 80% × context_limit are clamped with a warning log. Min 5_000, max 2_000_000."),
157295
157505
  protected_tags: exports_external.number().min(1).max(100).optional().describe("Number of recent tags to protect from dropping (min: 1, max: 100, default: 20)"),
157296
- auto_drop_tool_age: exports_external.number().min(10).default(100).describe("Auto-drop tool outputs older than N tags during queue execution (default: 100)"),
157297
- drop_tool_structure: exports_external.boolean().default(true).describe("When true, dropped tool parts are fully removed instead of truncated in place (default: true)"),
157298
157506
  clear_reasoning_age: exports_external.number().min(10).default(50).describe("Clear reasoning/thinking blocks older than N tags (default: 50)"),
157299
- iteration_nudge_threshold: exports_external.number().min(5).default(15).describe("Number of consecutive assistant messages without user input to trigger iteration nudge (default: 15)"),
157300
157507
  history_budget_percentage: exports_external.number().min(0.05).max(0.5).default(DEFAULT_HISTORY_BUDGET_PERCENTAGE).describe("Fraction of usable context (context_limit × execute_threshold) reserved for the session history block (default: 0.15)"),
157301
157508
  historian_timeout_ms: exports_external.number().min(60000).default(DEFAULT_HISTORIAN_TIMEOUT_MS).describe("Timeout for each historian prompt call in milliseconds (default: 300000)"),
157302
157509
  commit_cluster_trigger: exports_external.object({
@@ -157423,6 +157630,21 @@ function stripJsonComments(content) {
157423
157630
  // ../plugin/src/config/variable.ts
157424
157631
  var ENV_PATTERN = /\{env:([^}]+)\}/g;
157425
157632
  var FILE_PATTERN = /\{file:([^}]+)\}/g;
157633
+ function sensitiveFilePathReason(resolvedPath) {
157634
+ const home = homedir2();
157635
+ const sensitiveDirs = [
157636
+ { dir: resolve2(home, ".ssh"), label: "SSH keys" },
157637
+ { dir: resolve2(home, ".aws"), label: "AWS credentials" },
157638
+ { dir: resolve2(home, ".gnupg"), label: "GnuPG keyring" },
157639
+ { dir: resolve2(home, ".config", "gh"), label: "GitHub CLI auth" }
157640
+ ];
157641
+ for (const { dir, label } of sensitiveDirs) {
157642
+ if (resolvedPath === dir || resolvedPath.startsWith(`${dir}/`)) {
157643
+ return label;
157644
+ }
157645
+ }
157646
+ return null;
157647
+ }
157426
157648
  function substituteConfigVariables(input) {
157427
157649
  const warnings = [];
157428
157650
  let text = input.text;
@@ -157476,6 +157698,10 @@ function substituteConfigVariables(input) {
157476
157698
  } else if (!isAbsolute(filePath)) {
157477
157699
  filePath = resolve2(configDir, filePath);
157478
157700
  }
157701
+ const sensitiveReason = sensitiveFilePathReason(filePath);
157702
+ if (sensitiveReason) {
157703
+ warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
157704
+ }
157479
157705
  if (!existsSync4(filePath)) {
157480
157706
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
157481
157707
  continue;
@@ -157514,7 +157740,8 @@ function loadConfigFile(path3, scope) {
157514
157740
  const rawText = readFileSync3(path3, "utf-8");
157515
157741
  const substituted = substituteConfigVariables({
157516
157742
  text: rawText,
157517
- configPath: path3
157743
+ configPath: path3,
157744
+ isProjectConfig: scope === "project"
157518
157745
  });
157519
157746
  return {
157520
157747
  path: path3,
@@ -157556,14 +157783,14 @@ function redactConfigValue(value) {
157556
157783
  }
157557
157784
  return typeof value;
157558
157785
  }
157559
- function isPlainObject2(value) {
157786
+ function isPlainObject4(value) {
157560
157787
  return typeof value === "object" && value !== null && !Array.isArray(value);
157561
157788
  }
157562
157789
  function mergeRawConfigs(base, override) {
157563
157790
  const merged = { ...base };
157564
157791
  for (const [key, overrideValue] of Object.entries(override)) {
157565
157792
  const baseValue = merged[key];
157566
- merged[key] = isPlainObject2(baseValue) && isPlainObject2(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
157793
+ merged[key] = isPlainObject4(baseValue) && isPlainObject4(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
157567
157794
  }
157568
157795
  return merged;
157569
157796
  }
@@ -157577,10 +157804,15 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
157577
157804
  }
157578
157805
  const defaults = MagicContextConfigSchema.parse({});
157579
157806
  const errorPaths = new Set;
157807
+ const issuePathsByKey = new Map;
157580
157808
  for (const issue2 of parsed.error.issues) {
157581
157809
  const topKey = issue2.path[0];
157582
157810
  if (topKey !== undefined) {
157583
- errorPaths.add(String(topKey));
157811
+ const key = String(topKey);
157812
+ errorPaths.add(key);
157813
+ const paths = issuePathsByKey.get(key) ?? [];
157814
+ paths.push([...issue2.path]);
157815
+ issuePathsByKey.set(key, paths);
157584
157816
  }
157585
157817
  }
157586
157818
  const patched = { ...migrated };
@@ -157588,11 +157820,32 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
157588
157820
  for (const key of errorPaths) {
157589
157821
  recoveredTopLevelKeys.push(key);
157590
157822
  const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
157591
- delete patched[key];
157592
157823
  if (isAgentConfig) {
157824
+ delete patched[key];
157593
157825
  warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
157594
157826
  continue;
157595
157827
  }
157828
+ const issuePaths = issuePathsByKey.get(key) ?? [];
157829
+ const rawValue = migrated[key];
157830
+ const allNested = issuePaths.length > 0 && issuePaths.every((p) => p.length >= 2) && typeof rawValue === "object" && rawValue !== null && !Array.isArray(rawValue);
157831
+ if (allNested) {
157832
+ let prunedBlock = {
157833
+ ...rawValue
157834
+ };
157835
+ const prunedLeaves = [];
157836
+ for (const p of issuePaths) {
157837
+ const relative = p.slice(1);
157838
+ const result = pruneNestedConfigLeaf(prunedBlock, relative);
157839
+ if (result) {
157840
+ prunedBlock = result.block;
157841
+ prunedLeaves.push(result.removed);
157842
+ }
157843
+ }
157844
+ patched[key] = prunedBlock;
157845
+ warnings.push(`"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.`);
157846
+ continue;
157847
+ }
157848
+ delete patched[key];
157596
157849
  const defaultValue = defaults[key];
157597
157850
  warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultValue)}.`);
157598
157851
  }
@@ -157625,10 +157878,22 @@ function loadPiConfig(opts = {}) {
157625
157878
  return 0;
157626
157879
  return a.scope === "user" ? -1 : 1;
157627
157880
  });
157881
+ const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
157628
157882
  for (const loaded of mergeFiles) {
157629
157883
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
157630
157884
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
157631
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157885
+ if (loaded.scope === "project") {
157886
+ const projectRaw = { ...loaded.config };
157887
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
157888
+ warnings.push(`${prefix} ${warning}`);
157889
+ }
157890
+ rawConfig = mergeRawConfigs(rawConfig, projectRaw);
157891
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
157892
+ warnings.push(`${prefix} ${warning}`);
157893
+ }
157894
+ } else {
157895
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157896
+ }
157632
157897
  }
157633
157898
  const parsed = parsePiConfig(rawConfig);
157634
157899
  warnings.push(...parsed.warnings.map((warning) => `[merged config] ${warning}`));
@@ -157703,10 +157968,22 @@ function loadPiConfigDetailed(opts = {}) {
157703
157968
  return 0;
157704
157969
  return a.scope === "user" ? -1 : 1;
157705
157970
  });
157971
+ const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
157706
157972
  for (const loaded of mergeFiles) {
157707
157973
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
157708
157974
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
157709
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157975
+ if (loaded.scope === "project") {
157976
+ const projectRaw = { ...loaded.config };
157977
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
157978
+ warnings.push(`${prefix} ${warning}`);
157979
+ }
157980
+ rawConfig = mergeRawConfigs(rawConfig, projectRaw);
157981
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
157982
+ warnings.push(`${prefix} ${warning}`);
157983
+ }
157984
+ } else {
157985
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157986
+ }
157710
157987
  }
157711
157988
  const recoveredTopLevelKeys = [];
157712
157989
  const parsed = parsePiConfig(rawConfig, recoveredTopLevelKeys);
@@ -157733,6 +158010,9 @@ function loadPiConfigDetailed(opts = {}) {
157733
158010
  };
157734
158011
  }
157735
158012
 
158013
+ // ../plugin/src/features/magic-context/memory/embedding.ts
158014
+ init_logger();
158015
+
157736
158016
  // ../plugin/src/features/magic-context/memory/cosine-similarity.ts
157737
158017
  function cosineSimilarity(a, b) {
157738
158018
  if (a.length !== b.length) {
@@ -157761,7 +158041,7 @@ function computeNormalizedHash(content) {
157761
158041
  }
157762
158042
 
157763
158043
  // ../plugin/src/features/magic-context/memory/embedding-identity.ts
157764
- function normalizeEndpoint(endpoint) {
158044
+ function normalizeEndpoint2(endpoint) {
157765
158045
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
157766
158046
  }
157767
158047
  function getEmbeddingProviderIdentity(config2) {
@@ -157771,7 +158051,7 @@ function getEmbeddingProviderIdentity(config2) {
157771
158051
  const identityInput = config2.provider === "openai-compatible" ? {
157772
158052
  provider: "openai-compatible",
157773
158053
  model: config2.model.trim(),
157774
- endpoint: normalizeEndpoint(config2.endpoint),
158054
+ endpoint: normalizeEndpoint2(config2.endpoint),
157775
158055
  apiKeyPresent: Boolean(config2.api_key?.trim()),
157776
158056
  inputType: config2.input_type?.trim() || ""
157777
158057
  } : {
@@ -157788,6 +158068,8 @@ import { mkdirSync as mkdirSync3 } from "node:fs";
157788
158068
  import { open, stat, unlink, writeFile } from "node:fs/promises";
157789
158069
  import { dirname as dirname4, join as join6 } from "node:path";
157790
158070
  import { pathToFileURL } from "node:url";
158071
+ init_data_path();
158072
+ init_logger();
157791
158073
  var LOCK_POLL_MS = 150;
157792
158074
  var STALE_LOCK_MS = 3 * 60000;
157793
158075
  var MAX_LOCK_WAIT_MS = 5 * 60000;
@@ -158123,7 +158405,63 @@ class LocalEmbeddingProvider {
158123
158405
  }
158124
158406
 
158125
158407
  // ../plugin/src/features/magic-context/memory/embedding-openai.ts
158126
- function normalizeEndpoint2(endpoint) {
158408
+ init_logger();
158409
+
158410
+ // ../plugin/src/features/magic-context/memory/embedding-ssrf.ts
158411
+ var METADATA_HOSTNAMES = new Set(["metadata.google.internal", "metadata.goog"]);
158412
+ var IPV6_METADATA_HOSTS = new Set(["fd00:ec2::254"]);
158413
+ function isLinkLocalIpv4(host) {
158414
+ return /^169\.254\.\d{1,3}\.\d{1,3}$/.test(host);
158415
+ }
158416
+ function ipv4FromMappedIpv6(host) {
158417
+ const m = /^::ffff:(.+)$/.exec(host);
158418
+ if (!m)
158419
+ return null;
158420
+ const tail = m[1];
158421
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(tail))
158422
+ return tail;
158423
+ const hex3 = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(tail);
158424
+ if (hex3) {
158425
+ const hi = Number.parseInt(hex3[1], 16);
158426
+ const lo = Number.parseInt(hex3[2], 16);
158427
+ if (Number.isNaN(hi) || Number.isNaN(lo))
158428
+ return null;
158429
+ return `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
158430
+ }
158431
+ return null;
158432
+ }
158433
+ function blockedEmbeddingEndpointReason(endpoint) {
158434
+ const trimmed = endpoint.trim();
158435
+ if (trimmed.length === 0)
158436
+ return null;
158437
+ let url2;
158438
+ try {
158439
+ url2 = new URL(trimmed);
158440
+ } catch {
158441
+ return `embedding endpoint is not a valid URL: ${trimmed}`;
158442
+ }
158443
+ const host = url2.hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
158444
+ if (METADATA_HOSTNAMES.has(host)) {
158445
+ return `embedding endpoint host ${host} is a cloud metadata service (blocked)`;
158446
+ }
158447
+ if (IPV6_METADATA_HOSTS.has(host)) {
158448
+ return `embedding endpoint host ${host} is the AWS IPv6 metadata service (blocked)`;
158449
+ }
158450
+ if (isLinkLocalIpv4(host)) {
158451
+ return `embedding endpoint host ${host} is link-local / cloud metadata (blocked)`;
158452
+ }
158453
+ const mappedV4 = ipv4FromMappedIpv6(host);
158454
+ if (mappedV4 && isLinkLocalIpv4(mappedV4)) {
158455
+ return `embedding endpoint host ${host} (IPv4-mapped ${mappedV4}) is link-local / cloud metadata (blocked)`;
158456
+ }
158457
+ if (host.startsWith("fe80:")) {
158458
+ return `embedding endpoint host ${host} is link-local / cloud metadata (blocked)`;
158459
+ }
158460
+ return null;
158461
+ }
158462
+
158463
+ // ../plugin/src/features/magic-context/memory/embedding-openai.ts
158464
+ function normalizeEndpoint3(endpoint) {
158127
158465
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
158128
158466
  }
158129
158467
  var FAILURE_THRESHOLD = 3;
@@ -158144,7 +158482,7 @@ class OpenAICompatibleEmbeddingProvider {
158144
158482
  openLogged = false;
158145
158483
  halfOpenProbeInFlight = false;
158146
158484
  constructor(options) {
158147
- this.endpoint = normalizeEndpoint2(options.endpoint);
158485
+ this.endpoint = normalizeEndpoint3(options.endpoint);
158148
158486
  this.model = options.model?.trim() ?? "";
158149
158487
  this.apiKey = options.apiKey?.trim() ?? "";
158150
158488
  this.inputType = options.inputType?.trim() ?? "";
@@ -158164,6 +158502,12 @@ class OpenAICompatibleEmbeddingProvider {
158164
158502
  this.initialized = false;
158165
158503
  return false;
158166
158504
  }
158505
+ const blockedReason = blockedEmbeddingEndpointReason(this.endpoint);
158506
+ if (blockedReason) {
158507
+ log(`[magic-context] embedding endpoint blocked: ${blockedReason}`);
158508
+ this.initialized = false;
158509
+ return false;
158510
+ }
158167
158511
  this.initialized = true;
158168
158512
  return true;
158169
158513
  }
@@ -158209,6 +158553,7 @@ class OpenAICompatibleEmbeddingProvider {
158209
158553
  ...this.inputType ? { input_type: this.inputType } : {},
158210
158554
  ...this.truncate ? { truncate: this.truncate } : {}
158211
158555
  }),
158556
+ redirect: "error",
158212
158557
  signal: internalController.signal
158213
158558
  });
158214
158559
  if (!response.ok) {
@@ -158413,6 +158758,7 @@ function getDistinctStoredModelIds(db, projectPath) {
158413
158758
 
158414
158759
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
158415
158760
  import { createHash as createHash3, randomUUID } from "node:crypto";
158761
+ init_logger();
158416
158762
 
158417
158763
  // ../plugin/src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
158418
158764
  var saveStatements = new WeakMap;
@@ -158688,11 +159034,15 @@ function resolveEmbeddingConfig(config2) {
158688
159034
  }
158689
159035
  if (config2.provider === "openai-compatible") {
158690
159036
  const apiKey = config2.api_key?.trim();
159037
+ const inputType = config2.input_type?.trim();
159038
+ const truncate = config2.truncate?.trim();
158691
159039
  return {
158692
159040
  provider: "openai-compatible",
158693
159041
  model: config2.model.trim(),
158694
159042
  endpoint: config2.endpoint.trim(),
158695
- ...apiKey ? { api_key: apiKey } : {}
159043
+ ...apiKey ? { api_key: apiKey } : {},
159044
+ ...inputType ? { input_type: inputType } : {},
159045
+ ...truncate ? { truncate } : {}
158696
159046
  };
158697
159047
  }
158698
159048
  return { provider: "off" };
@@ -158708,7 +159058,9 @@ function createProvider(config2) {
158708
159058
  return new OpenAICompatibleEmbeddingProvider({
158709
159059
  endpoint: config2.endpoint,
158710
159060
  model: config2.model,
158711
- apiKey: config2.api_key
159061
+ apiKey: config2.api_key,
159062
+ inputType: config2.input_type,
159063
+ truncate: config2.truncate
158712
159064
  });
158713
159065
  }
158714
159066
  return new LocalEmbeddingProvider(config2.model);
@@ -159147,17 +159499,41 @@ function hasGitDir(canonical) {
159147
159499
  return false;
159148
159500
  }
159149
159501
  }
159502
+ function normalizeStoredProjectPath(rawOrStored) {
159503
+ if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
159504
+ return rawOrStored;
159505
+ }
159506
+ try {
159507
+ return resolveProjectIdentity(rawOrStored);
159508
+ } catch {
159509
+ return directoryFallback(rawOrStored);
159510
+ }
159511
+ }
159512
+ function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
159513
+ return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
159514
+ }
159150
159515
 
159151
159516
  // ../plugin/src/plugin/embedding-bootstrap-helpers.ts
159152
159517
  import { createHash as createHash5 } from "node:crypto";
159518
+ init_logger();
159153
159519
  var EMBEDDING_AFFECTING_KEYS = new Set([
159154
159520
  "embedding.api_key",
159155
159521
  "embedding.endpoint",
159156
159522
  "embedding.model",
159157
- "embedding.provider"
159523
+ "embedding.provider",
159524
+ "embedding.input_type",
159525
+ "embedding.truncate"
159158
159526
  ]);
159159
159527
  var EMBEDDING_AFFECTING_TOP_LEVEL_KEYS = new Set(["embedding", "memory", "experimental"]);
159160
- var EMBEDDING_WARNING_TERMS = ["api_key", "endpoint", "model", "provider", "embedding"];
159528
+ var EMBEDDING_WARNING_TERMS = [
159529
+ "api_key",
159530
+ "endpoint",
159531
+ "model",
159532
+ "provider",
159533
+ "embedding",
159534
+ "input_type",
159535
+ "truncate"
159536
+ ];
159161
159537
  var loggedFailureSignatures = new Map;
159162
159538
  function sha256Prefix2(value, length = 16) {
159163
159539
  return createHash5("sha256").update(value).digest("hex").slice(0, length);
@@ -159237,7 +159613,6 @@ async function ensureProjectRegisteredFromPiDirectory(directory, db) {
159237
159613
  };
159238
159614
  registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, detailed.config.embedding, features, directory);
159239
159615
  }
159240
-
159241
159616
  // ../plugin/src/features/magic-context/compartment-lease.ts
159242
159617
  var COMPARTMENT_LEASE_TTL_MS = 5 * 60 * 1000;
159243
159618
  var COMPARTMENT_LEASE_RENEWAL_MS = 60 * 1000;
@@ -159335,6 +159710,14 @@ var SESSION_META_SELECT_COLUMNS = [
159335
159710
  "cached_m0_tool_set_hash",
159336
159711
  "cached_m0_model_key",
159337
159712
  "last_observed_model_key",
159713
+ "last_usage_context_limit",
159714
+ "prior_boundary_ordinal",
159715
+ "protected_tail_policy_version",
159716
+ "protected_tail_drain_window_started_at",
159717
+ "protected_tail_drain_tokens",
159718
+ "recovery_no_eligible_head_count",
159719
+ "force_emergency_bypass_window_start",
159720
+ "force_emergency_bypass_used",
159338
159721
  "upgrade_reminded_at",
159339
159722
  "pi_stable_id_scheme"
159340
159723
  ];
@@ -159374,6 +159757,14 @@ var META_COLUMNS = {
159374
159757
  cachedM0ToolSetHash: "cached_m0_tool_set_hash",
159375
159758
  cachedM0ModelKey: "cached_m0_model_key",
159376
159759
  lastObservedModelKey: "last_observed_model_key",
159760
+ lastUsageContextLimit: "last_usage_context_limit",
159761
+ priorBoundaryOrdinal: "prior_boundary_ordinal",
159762
+ protectedTailPolicyVersion: "protected_tail_policy_version",
159763
+ protectedTailDrainWindowStartedAt: "protected_tail_drain_window_started_at",
159764
+ protectedTailDrainTokens: "protected_tail_drain_tokens",
159765
+ recoveryNoEligibleHeadCount: "recovery_no_eligible_head_count",
159766
+ forceEmergencyBypassWindowStart: "force_emergency_bypass_window_start",
159767
+ forceEmergencyBypassUsed: "force_emergency_bypass_used",
159377
159768
  upgradeRemindedAt: "upgrade_reminded_at",
159378
159769
  piStableIdScheme: "pi_stable_id_scheme"
159379
159770
  };
@@ -159415,7 +159806,7 @@ function isSessionMetaRow(row) {
159415
159806
  if (row === null || typeof row !== "object")
159416
159807
  return false;
159417
159808
  const r = row;
159418
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
159809
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
159419
159810
  }
159420
159811
  function getDefaultSessionMeta(sessionId) {
159421
159812
  return {
@@ -159455,6 +159846,14 @@ function getDefaultSessionMeta(sessionId) {
159455
159846
  cachedM0ToolSetHash: null,
159456
159847
  cachedM0ModelKey: null,
159457
159848
  lastObservedModelKey: null,
159849
+ lastUsageContextLimit: 0,
159850
+ priorBoundaryOrdinal: 1,
159851
+ protectedTailPolicyVersion: 0,
159852
+ protectedTailDrainWindowStartedAt: 0,
159853
+ protectedTailDrainTokens: 0,
159854
+ recoveryNoEligibleHeadCount: 0,
159855
+ forceEmergencyBypassWindowStart: 0,
159856
+ forceEmergencyBypassUsed: 0,
159458
159857
  upgradeRemindedAt: null,
159459
159858
  piStableIdScheme: null
159460
159859
  };
@@ -159509,6 +159908,14 @@ function toSessionMeta(row) {
159509
159908
  cachedM0ToolSetHash: stringOrNull(row.cached_m0_tool_set_hash),
159510
159909
  cachedM0ModelKey: stringOrNull(row.cached_m0_model_key),
159511
159910
  lastObservedModelKey: stringOrNull(row.last_observed_model_key),
159911
+ lastUsageContextLimit: numOrZero(row.last_usage_context_limit),
159912
+ priorBoundaryOrdinal: Math.max(1, numOrZero(row.prior_boundary_ordinal) || 1),
159913
+ protectedTailPolicyVersion: numOrZero(row.protected_tail_policy_version),
159914
+ protectedTailDrainWindowStartedAt: numOrZero(row.protected_tail_drain_window_started_at),
159915
+ protectedTailDrainTokens: numOrZero(row.protected_tail_drain_tokens),
159916
+ recoveryNoEligibleHeadCount: numOrZero(row.recovery_no_eligible_head_count),
159917
+ forceEmergencyBypassWindowStart: numOrZero(row.force_emergency_bypass_window_start),
159918
+ forceEmergencyBypassUsed: numOrZero(row.force_emergency_bypass_used),
159512
159919
  upgradeRemindedAt: numOrNull(row.upgrade_reminded_at),
159513
159920
  piStableIdScheme: numOrNull(row.pi_stable_id_scheme)
159514
159921
  };
@@ -159593,12 +160000,6 @@ function isCompartmentRow(row) {
159593
160000
  const candidate = row;
159594
160001
  return typeof candidate.id === "number" && typeof candidate.session_id === "string" && typeof candidate.sequence === "number" && typeof candidate.start_message === "number" && typeof candidate.end_message === "number" && typeof candidate.start_message_id === "string" && typeof candidate.end_message_id === "string" && typeof candidate.title === "string" && typeof candidate.content === "string" && isStringOrNullish(candidate.p1) && isStringOrNullish(candidate.p2) && isStringOrNullish(candidate.p3) && isStringOrNullish(candidate.p4) && isNumberOrNullish(candidate.importance) && isStringOrNullish(candidate.episode_type) && isNumberOrNullish(candidate.legacy) && typeof candidate.created_at === "number";
159595
160002
  }
159596
- function isSessionFactRow(row) {
159597
- if (row === null || typeof row !== "object")
159598
- return false;
159599
- const candidate = row;
159600
- 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";
159601
- }
159602
160003
  function insertCompartmentRows(db, sessionId, compartments, now) {
159603
160004
  const stmt = getInsertCompartmentStatement(db);
159604
160005
  for (const compartment of compartments) {
@@ -159627,16 +160028,6 @@ function toCompartment(row) {
159627
160028
  createdAt: row.created_at
159628
160029
  };
159629
160030
  }
159630
- function toSessionFact(row) {
159631
- return {
159632
- id: row.id,
159633
- sessionId: row.session_id,
159634
- category: row.category,
159635
- content: row.content,
159636
- createdAt: row.created_at,
159637
- updatedAt: row.updated_at
159638
- };
159639
- }
159640
160031
  function getCompartments(db, sessionId) {
159641
160032
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? ORDER BY sequence ASC").all(sessionId).filter(isCompartmentRow);
159642
160033
  return rows.map(toCompartment);
@@ -159645,6 +160036,11 @@ function getLastCompartmentEndMessage(db, sessionId) {
159645
160036
  const row = db.prepare("SELECT MAX(end_message) as max_end FROM compartments WHERE session_id = ?").get(sessionId);
159646
160037
  return row?.max_end ?? -1;
159647
160038
  }
160039
+ function getLastCompartmentEndMessageId(db, sessionId) {
160040
+ const row = db.prepare("SELECT end_message_id FROM compartments WHERE session_id = ? ORDER BY sequence DESC LIMIT 1").get(sessionId);
160041
+ const id = row?.end_message_id;
160042
+ return id && id.length > 0 ? id : null;
160043
+ }
159648
160044
  function getCompartmentsByEndMessageId(db, sessionId, endMessageId) {
159649
160045
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? AND end_message_id = ? ORDER BY sequence ASC").all(sessionId, endMessageId).filter(isCompartmentRow);
159650
160046
  return rows.map(toCompartment);
@@ -159657,10 +160053,6 @@ function appendCompartments(db, sessionId, compartments) {
159657
160053
  insertCompartmentRows(db, sessionId, compartments, now);
159658
160054
  })();
159659
160055
  }
159660
- function getSessionFacts(db, sessionId) {
159661
- const rows = db.prepare("SELECT * FROM session_facts WHERE session_id = ? ORDER BY category ASC, id ASC").all(sessionId).filter(isSessionFactRow);
159662
- return rows.map(toSessionFact);
159663
- }
159664
160056
  function saveRecompStagingPass(db, sessionId, passNumber, compartments, facts) {
159665
160057
  const now = Date.now();
159666
160058
  db.transaction(() => {
@@ -159704,14 +160096,6 @@ function getRecompStaging(db, sessionId) {
159704
160096
  lastEndMessage: lastEnd
159705
160097
  };
159706
160098
  }
159707
- function invalidateAllMemoryBlockCaches(db) {
159708
- try {
159709
- const rows = db.prepare("SELECT session_id FROM session_meta").all();
159710
- for (const row of rows) {
159711
- clearCachedM0M1(db, row.session_id);
159712
- }
159713
- } catch {}
159714
- }
159715
160099
  function clearRecompStaging(db, sessionId) {
159716
160100
  db.transaction(() => {
159717
160101
  db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
@@ -159759,6 +160143,8 @@ function escapeXmlContent(s) {
159759
160143
  }
159760
160144
 
159761
160145
  // ../plugin/src/hooks/magic-context/read-session-db.ts
160146
+ init_data_path();
160147
+ init_logger();
159762
160148
  import { existsSync as existsSync6 } from "node:fs";
159763
160149
  import { join as join7 } from "node:path";
159764
160150
  function getOpenCodeDbPath() {
@@ -159831,13 +160217,27 @@ function parseJsonUnknown(value) {
159831
160217
  return null;
159832
160218
  }
159833
160219
  }
160220
+ function attachRawPartVersion(value, timeUpdated) {
160221
+ if (value === null || typeof value !== "object" || Array.isArray(value))
160222
+ return value;
160223
+ if (typeof timeUpdated !== "number")
160224
+ return value;
160225
+ try {
160226
+ Object.defineProperty(value, "__magicContextPartUpdatedAt", {
160227
+ value: timeUpdated,
160228
+ enumerable: false,
160229
+ configurable: true
160230
+ });
160231
+ } catch {}
160232
+ return value;
160233
+ }
159834
160234
  function readRawSessionMessagesFromDb(db, sessionId) {
159835
- const messageRows = db.prepare("SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
159836
- const partRows = db.prepare("SELECT message_id, data FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
160235
+ const messageRows = db.prepare("SELECT id, data, time_updated FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
160236
+ const partRows = db.prepare("SELECT message_id, data, time_updated FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
159837
160237
  const partsByMessageId = new Map;
159838
160238
  for (const part of partRows) {
159839
160239
  const list = partsByMessageId.get(part.message_id) ?? [];
159840
- list.push(parseJsonUnknown(part.data));
160240
+ list.push(attachRawPartVersion(parseJsonUnknown(part.data), part.time_updated));
159841
160241
  partsByMessageId.set(part.message_id, list);
159842
160242
  }
159843
160243
  const filtered = messageRows.filter((row) => {
@@ -159853,10 +160253,63 @@ function readRawSessionMessagesFromDb(db, sessionId) {
159853
160253
  ordinal: index + 1,
159854
160254
  id: row.id,
159855
160255
  role,
159856
- parts: partsByMessageId.get(row.id) ?? []
160256
+ parts: partsByMessageId.get(row.id) ?? [],
160257
+ version: row.time_updated ?? null
159857
160258
  };
159858
160259
  });
159859
160260
  }
160261
+ function isAnchorRow(row) {
160262
+ return row !== null && typeof row === "object" && typeof row.time_created === "number" && typeof row.id === "string";
160263
+ }
160264
+ function readRawSessionTailFromDb(db, sessionId, baseOrdinal, anchorMessageId) {
160265
+ const anchorRow = db.prepare("SELECT time_created, id, data FROM message WHERE id = ? AND session_id = ?").get(anchorMessageId, sessionId);
160266
+ if (!isAnchorRow(anchorRow))
160267
+ return null;
160268
+ const anchorInfo = parseJsonRecord(anchorRow.data ?? "");
160269
+ if (anchorInfo?.summary === true && anchorInfo?.finish === "stop")
160270
+ return null;
160271
+ const messageRows = db.prepare(`SELECT id, data, time_updated FROM message
160272
+ WHERE session_id = ?
160273
+ AND (time_created > ? OR (time_created = ? AND id >= ?))
160274
+ ORDER BY time_created ASC, id ASC`).all(sessionId, anchorRow.time_created, anchorRow.time_created, anchorRow.id).filter(isRawMessageRow);
160275
+ const filtered = messageRows.filter((row) => {
160276
+ const info = parseJsonRecord(row.data);
160277
+ return !(info?.summary === true && info?.finish === "stop");
160278
+ });
160279
+ const ids = filtered.map((row) => row.id);
160280
+ const partsByMessageId = new Map;
160281
+ if (ids.length > 0) {
160282
+ const CHUNK = 800;
160283
+ for (let i = 0;i < ids.length; i += CHUNK) {
160284
+ const slice = ids.slice(i, i + CHUNK);
160285
+ const placeholders = slice.map(() => "?").join(",");
160286
+ const partRows = db.prepare(`SELECT message_id, data, time_updated FROM part WHERE session_id = ? AND message_id IN (${placeholders}) ORDER BY time_created ASC, id ASC`).all(sessionId, ...slice).filter(isRawPartRow);
160287
+ for (const part of partRows) {
160288
+ const list = partsByMessageId.get(part.message_id) ?? [];
160289
+ list.push(attachRawPartVersion(parseJsonUnknown(part.data), part.time_updated));
160290
+ partsByMessageId.set(part.message_id, list);
160291
+ }
160292
+ }
160293
+ }
160294
+ const messages = [];
160295
+ let ord = baseOrdinal;
160296
+ for (const row of filtered) {
160297
+ const info = parseJsonRecord(row.data);
160298
+ if (!info) {
160299
+ ord += 1;
160300
+ continue;
160301
+ }
160302
+ messages.push({
160303
+ ordinal: ord,
160304
+ id: row.id,
160305
+ role: typeof info.role === "string" ? info.role : "unknown",
160306
+ parts: partsByMessageId.get(row.id) ?? [],
160307
+ version: row.time_updated ?? null
160308
+ });
160309
+ ord += 1;
160310
+ }
160311
+ return { messages, absoluteMessageCount: Math.max(0, ord - 1) };
160312
+ }
159860
160313
 
159861
160314
  // ../plugin/src/hooks/magic-context/tag-content-primitives.ts
159862
160315
  var encoder = new TextEncoder;
@@ -160001,6 +160454,7 @@ class ToolMutationBatch {
160001
160454
 
160002
160455
  // ../plugin/src/hooks/magic-context/read-session-chunk.ts
160003
160456
  var activeRawMessageCache = null;
160457
+ var activeAbsoluteCountCache = null;
160004
160458
  var sessionProviders = new Map;
160005
160459
  function setRawMessageProvider(sessionId, provider2) {
160006
160460
  sessionProviders.set(sessionId, provider2);
@@ -160032,12 +160486,14 @@ function withRawSessionMessageCache(fn) {
160032
160486
  const outerCache = activeRawMessageCache;
160033
160487
  if (!outerCache) {
160034
160488
  activeRawMessageCache = new Map;
160489
+ activeAbsoluteCountCache = new Map;
160035
160490
  }
160036
160491
  try {
160037
160492
  return fn();
160038
160493
  } finally {
160039
160494
  if (!outerCache) {
160040
160495
  activeRawMessageCache = null;
160496
+ activeAbsoluteCountCache = null;
160041
160497
  }
160042
160498
  }
160043
160499
  }
@@ -160053,6 +160509,40 @@ function readRawSessionMessages(sessionId) {
160053
160509
  }
160054
160510
  return readRawSessionMessagesFromSource(sessionId);
160055
160511
  }
160512
+ function primeTailRawMessageCache(args) {
160513
+ const { sessionId, lastCompartmentEnd, anchorMessageId } = args;
160514
+ if (!activeRawMessageCache)
160515
+ return false;
160516
+ if (activeRawMessageCache.has(sessionId))
160517
+ return false;
160518
+ if (sessionProviders.has(sessionId))
160519
+ return false;
160520
+ if (!openCodeDbExists())
160521
+ return false;
160522
+ if (lastCompartmentEnd < 1 || !anchorMessageId)
160523
+ return false;
160524
+ const result = withReadOnlySessionDb((db) => readRawSessionTailFromDb(db, sessionId, lastCompartmentEnd, anchorMessageId));
160525
+ if (!result)
160526
+ return false;
160527
+ activeRawMessageCache.set(sessionId, result.messages);
160528
+ activeAbsoluteCountCache?.set(sessionId, result.absoluteMessageCount);
160529
+ return true;
160530
+ }
160531
+ function getCachedAbsoluteMessageCount(sessionId) {
160532
+ return activeAbsoluteCountCache?.get(sessionId) ?? null;
160533
+ }
160534
+ function primeInMemoryTailRawMessageCache(args) {
160535
+ const { sessionId, messages, absoluteMessageCount } = args;
160536
+ if (!activeRawMessageCache)
160537
+ return false;
160538
+ if (activeRawMessageCache.has(sessionId))
160539
+ return false;
160540
+ if (sessionProviders.has(sessionId))
160541
+ return false;
160542
+ activeRawMessageCache.set(sessionId, messages);
160543
+ activeAbsoluteCountCache?.set(sessionId, absoluteMessageCount);
160544
+ return true;
160545
+ }
160056
160546
  function readRawSessionMessagesFromSource(sessionId) {
160057
160547
  const provider2 = sessionProviders.get(sessionId);
160058
160548
  if (provider2)
@@ -160117,7 +160607,7 @@ function getRawSessionTagKeysThrough(sessionId, upToMessageIndex) {
160117
160607
  return { messageFileKeys, toolObservations };
160118
160608
  }
160119
160609
  var PROTECTED_TAIL_USER_TURNS = 5;
160120
- function getProtectedTailStartOrdinal(sessionId) {
160610
+ function getLegacyProtectedTailStartOrdinal(sessionId) {
160121
160611
  const messages = readRawSessionMessages(sessionId);
160122
160612
  const userOrdinals = messages.filter((m) => m.role === "user" && hasMeaningfulUserText(m.parts)).map((m) => m.ordinal);
160123
160613
  if (userOrdinals.length < PROTECTED_TAIL_USER_TURNS) {
@@ -160127,6 +160617,7 @@ function getProtectedTailStartOrdinal(sessionId) {
160127
160617
  }
160128
160618
  function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal) {
160129
160619
  const messages = readRawSessionMessages(sessionId);
160620
+ const totalMessageCount = getCachedAbsoluteMessageCount(sessionId) ?? messages.length;
160130
160621
  const startOrdinal = Math.max(1, offset);
160131
160622
  const lines = [];
160132
160623
  const lineMeta = [];
@@ -160254,7 +160745,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
160254
160745
  endMessageId: lastMessageId,
160255
160746
  messageCount: messagesProcessed,
160256
160747
  tokenEstimate: totalTokens,
160257
- hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, messages.length) : messages.length),
160748
+ hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
160258
160749
  text: lines.join(`
160259
160750
  `),
160260
160751
  lines: lineMeta,
@@ -160264,7 +160755,13 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
160264
160755
  }
160265
160756
 
160266
160757
  // ../plugin/src/tools/ctx-expand/constants.ts
160267
- var CTX_EXPAND_DESCRIPTION = "Decompress a compartment range to see the original conversation transcript. " + 'Use start/end from <compartment start="N" end="M"> attributes. ' + "Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.";
160758
+ var CTX_EXPAND_DESCRIPTION = `Recover the original conversation from your compacted history.
160759
+
160760
+ Older parts of this session are summarized into <compartment> blocks inside <session-history> — e.g. <compartment start="120" end="245" title="Fixed tagger collision">. Each one replaces the raw messages in that ordinal range with a summary. When the summary isn't enough — you need exact wording, a specific value, an error message, or the reasoning behind a decision — expand the range:
160761
+
160762
+ ctx_expand(start=120, end=245) ← the compartment's own start/end attributes
160763
+
160764
+ Returns the raw transcript as [N] U:/A: lines, capped at ~15K tokens; an oversized range returns the head and tells you where to continue. Also works with ordinals from ctx_search message results — expand a window around a hit (e.g. start=N-10, end=N+5). Ranges after the last compartment are your live tail — already visible in context, not expandable.`;
160268
160765
  var CTX_EXPAND_TOKEN_BUDGET = 15000;
160269
160766
 
160270
160767
  // ../../node_modules/.bun/typebox@1.1.38/node_modules/typebox/build/system/memory/memory.mjs
@@ -164433,11 +164930,31 @@ function findLastModelKeyFromBranch(entries) {
164433
164930
  }
164434
164931
  return;
164435
164932
  }
164933
+ function rawEntryVersion(entry) {
164934
+ const record4 = entry;
164935
+ const updated = record4.updatedAt ?? record4.updated_at ?? record4.timestamp;
164936
+ return typeof updated === "string" || typeof updated === "number" ? updated : entry.id;
164937
+ }
164938
+ function attachPiPartVersion(parts, version2) {
164939
+ return parts.map((part) => {
164940
+ if (part === null || typeof part !== "object" || Array.isArray(part))
164941
+ return part;
164942
+ try {
164943
+ Object.defineProperty(part, "__magicContextPartUpdatedAt", {
164944
+ value: version2,
164945
+ enumerable: false,
164946
+ configurable: true
164947
+ });
164948
+ } catch {}
164949
+ return part;
164950
+ });
164951
+ }
164436
164952
  function convertEntriesToRawMessages(entries) {
164437
164953
  const result = [];
164438
164954
  let nextOrdinal = 1;
164439
164955
  let pendingToolParts = [];
164440
164956
  let pendingFirstRealId = "";
164957
+ let pendingFirstRealVersion = "";
164441
164958
  for (const entry of entries) {
164442
164959
  if (!isMessageEntry(entry)) {
164443
164960
  continue;
@@ -164445,24 +164962,29 @@ function convertEntriesToRawMessages(entries) {
164445
164962
  const msg = entry.message;
164446
164963
  const role = msg.role;
164447
164964
  if (role === "toolResult") {
164448
- pendingToolParts.push(...synthesizeToolResultParts(msg));
164965
+ const version2 = rawEntryVersion(entry);
164966
+ pendingToolParts.push(...attachPiPartVersion(synthesizeToolResultParts(msg), version2));
164449
164967
  if (pendingFirstRealId === "") {
164450
164968
  pendingFirstRealId = entry.id;
164969
+ pendingFirstRealVersion = version2;
164451
164970
  }
164452
164971
  continue;
164453
164972
  }
164454
164973
  if (role === "user") {
164974
+ const version2 = rawEntryVersion(entry);
164455
164975
  const parts = [
164456
164976
  ...pendingToolParts,
164457
- ...synthesizeUserParts(msg)
164977
+ ...attachPiPartVersion(synthesizeUserParts(msg), version2)
164458
164978
  ];
164459
164979
  pendingToolParts = [];
164460
164980
  pendingFirstRealId = "";
164981
+ pendingFirstRealVersion = "";
164461
164982
  result.push({
164462
164983
  ordinal: nextOrdinal++,
164463
164984
  id: entry.id,
164464
164985
  role: "user",
164465
- parts
164986
+ parts,
164987
+ version: version2
164466
164988
  });
164467
164989
  continue;
164468
164990
  }
@@ -164472,16 +164994,20 @@ function convertEntriesToRawMessages(entries) {
164472
164994
  ordinal: nextOrdinal++,
164473
164995
  id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
164474
164996
  role: "user",
164475
- parts: pendingToolParts
164997
+ parts: pendingToolParts,
164998
+ version: pendingFirstRealVersion
164476
164999
  });
164477
165000
  pendingToolParts = [];
164478
165001
  pendingFirstRealId = "";
165002
+ pendingFirstRealVersion = "";
164479
165003
  }
165004
+ const version2 = rawEntryVersion(entry);
164480
165005
  result.push({
164481
165006
  ordinal: nextOrdinal++,
164482
165007
  id: entry.id,
164483
165008
  role: "assistant",
164484
- parts: synthesizeAssistantParts(msg)
165009
+ parts: attachPiPartVersion(synthesizeAssistantParts(msg), version2),
165010
+ version: version2
164485
165011
  });
164486
165012
  continue;
164487
165013
  }
@@ -164489,7 +165015,8 @@ function convertEntriesToRawMessages(entries) {
164489
165015
  ordinal: nextOrdinal++,
164490
165016
  id: entry.id,
164491
165017
  role: typeof role === "string" ? role : "unknown",
164492
- parts: []
165018
+ parts: [],
165019
+ version: rawEntryVersion(entry)
164493
165020
  });
164494
165021
  }
164495
165022
  if (pendingToolParts.length > 0) {
@@ -164497,7 +165024,8 @@ function convertEntriesToRawMessages(entries) {
164497
165024
  ordinal: nextOrdinal,
164498
165025
  id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
164499
165026
  role: "user",
164500
- parts: pendingToolParts
165027
+ parts: pendingToolParts,
165028
+ version: pendingFirstRealVersion
164501
165029
  });
164502
165030
  }
164503
165031
  return result;
@@ -164654,6 +165182,13 @@ function createCtxExpandTool(deps) {
164654
165182
  }
164655
165183
 
164656
165184
  // ../plugin/src/features/magic-context/memory/constants.ts
165185
+ var V2_MEMORY_CATEGORIES = [
165186
+ "PROJECT_RULES",
165187
+ "ARCHITECTURE",
165188
+ "CONSTRAINTS",
165189
+ "CONFIG_VALUES",
165190
+ "NAMING"
165191
+ ];
164657
165192
  var PROMOTABLE_CATEGORIES = [
164658
165193
  "PROJECT_RULES",
164659
165194
  "ARCHITECTURE",
@@ -164693,6 +165228,7 @@ var CATEGORY_DEFAULT_TTL = {
164693
165228
  KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
164694
165229
  };
164695
165230
  // ../plugin/src/features/magic-context/memory/embedding-backfill.ts
165231
+ init_logger();
164696
165232
  async function ensureMemoryEmbeddings(args) {
164697
165233
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
164698
165234
  if (!snapshot?.enabled) {
@@ -164730,6 +165266,9 @@ async function ensureMemoryEmbeddings(args) {
164730
165266
  }
164731
165267
  return args.existingEmbeddings;
164732
165268
  }
165269
+ // ../plugin/src/features/magic-context/memory/promotion.ts
165270
+ init_logger();
165271
+
164733
165272
  // ../plugin/src/features/magic-context/memory/storage-memory.ts
164734
165273
  var COLUMN_MAP = {
164735
165274
  id: "id",
@@ -165401,7 +165940,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
165401
165940
  }
165402
165941
  // ../plugin/src/features/magic-context/project-docs-hash.ts
165403
165942
  import { createHash as createHash6 } from "node:crypto";
165404
- import { readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
165943
+ import { lstatSync, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
165405
165944
  import path4 from "node:path";
165406
165945
  var PROJECT_DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
165407
165946
  var PROJECT_DOCS_DELIMITER = `
@@ -165409,6 +165948,7 @@ var PROJECT_DOCS_DELIMITER = `
165409
165948
  ---
165410
165949
 
165411
165950
  `;
165951
+ var MAX_PROJECT_DOC_BYTES = 256 * 1024;
165412
165952
  var docsCache = new Map;
165413
165953
  function canonicalizeDocContent(raw) {
165414
165954
  return raw.replace(/^\uFEFF/, "").replace(/\r\n/g, `
@@ -165418,9 +165958,10 @@ function canonicalizeDocContent(raw) {
165418
165958
  }
165419
165959
  function fingerprintFile(filePath) {
165420
165960
  try {
165421
- const stat2 = statSync2(filePath);
165961
+ const stat2 = lstatSync(filePath);
165962
+ const isReadableDoc = stat2.isFile() && stat2.size <= MAX_PROJECT_DOC_BYTES;
165422
165963
  return {
165423
- exists: stat2.isFile(),
165964
+ exists: isReadableDoc,
165424
165965
  mtimeMs: stat2.mtimeMs,
165425
165966
  size: stat2.size
165426
165967
  };
@@ -165463,6 +166004,16 @@ function readCanonicalPieces(projectDirectory, files) {
165463
166004
  if (!fingerprint?.exists) {
165464
166005
  continue;
165465
166006
  }
166007
+ let safeToRead = false;
166008
+ try {
166009
+ const st = lstatSync(filePath);
166010
+ safeToRead = st.isFile() && st.size <= MAX_PROJECT_DOC_BYTES;
166011
+ } catch {
166012
+ safeToRead = false;
166013
+ }
166014
+ if (!safeToRead) {
166015
+ continue;
166016
+ }
165466
166017
  const canonicalContent = canonicalizeDocContent(readFileSync4(filePath, "utf8"));
165467
166018
  hashPieces.push(`file:${filename}
165468
166019
  ${canonicalContent}`);
@@ -165629,6 +166180,7 @@ function getMaxMemoryMutationId(db, projectPath) {
165629
166180
  return row?.max_id ?? null;
165630
166181
  }
165631
166182
  // ../plugin/src/features/magic-context/storage-meta-persisted.ts
166183
+ init_logger();
165632
166184
  var CAS_RETRY_LIMIT = 5;
165633
166185
  var AUTO_SEARCH_NO_HINT_REASONS = new Set([
165634
166186
  "below-threshold",
@@ -165638,12 +166190,6 @@ var AUTO_SEARCH_NO_HINT_REASONS = new Set([
165638
166190
  "stacked",
165639
166191
  "too-short"
165640
166192
  ]);
165641
- function isPersistedStickyTurnReminderRow(row) {
165642
- if (row === null || typeof row !== "object")
165643
- return false;
165644
- const r = row;
165645
- return typeof r.sticky_turn_reminder_text === "string" && typeof r.sticky_turn_reminder_message_id === "string";
165646
- }
165647
166193
  function isPersistedNoteNudgeRow(row) {
165648
166194
  if (row === null || typeof row !== "object")
165649
166195
  return false;
@@ -165709,6 +166255,139 @@ function getDefaultHistorianFailureState() {
165709
166255
  lastFailureAt: null
165710
166256
  };
165711
166257
  }
166258
+ var DEFAULT_PROTECTED_TAIL_META = {
166259
+ priorBoundaryOrdinal: 1,
166260
+ protectedTailPolicyVersion: 0,
166261
+ protectedTailDrainWindowStartedAt: 0,
166262
+ protectedTailDrainTokens: 0,
166263
+ recoveryNoEligibleHeadCount: 0,
166264
+ forceEmergencyBypassWindowStart: 0,
166265
+ forceEmergencyBypassUsed: 0
166266
+ };
166267
+ function toProtectedTailMeta(row) {
166268
+ if (row === null || typeof row !== "object")
166269
+ return { ...DEFAULT_PROTECTED_TAIL_META };
166270
+ const r = row;
166271
+ const numberOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
166272
+ return {
166273
+ priorBoundaryOrdinal: Math.max(1, numberOr(r.prior_boundary_ordinal, 1)),
166274
+ protectedTailPolicyVersion: numberOr(r.protected_tail_policy_version, 0),
166275
+ protectedTailDrainWindowStartedAt: numberOr(r.protected_tail_drain_window_started_at, 0),
166276
+ protectedTailDrainTokens: numberOr(r.protected_tail_drain_tokens, 0),
166277
+ recoveryNoEligibleHeadCount: numberOr(r.recovery_no_eligible_head_count, 0),
166278
+ forceEmergencyBypassWindowStart: numberOr(r.force_emergency_bypass_window_start, 0),
166279
+ forceEmergencyBypassUsed: numberOr(r.force_emergency_bypass_used, 0)
166280
+ };
166281
+ }
166282
+ function loadProtectedTailMeta(db, sessionId) {
166283
+ ensureSessionMetaRow(db, sessionId);
166284
+ const row = db.prepare(`SELECT prior_boundary_ordinal, protected_tail_policy_version,
166285
+ protected_tail_drain_window_started_at, protected_tail_drain_tokens,
166286
+ recovery_no_eligible_head_count, force_emergency_bypass_window_start,
166287
+ force_emergency_bypass_used
166288
+ FROM session_meta WHERE session_id = ?`).get(sessionId);
166289
+ return toProtectedTailMeta(row);
166290
+ }
166291
+ function markProtectedTailPolicyV3Seeded(db, sessionId, priorBoundaryOrdinal) {
166292
+ let seeded = false;
166293
+ db.transaction(() => {
166294
+ ensureSessionMetaRow(db, sessionId);
166295
+ const existing = loadProtectedTailMeta(db, sessionId);
166296
+ if (existing.protectedTailPolicyVersion < 3) {
166297
+ db.prepare(`UPDATE session_meta
166298
+ SET prior_boundary_ordinal = ?, protected_tail_policy_version = 3
166299
+ WHERE session_id = ? AND protected_tail_policy_version < 3`).run(Math.max(1, Math.floor(priorBoundaryOrdinal)), sessionId);
166300
+ seeded = true;
166301
+ }
166302
+ })();
166303
+ return { ...loadProtectedTailMeta(db, sessionId), seeded };
166304
+ }
166305
+ function recordProtectedTailPublicationFloor(db, sessionId, floorOrdinal) {
166306
+ db.transaction(() => {
166307
+ ensureSessionMetaRow(db, sessionId);
166308
+ db.prepare(`UPDATE session_meta
166309
+ SET prior_boundary_ordinal = MAX(COALESCE(prior_boundary_ordinal, 1), ?),
166310
+ recovery_no_eligible_head_count = 0
166311
+ WHERE session_id = ?`).run(Math.max(1, Math.floor(floorOrdinal)), sessionId);
166312
+ })();
166313
+ }
166314
+ function recordProtectedTailNoEligibleHead(db, sessionId) {
166315
+ db.transaction(() => {
166316
+ ensureSessionMetaRow(db, sessionId);
166317
+ db.prepare(`UPDATE session_meta
166318
+ SET recovery_no_eligible_head_count = COALESCE(recovery_no_eligible_head_count, 0) + 1
166319
+ WHERE session_id = ?`).run(sessionId);
166320
+ })();
166321
+ return loadProtectedTailMeta(db, sessionId).recoveryNoEligibleHeadCount;
166322
+ }
166323
+ var DRAIN_WINDOW_MS = 10 * 60 * 1000;
166324
+ function protectedTailWindowBudget(usagePercentage, usable, perRunCap) {
166325
+ if (usagePercentage >= 95)
166326
+ return Math.min(1e6, Math.max(4 * perRunCap, Math.round(0.5 * usable)));
166327
+ if (usagePercentage >= 80)
166328
+ return Math.min(750000, Math.max(3 * perRunCap, Math.round(0.35 * usable)));
166329
+ return Math.min(500000, Math.max(perRunCap, Math.round(0.2 * usable)));
166330
+ }
166331
+ function reserveProtectedTailDrainTokens(args) {
166332
+ const now = args.now ?? Date.now();
166333
+ const requested = Math.max(0, Math.floor(args.trueRawTokens));
166334
+ if (requested === 0) {
166335
+ return { ok: true, reservedTokens: 0, overQuotaBypass: false, reservation: null };
166336
+ }
166337
+ let result = {
166338
+ ok: false,
166339
+ reservedTokens: 0,
166340
+ overQuotaBypass: false,
166341
+ reservation: null,
166342
+ skippedReason: "quota exhausted"
166343
+ };
166344
+ args.db.transaction(() => {
166345
+ ensureSessionMetaRow(args.db, args.sessionId);
166346
+ let meta3 = loadProtectedTailMeta(args.db, args.sessionId);
166347
+ if (now - meta3.protectedTailDrainWindowStartedAt > DRAIN_WINDOW_MS) {
166348
+ args.db.prepare(`UPDATE session_meta
166349
+ SET protected_tail_drain_window_started_at = ?, protected_tail_drain_tokens = 0,
166350
+ force_emergency_bypass_window_start = ?, force_emergency_bypass_used = 0
166351
+ WHERE session_id = ?`).run(now, now, args.sessionId);
166352
+ meta3 = loadProtectedTailMeta(args.db, args.sessionId);
166353
+ }
166354
+ const budget = protectedTailWindowBudget(args.usagePercentage, args.usable, args.perRunCap);
166355
+ const remaining = Math.max(0, budget - meta3.protectedTailDrainTokens);
166356
+ let reserved = Math.min(requested, args.perRunCap, remaining);
166357
+ let bypass = false;
166358
+ const bypassWindowExpired = now - meta3.forceEmergencyBypassWindowStart > DRAIN_WINDOW_MS;
166359
+ const bypassUsed = bypassWindowExpired ? 0 : meta3.forceEmergencyBypassUsed;
166360
+ if (reserved <= 0 && args.usagePercentage >= 95 && bypassUsed === 0) {
166361
+ reserved = Math.min(requested, args.perRunCap);
166362
+ bypass = true;
166363
+ }
166364
+ if (reserved <= 0)
166365
+ return;
166366
+ args.db.prepare(`UPDATE session_meta
166367
+ SET protected_tail_drain_window_started_at = CASE WHEN protected_tail_drain_window_started_at = 0 THEN ? ELSE protected_tail_drain_window_started_at END,
166368
+ protected_tail_drain_tokens = COALESCE(protected_tail_drain_tokens, 0) + ?,
166369
+ force_emergency_bypass_window_start = CASE WHEN ? THEN ? ELSE force_emergency_bypass_window_start END,
166370
+ force_emergency_bypass_used = CASE WHEN ? THEN 1 ELSE force_emergency_bypass_used END
166371
+ WHERE session_id = ?`).run(now, reserved, bypass ? 1 : 0, now, bypass ? 1 : 0, args.sessionId);
166372
+ result = {
166373
+ ok: true,
166374
+ reservedTokens: reserved,
166375
+ overQuotaBypass: bypass,
166376
+ reservation: { sessionId: args.sessionId, runId: args.runId, tokens: reserved }
166377
+ };
166378
+ })();
166379
+ return result;
166380
+ }
166381
+ function rollbackProtectedTailDrainReservation(db, reservation) {
166382
+ if (!reservation || reservation.tokens <= 0)
166383
+ return;
166384
+ db.transaction(() => {
166385
+ ensureSessionMetaRow(db, reservation.sessionId);
166386
+ db.prepare(`UPDATE session_meta
166387
+ SET protected_tail_drain_tokens = MAX(0, COALESCE(protected_tail_drain_tokens, 0) - ?)
166388
+ WHERE session_id = ?`).run(reservation.tokens, reservation.sessionId);
166389
+ })();
166390
+ }
165712
166391
  function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
165713
166392
  ensureSessionMetaRow(db, sessionId);
165714
166393
  db.prepare("UPDATE session_meta SET cleared_reasoning_through_tag = ? WHERE session_id = ?").run(tagNumber, sessionId);
@@ -165716,27 +166395,95 @@ function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
165716
166395
  function clearPersistedReasoningWatermark(db, sessionId) {
165717
166396
  setPersistedReasoningWatermark(db, sessionId, 0);
165718
166397
  }
165719
- function getPersistedStickyTurnReminder(db, sessionId) {
165720
- const result = db.prepare("SELECT sticky_turn_reminder_text, sticky_turn_reminder_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
165721
- if (!isPersistedStickyTurnReminderRow(result)) {
165722
- return null;
165723
- }
165724
- if (result.sticky_turn_reminder_text.length === 0) {
165725
- return null;
165726
- }
165727
- return {
165728
- text: result.sticky_turn_reminder_text,
165729
- messageId: result.sticky_turn_reminder_message_id.length > 0 ? result.sticky_turn_reminder_message_id : null
165730
- };
166398
+ function isEmergencyInputSampleRow(row) {
166399
+ return typeof row === "object" && row !== null && typeof row.last_emergency_input_sample === "number";
166400
+ }
166401
+ function getEmergencyInputSample(db, sessionId) {
166402
+ const result = db.prepare("SELECT last_emergency_input_sample FROM session_meta WHERE session_id = ?").get(sessionId);
166403
+ return isEmergencyInputSampleRow(result) ? result.last_emergency_input_sample : 0;
166404
+ }
166405
+ function setEmergencyDropSample(db, sessionId, inputSample) {
166406
+ db.transaction(() => {
166407
+ ensureSessionMetaRow(db, sessionId);
166408
+ db.prepare("UPDATE session_meta SET last_emergency_input_sample = ? WHERE session_id = ?").run(Math.max(0, Math.round(inputSample)), sessionId);
166409
+ })();
165731
166410
  }
165732
- function setPersistedStickyTurnReminder(db, sessionId, text, messageId = "") {
166411
+ function clearEmergencyDropSample(db, sessionId) {
165733
166412
  db.transaction(() => {
165734
166413
  ensureSessionMetaRow(db, sessionId);
165735
- db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = ?, sticky_turn_reminder_message_id = ? WHERE session_id = ?").run(text, messageId, sessionId);
166414
+ db.prepare("UPDATE session_meta SET last_emergency_input_sample = 0 WHERE session_id = ?").run(sessionId);
165736
166415
  })();
165737
166416
  }
165738
- function clearPersistedStickyTurnReminder(db, sessionId) {
165739
- db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = '', sticky_turn_reminder_message_id = '' WHERE session_id = ?").run(sessionId);
166417
+ function isLastNudgeUndroppedRow(row) {
166418
+ return typeof row === "object" && row !== null && typeof row.last_nudge_undropped === "number";
166419
+ }
166420
+ function isLastNudgeLevelRow(row) {
166421
+ return typeof row === "object" && row !== null && typeof row.last_nudge_level === "string";
166422
+ }
166423
+ function normalizeLastNudgeLevel(value) {
166424
+ return value === "gentle" || value === "firm" || value === "urgent" ? value : "";
166425
+ }
166426
+ function getLastNudgeUndropped(db, sessionId) {
166427
+ const result = db.prepare("SELECT last_nudge_undropped FROM session_meta WHERE session_id = ?").get(sessionId);
166428
+ return isLastNudgeUndroppedRow(result) ? result.last_nudge_undropped : 0;
166429
+ }
166430
+ function setLastNudgeUndropped(db, sessionId, value) {
166431
+ db.transaction(() => {
166432
+ ensureSessionMetaRow(db, sessionId);
166433
+ db.prepare("UPDATE session_meta SET last_nudge_undropped = ? WHERE session_id = ?").run(Math.max(0, Math.round(value)), sessionId);
166434
+ })();
166435
+ }
166436
+ function getLastNudgeLevel(db, sessionId) {
166437
+ const result = db.prepare("SELECT last_nudge_level FROM session_meta WHERE session_id = ?").get(sessionId);
166438
+ return isLastNudgeLevelRow(result) ? normalizeLastNudgeLevel(result.last_nudge_level) : "";
166439
+ }
166440
+ function setLastNudgeLevel(db, sessionId, value) {
166441
+ db.transaction(() => {
166442
+ ensureSessionMetaRow(db, sessionId);
166443
+ db.prepare("UPDATE session_meta SET last_nudge_level = ? WHERE session_id = ?").run(normalizeLastNudgeLevel(value), sessionId);
166444
+ })();
166445
+ }
166446
+ function resetLastNudgeCycle(db, sessionId) {
166447
+ db.transaction(() => {
166448
+ ensureSessionMetaRow(db, sessionId);
166449
+ db.prepare("UPDATE session_meta SET last_nudge_undropped = 0, last_nudge_level = '' WHERE session_id = ?").run(sessionId);
166450
+ })();
166451
+ }
166452
+ function resetLastNudgeCycleIfTailShrank(db, sessionId, measuredUndropped) {
166453
+ let changed = false;
166454
+ db.transaction(() => {
166455
+ ensureSessionMetaRow(db, sessionId);
166456
+ const result = db.prepare("UPDATE session_meta SET last_nudge_undropped = 0, last_nudge_level = '' WHERE session_id = ? AND last_nudge_undropped > ?").run(sessionId, Math.max(0, Math.round(measuredUndropped)));
166457
+ changed = (result.changes ?? 0) > 0;
166458
+ })();
166459
+ return changed;
166460
+ }
166461
+ function isChannel2StateRow(row) {
166462
+ return typeof row === "object" && row !== null && typeof row.channel2_nudge_state === "string";
166463
+ }
166464
+ function getChannel2NudgeState(db, sessionId) {
166465
+ const result = db.prepare("SELECT channel2_nudge_state FROM session_meta WHERE session_id = ?").get(sessionId);
166466
+ if (!isChannel2StateRow(result))
166467
+ return "";
166468
+ const raw = result.channel2_nudge_state;
166469
+ return raw === "pending" || raw === "claimed" || raw === "delivered" ? raw : "";
166470
+ }
166471
+ function setChannel2NudgeState(db, sessionId, state) {
166472
+ db.transaction(() => {
166473
+ ensureSessionMetaRow(db, sessionId);
166474
+ const claimedAt = state === "claimed" ? Date.now() : 0;
166475
+ db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ?").run(state, claimedAt, sessionId);
166476
+ })();
166477
+ }
166478
+ function casChannel2NudgeState(db, sessionId, from, to) {
166479
+ let changed = false;
166480
+ db.transaction(() => {
166481
+ ensureSessionMetaRow(db, sessionId);
166482
+ const claimedAt = to === "claimed" ? Date.now() : 0;
166483
+ const result = db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ? AND channel2_nudge_state = ?").run(to, claimedAt, sessionId, from);
166484
+ changed = (result.changes ?? 0) > 0;
166485
+ })();
166486
+ return changed;
165740
166487
  }
165741
166488
  function getPersistedNoteNudge(db, sessionId) {
165742
166489
  const result = db.prepare("SELECT note_nudge_trigger_pending, note_nudge_trigger_message_id, note_nudge_sticky_text, note_nudge_sticky_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
@@ -165783,7 +166530,8 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
165783
166530
  }
165784
166531
  for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
165785
166532
  const row = db.prepare(`SELECT ${column} FROM session_meta WHERE session_id = ?`).get(sessionId);
165786
- const currentBlob = row?.[column] ?? "[]";
166533
+ const rawCurrent = row?.[column] ?? null;
166534
+ const currentBlob = rawCurrent ?? "[]";
165787
166535
  const current = parseJsonArray(currentBlob, validator);
165788
166536
  const next = mutate(current);
165789
166537
  if (next === null)
@@ -165791,7 +166539,7 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
165791
166539
  const nextBlob = stableStringify(next);
165792
166540
  if (nextBlob === currentBlob)
165793
166541
  return true;
165794
- const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} = ?`).run(nextBlob, sessionId, currentBlob);
166542
+ const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} IS ?`).run(nextBlob, sessionId, rawCurrent);
165795
166543
  if (result.changes > 0)
165796
166544
  return true;
165797
166545
  }
@@ -165920,14 +166668,16 @@ function getHistorianFailureState(db, sessionId) {
165920
166668
  };
165921
166669
  }
165922
166670
  function incrementHistorianFailure(db, sessionId, error51) {
166671
+ let nextCount = 1;
165923
166672
  db.transaction(() => {
165924
166673
  ensureSessionMetaRow(db, sessionId);
165925
166674
  const current = getHistorianFailureState(db, sessionId);
165926
- const nextCount = current.failureCount + 1;
166675
+ nextCount = current.failureCount + 1;
165927
166676
  db.prepare("UPDATE session_meta SET historian_failure_count = ?, historian_last_error = ?, historian_last_failure_at = ? WHERE session_id = ?").run(nextCount, error51, Date.now(), sessionId);
165928
166677
  const reason = error51.replace(/\s+/g, " ").trim().slice(0, 300);
165929
166678
  sessionLog(sessionId, `historian failure recorded: count=${nextCount} reason="${reason}"`);
165930
166679
  })();
166680
+ return nextCount;
165931
166681
  }
165932
166682
  function clearHistorianFailureState(db, sessionId) {
165933
166683
  db.transaction(() => {
@@ -165957,7 +166707,11 @@ function recordOverflowDetected(db, sessionId, reportedLimit) {
165957
166707
  function clearEmergencyRecovery(db, sessionId) {
165958
166708
  db.transaction(() => {
165959
166709
  ensureSessionMetaRow(db, sessionId);
165960
- db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0 WHERE session_id = ?").run(sessionId);
166710
+ try {
166711
+ db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0, recovery_no_eligible_head_count = 0 WHERE session_id = ?").run(sessionId);
166712
+ } catch {
166713
+ db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0 WHERE session_id = ?").run(sessionId);
166714
+ }
165961
166715
  })();
165962
166716
  }
165963
166717
  function clearDetectedContextLimit(db, sessionId) {
@@ -165996,10 +166750,39 @@ function getStrippedPlaceholderIds(db, sessionId) {
165996
166750
  } catch {}
165997
166751
  return new Set;
165998
166752
  }
165999
- function setStrippedPlaceholderIds(db, sessionId, ids) {
166753
+ function applyStrippedPlaceholderDelta(db, sessionId, delta) {
166754
+ const add = delta.add ? [...delta.add] : [];
166755
+ const remove = delta.remove ? [...delta.remove] : [];
166756
+ if (add.length === 0 && remove.length === 0)
166757
+ return true;
166000
166758
  ensureSessionMetaRow(db, sessionId);
166001
- const json2 = ids.size > 0 ? JSON.stringify([...ids]) : "";
166002
- db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ?").run(json2, sessionId);
166759
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
166760
+ const row = db.prepare("SELECT stripped_placeholder_ids FROM session_meta WHERE session_id = ?").get(sessionId);
166761
+ const rawStored = row ? row.stripped_placeholder_ids ?? null : null;
166762
+ const current = new Set(parseStrippedBlob(rawStored));
166763
+ for (const id of add)
166764
+ current.add(id);
166765
+ for (const id of remove)
166766
+ current.delete(id);
166767
+ const nextBlob = current.size > 0 ? JSON.stringify([...current]) : "";
166768
+ if (nextBlob === (rawStored ?? ""))
166769
+ return true;
166770
+ const result = db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ? AND stripped_placeholder_ids IS ?").run(nextBlob, sessionId, rawStored);
166771
+ if (result.changes > 0)
166772
+ return true;
166773
+ }
166774
+ sessionLog(sessionId, `stripped_placeholder_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
166775
+ return false;
166776
+ }
166777
+ function parseStrippedBlob(raw) {
166778
+ if (!raw || raw.length === 0)
166779
+ return [];
166780
+ try {
166781
+ const parsed = JSON.parse(raw);
166782
+ if (Array.isArray(parsed))
166783
+ return parsed.filter((v) => typeof v === "string");
166784
+ } catch {}
166785
+ return [];
166003
166786
  }
166004
166787
  function isPendingCompactionMarker(value) {
166005
166788
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
@@ -166091,6 +166874,7 @@ function setSessionWorkMetrics(db, sessionId, newWorkTokens, totalInputTokens) {
166091
166874
  import { Buffer as Buffer3 } from "node:buffer";
166092
166875
 
166093
166876
  // ../plugin/src/features/magic-context/resolve-subagent-fallback.ts
166877
+ init_logger();
166094
166878
  function resolveIsSubagentFromOpenCodeDb(sessionId) {
166095
166879
  try {
166096
166880
  return withReadOnlySessionDb((openCodeDb) => {
@@ -166309,7 +167093,8 @@ function updateNote(db, noteId, updates, scope) {
166309
167093
  sets.push("session_id = ?");
166310
167094
  params.push(updates.sessionId);
166311
167095
  }
166312
- if (updates.status !== undefined) {
167096
+ const smartConditionChanged = existing.type === "smart" && updates.surfaceCondition !== undefined && updates.surfaceCondition !== existing.surfaceCondition;
167097
+ if (updates.status !== undefined && !smartConditionChanged) {
166313
167098
  sets.push("status = ?");
166314
167099
  params.push(updates.status);
166315
167100
  }
@@ -166322,17 +167107,21 @@ function updateNote(db, noteId, updates, scope) {
166322
167107
  sets.push("surface_condition = ?");
166323
167108
  params.push(updates.surfaceCondition);
166324
167109
  }
166325
- if (updates.lastCheckedAt !== undefined) {
166326
- sets.push("last_checked_at = ?");
166327
- params.push(updates.lastCheckedAt);
166328
- }
166329
- if (updates.readyAt !== undefined) {
166330
- sets.push("ready_at = ?");
166331
- params.push(updates.readyAt);
166332
- }
166333
- if (updates.readyReason !== undefined) {
166334
- sets.push("ready_reason = ?");
166335
- params.push(updates.readyReason);
167110
+ if (smartConditionChanged) {
167111
+ sets.push("status = 'pending'", "last_checked_at = NULL", "ready_at = NULL", "ready_reason = NULL");
167112
+ } else {
167113
+ if (updates.lastCheckedAt !== undefined) {
167114
+ sets.push("last_checked_at = ?");
167115
+ params.push(updates.lastCheckedAt);
167116
+ }
167117
+ if (updates.readyAt !== undefined) {
167118
+ sets.push("ready_at = ?");
167119
+ params.push(updates.readyAt);
167120
+ }
167121
+ if (updates.readyReason !== undefined) {
167122
+ sets.push("ready_reason = ?");
167123
+ params.push(updates.readyReason);
167124
+ }
166336
167125
  }
166337
167126
  }
166338
167127
  if (sets.length === 1) {
@@ -166359,6 +167148,7 @@ function markNoteChecked(db, noteId) {
166359
167148
  db.prepare("UPDATE notes SET last_checked_at = ?, updated_at = ? WHERE id = ? AND type = 'smart'").run(now, now, noteId);
166360
167149
  }
166361
167150
  // ../plugin/src/features/magic-context/storage-ops.ts
167151
+ init_logger();
166362
167152
  var queuePendingOpStatements = new WeakMap;
166363
167153
  var getPendingOpsStatements = new WeakMap;
166364
167154
  var clearPendingOpsStatements = new WeakMap;
@@ -166521,7 +167311,7 @@ var getTagNumberByMessageIdStatements = new WeakMap;
166521
167311
  function getInsertTagStatement(db) {
166522
167312
  let stmt = insertTagStatements.get(db);
166523
167313
  if (!stmt) {
166524
- stmt = db.prepare("INSERT INTO tags (session_id, message_id, type, byte_size, reasoning_byte_size, tag_number, tool_name, input_byte_size, harness, tool_owner_message_id, entry_fingerprint) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
167314
+ stmt = db.prepare("INSERT INTO tags (session_id, message_id, type, byte_size, reasoning_byte_size, tag_number, tool_name, input_byte_size, harness, tool_owner_message_id, entry_fingerprint, token_count, input_token_count, reasoning_token_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
166525
167315
  insertTagStatements.set(db, stmt);
166526
167316
  }
166527
167317
  return stmt;
@@ -166563,9 +167353,95 @@ function getUpdateTagInputByteSizeStatement(db) {
166563
167353
  function updateTagByteSize(db, sessionId, tagNumber, newByteSize) {
166564
167354
  getUpdateTagByteSizeStatement(db).run(newByteSize, sessionId, tagNumber);
166565
167355
  }
167356
+ var CONTENT_ID_SUFFIX = /:(?:p|file)\d+$/;
167357
+ function ownerMessageIdForTagRow(row) {
167358
+ if (row.type === "tool") {
167359
+ return row.tool_owner_message_id ?? row.message_id;
167360
+ }
167361
+ return row.message_id.replace(CONTENT_ID_SUFFIX, "");
167362
+ }
167363
+ function getActiveTagTokenAggregate(db, sessionId) {
167364
+ const row = db.prepare(`SELECT
167365
+ COALESCE(SUM(CASE WHEN type != 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)
167366
+ + COALESCE(SUM(COALESCE(reasoning_token_count, 0)), 0) AS conversation,
167367
+ COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) + COALESCE(input_token_count, 0) ELSE 0 END), 0) AS tool_call,
167368
+ COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0) AS tool_output,
167369
+ COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
167370
+ FROM tags
167371
+ WHERE session_id = ? AND status = 'active'`).get(sessionId);
167372
+ return {
167373
+ conversation: row?.conversation ?? 0,
167374
+ toolCall: row?.tool_call ?? 0,
167375
+ toolOutput: row?.tool_output ?? 0,
167376
+ nullCount: row?.null_count ?? 0
167377
+ };
167378
+ }
167379
+ function getTriggerTagTokenUpperBound(db, sessionId) {
167380
+ const row = db.prepare(`SELECT
167381
+ COALESCE(SUM(COALESCE(token_count, 0) + COALESCE(input_token_count, 0) + COALESCE(reasoning_token_count, 0)), 0) AS bound,
167382
+ COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
167383
+ FROM tags
167384
+ WHERE session_id = ? AND status IN ('active', 'dropped')`).get(sessionId);
167385
+ return { bound: row?.bound ?? 0, nullCount: row?.null_count ?? 0 };
167386
+ }
166566
167387
  function updateTagInputByteSize(db, sessionId, tagNumber, newInputByteSize) {
166567
167388
  getUpdateTagInputByteSizeStatement(db).run(newInputByteSize, sessionId, tagNumber);
166568
167389
  }
167390
+ var updateTagTokenCountStatements = new WeakMap;
167391
+ var updateTagInputTokenCountStatements = new WeakMap;
167392
+ function getUpdateTagTokenCountStatement(db) {
167393
+ let stmt = updateTagTokenCountStatements.get(db);
167394
+ if (!stmt) {
167395
+ stmt = db.prepare("UPDATE tags SET token_count = ? WHERE session_id = ? AND tag_number = ?");
167396
+ updateTagTokenCountStatements.set(db, stmt);
167397
+ }
167398
+ return stmt;
167399
+ }
167400
+ function getUpdateTagInputTokenCountStatement(db) {
167401
+ let stmt = updateTagInputTokenCountStatements.get(db);
167402
+ if (!stmt) {
167403
+ stmt = db.prepare("UPDATE tags SET input_token_count = ? WHERE session_id = ? AND tag_number = ?");
167404
+ updateTagInputTokenCountStatements.set(db, stmt);
167405
+ }
167406
+ return stmt;
167407
+ }
167408
+ function updateTagTokenCount(db, sessionId, tagNumber, newTokenCount) {
167409
+ getUpdateTagTokenCountStatement(db).run(newTokenCount, sessionId, tagNumber);
167410
+ }
167411
+ function getAllStatusTagTokenTotalsFlat(db, sessionId) {
167412
+ const rows = db.prepare(`SELECT type, message_id, tool_owner_message_id, token_count, input_token_count, reasoning_token_count
167413
+ FROM tags
167414
+ WHERE session_id = ?`).all(sessionId);
167415
+ const totals = new Map;
167416
+ const nullMessageIds = new Set;
167417
+ for (const row of rows) {
167418
+ if (row.type === "tool" && row.tool_owner_message_id === null)
167419
+ continue;
167420
+ const owner = ownerMessageIdForTagRow(row);
167421
+ if (row.token_count === null) {
167422
+ nullMessageIds.add(owner);
167423
+ totals.delete(owner);
167424
+ continue;
167425
+ }
167426
+ if (nullMessageIds.has(owner))
167427
+ continue;
167428
+ const weight = (row.token_count ?? 0) + (row.input_token_count ?? 0) + (row.reasoning_token_count ?? 0);
167429
+ totals.set(owner, (totals.get(owner) ?? 0) + weight);
167430
+ }
167431
+ return { totals, nullMessageIds };
167432
+ }
167433
+ function updateTagInputTokenCount(db, sessionId, tagNumber, newInputTokenCount) {
167434
+ getUpdateTagInputTokenCountStatement(db).run(newInputTokenCount, sessionId, tagNumber);
167435
+ }
167436
+ function tagTokenCountIsNull(db, sessionId, tagNumber) {
167437
+ const row = db.prepare("SELECT token_count FROM tags WHERE session_id = ? AND tag_number = ?").get(sessionId, tagNumber);
167438
+ return row !== undefined && row.token_count === null;
167439
+ }
167440
+ function backfillTagTokenCounts(db, sessionId, tagNumber, counts) {
167441
+ db.prepare(`UPDATE tags
167442
+ SET token_count = ?, input_token_count = ?, reasoning_token_count = ?
167443
+ WHERE session_id = ? AND tag_number = ? AND token_count IS NULL`).run(counts.tokenCount ?? null, counts.inputTokenCount ?? null, counts.reasoningTokenCount ?? null, sessionId, tagNumber);
167444
+ }
166569
167445
  function getMaxTagNumberBySessionStatement(db) {
166570
167446
  let stmt = getMaxTagNumberBySessionStatements.get(db);
166571
167447
  if (!stmt) {
@@ -166618,8 +167494,8 @@ function isMaxTagNumberRow(row) {
166618
167494
  const r = row;
166619
167495
  return typeof r.max_tag_number === "number";
166620
167496
  }
166621
- function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber, reasoningByteSize = 0, toolName = null, inputByteSize = 0, toolOwnerMessageId = null, entryFingerprint = null) {
166622
- getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, reasoningByteSize, tagNumber, toolName, inputByteSize, getHarness(), toolOwnerMessageId, entryFingerprint);
167497
+ function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber, reasoningByteSize = 0, toolName = null, inputByteSize = 0, toolOwnerMessageId = null, entryFingerprint = null, tokenCounts = null) {
167498
+ getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, reasoningByteSize, tagNumber, toolName, inputByteSize, getHarness(), toolOwnerMessageId, entryFingerprint, tokenCounts?.tokenCount ?? null, tokenCounts?.inputTokenCount ?? null, tokenCounts?.reasoningTokenCount ?? null);
166623
167499
  return tagNumber;
166624
167500
  }
166625
167501
  function updateTagStatus(db, sessionId, tagId, status) {
@@ -166687,13 +167563,6 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
166687
167563
  const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
166688
167564
  return rows.map(toTagEntry);
166689
167565
  }
166690
- function getTopNBySize(db, sessionId, n) {
166691
- if (n <= 0) {
166692
- return [];
166693
- }
166694
- const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND status = 'active' ORDER BY byte_size DESC, tag_number ASC LIMIT ?`).all(sessionId, n).filter(isTagRow);
166695
- return rows.map(toTagEntry);
166696
- }
166697
167566
  var getToolTagNumberByOwnerStatements = new WeakMap;
166698
167567
  var getNullOwnerToolTagStatements = new WeakMap;
166699
167568
  var adoptNullOwnerToolTagStatements = new WeakMap;
@@ -166759,44 +167628,43 @@ var ERROR_CLASSES = new Set([
166759
167628
  "permission_denied",
166760
167629
  "unknown"
166761
167630
  ]);
167631
+ // src/tools/ctx-memory.ts
167632
+ init_logger();
167633
+
167634
+ // ../plugin/src/tools/ctx-memory/constants.ts
167635
+ var CTX_MEMORY_DESCRIPTION = `Durable project knowledge shared across every session on this project.
167636
+
167637
+ Your active memories are already visible in <project-memory> (each with its id), and every future session starts with them — write one when you learn something future sessions must know: a project rule, an architectural fact, a hard-won constraint, a config value, or a naming convention. Keep each memory one standalone fact, phrased to make sense without this session's context.
167638
+
167639
+ Actions:
167640
+ - write: save a new memory (content + category).
167641
+ - update: rewrite one memory whose fact changed (ids: [one], content).
167642
+ - archive: retire wrong or obsolete memories (ids: [one or more], optional reason).
167643
+ - merge: collapse duplicates into one memory (ids: [two or more], content).
167644
+
167645
+ Example: ctx_memory(action="write", category="CONSTRAINTS", content="Pi stores sessions as JSONL under ~/.pi/agent/sessions/, not SQLite")`;
167646
+
166762
167647
  // src/tools/ctx-memory.ts
166763
167648
  var DEFAULT_LIST_LIMIT = 10;
166764
- var VALID_CATEGORIES = new Set(CATEGORY_PRIORITY);
166765
- function isMemoryCategory2(value) {
166766
- return VALID_CATEGORIES.has(value);
166767
- }
166768
- var ALL_ACTIONS = [
166769
- "write",
166770
- "delete",
166771
- "list",
166772
- "update",
166773
- "merge",
166774
- "archive"
166775
- ];
166776
- var DREAMER_ONLY_ACTIONS = new Set([
166777
- "list",
166778
- "update",
166779
- "merge",
166780
- "archive"
166781
- ]);
167649
+ var ALL_ACTIONS = ["write", "archive", "update", "merge", "list"];
167650
+ var DREAMER_ONLY_ACTIONS = new Set(["list"]);
166782
167651
  var ParamsSchema2 = exports_typebox.Object({
166783
- action: exports_typebox.Union(ALL_ACTIONS.map((a) => exports_typebox.Literal(a)), { description: "Action to perform on memories" }),
167652
+ action: exports_typebox.Union(ALL_ACTIONS.map((a) => exports_typebox.Literal(a)), { description: "What to do: write, update, archive, or merge" }),
166784
167653
  content: exports_typebox.Optional(exports_typebox.String({
166785
- description: "Memory content (required for write, update, merge)"
166786
- })),
166787
- category: exports_typebox.Optional(exports_typebox.String({
166788
- description: "Memory category (required for write, optional filter for list, optional override for merge). One of: " + CATEGORY_PRIORITY.join(", ")
167654
+ description: "The memory text — one standalone fact (required for write, update, merge)"
166789
167655
  })),
166790
- id: exports_typebox.Optional(exports_typebox.Number({
166791
- description: "Memory ID (required for delete, update, archive)"
167656
+ category: exports_typebox.Optional(exports_typebox.Union(V2_MEMORY_CATEGORIES.map((c) => exports_typebox.Literal(c)), {
167657
+ description: "What kind of fact this is (required for write; optional merge override)"
166792
167658
  })),
166793
167659
  ids: exports_typebox.Optional(exports_typebox.Array(exports_typebox.Number(), {
166794
- description: "Memory IDs to merge (required for merge)"
167660
+ description: "Target memory id(s) from <project-memory>: update takes exactly one, archive one or more, merge two or more"
166795
167661
  })),
166796
167662
  limit: exports_typebox.Optional(exports_typebox.Number({
166797
- description: "Maximum results to return for list (default: 10)"
167663
+ description: "Max results for list (default: 10)"
166798
167664
  })),
166799
- reason: exports_typebox.Optional(exports_typebox.String({ description: "Archive reason (optional for archive)" }))
167665
+ reason: exports_typebox.Optional(exports_typebox.String({
167666
+ description: "Why the memory is being archived (optional, recommended)"
167667
+ }))
166800
167668
  });
166801
167669
  function ok2(text) {
166802
167670
  return { content: [{ type: "text", text }], details: undefined };
@@ -166843,6 +167711,12 @@ function formatMemoryList(memories) {
166843
167711
  ].join(`
166844
167712
  `);
166845
167713
  }
167714
+ function isPrimaryMutableMemory(memory2) {
167715
+ return (memory2.status === "active" || memory2.status === "permanent") && memory2.supersededByMemoryId === null;
167716
+ }
167717
+ function inactiveMemoryError(id, action2) {
167718
+ return `Error: Memory with ID ${id} is archived or superseded; restore it before ${action2}.`;
167719
+ }
166846
167720
  function queueEmbedding(args) {
166847
167721
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
166848
167722
  if (!snapshot?.enabled)
@@ -166863,7 +167737,8 @@ function queueEmbedding(args) {
166863
167737
  }
166864
167738
  function createCtxMemoryTool(deps) {
166865
167739
  const dreamerAllowed = deps.allowDreamerActions === true;
166866
- const description = dreamerAllowed ? "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete, list, update, merge, archive." : "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete.";
167740
+ const description = dreamerAllowed ? `${CTX_MEMORY_DESCRIPTION}
167741
+ - list: enumerate stored memories (maintenance sessions).` : CTX_MEMORY_DESCRIPTION;
166867
167742
  return {
166868
167743
  name: "ctx_memory",
166869
167744
  label: "Magic Context: Memory",
@@ -166884,13 +167759,10 @@ function createCtxMemoryTool(deps) {
166884
167759
  const content = params.content?.trim();
166885
167760
  if (!content)
166886
167761
  return err2("Error: 'content' is required when action is 'write'.");
166887
- const rawCategory = params.category?.trim();
167762
+ const rawCategory = params.category;
166888
167763
  if (!rawCategory) {
166889
167764
  return err2("Error: 'category' is required when action is 'write'.");
166890
167765
  }
166891
- if (!isMemoryCategory2(rawCategory)) {
166892
- return err2(`Error: Unknown memory category '${rawCategory}'. Valid: ${CATEGORY_PRIORITY.join(", ")}`);
166893
- }
166894
167766
  const existing = getMemoryByHash(deps.db, projectIdentity, rawCategory, computeNormalizedHash(content));
166895
167767
  if (existing) {
166896
167768
  updateMemorySeenCount(deps.db, existing.id);
@@ -166904,45 +167776,31 @@ function createCtxMemoryTool(deps) {
166904
167776
  sourceType: dreamerAllowed ? "dreamer" : "agent"
166905
167777
  });
166906
167778
  queueEmbedding({ deps, projectIdentity, memoryId: memory2.id, content });
166907
- invalidateAllMemoryBlockCaches(deps.db);
166908
167779
  return ok2(`Saved memory [ID: ${memory2.id}] in ${rawCategory}.`);
166909
167780
  }
166910
- if (params.action === "delete") {
166911
- if (typeof params.id !== "number" || !Number.isInteger(params.id)) {
166912
- return err2("Error: 'id' is required when action is 'delete'.");
166913
- }
166914
- const memory2 = getMemoryById(deps.db, params.id);
166915
- if (!memory2 || memory2.projectPath !== projectIdentity) {
166916
- return err2(`Error: Memory with ID ${params.id} was not found.`);
166917
- }
166918
- deps.db.transaction(() => {
166919
- archiveMemory(deps.db, params.id);
166920
- queueMemoryMutation(deps.db, {
166921
- projectPath: projectIdentity,
166922
- mutationType: "delete",
166923
- targetMemoryId: params.id
166924
- });
166925
- })();
166926
- return ok2(`Archived memory [ID: ${params.id}].`);
166927
- }
166928
167781
  if (params.action === "list") {
166929
167782
  const limit = normalizeLimit(params.limit);
166930
167783
  const filtered = getMemoriesByProject(deps.db, projectIdentity);
166931
- const category = params.category?.trim();
167784
+ const category = params.category;
166932
167785
  const filtered2 = category ? filtered.filter((m) => m.category === category) : filtered;
166933
167786
  return ok2(formatMemoryList(filtered2.slice(0, limit)));
166934
167787
  }
166935
167788
  if (params.action === "update") {
166936
- if (typeof params.id !== "number" || !Number.isInteger(params.id)) {
166937
- return err2("Error: 'id' is required when action is 'update'.");
167789
+ const updateIds = params.ids;
167790
+ if (!updateIds || updateIds.length !== 1 || !updateIds.every(Number.isInteger)) {
167791
+ return err2("Error: 'ids' must contain exactly one integer memory ID when action is 'update'.");
166938
167792
  }
167793
+ const updateId = updateIds[0];
166939
167794
  const content = params.content?.trim();
166940
167795
  if (!content) {
166941
167796
  return err2("Error: 'content' is required when action is 'update'.");
166942
167797
  }
166943
- const memory2 = getMemoryById(deps.db, params.id);
166944
- if (!memory2 || memory2.projectPath !== projectIdentity) {
166945
- return err2(`Error: Memory with ID ${params.id} was not found.`);
167798
+ const memory2 = getMemoryById(deps.db, updateId);
167799
+ if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
167800
+ return err2(`Error: Memory with ID ${updateId} was not found.`);
167801
+ }
167802
+ if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
167803
+ return err2(inactiveMemoryError(updateId, "updating"));
166946
167804
  }
166947
167805
  const normalizedHash = computeNormalizedHash(content);
166948
167806
  const duplicate = getMemoryByHash(deps.db, projectIdentity, memory2.category, normalizedHash);
@@ -166963,9 +167821,12 @@ function createCtxMemoryTool(deps) {
166963
167821
  return ok2(`Updated memory [ID: ${memory2.id}] in ${memory2.category}.`);
166964
167822
  }
166965
167823
  if (params.action === "merge") {
166966
- const ids = params.ids?.filter((id) => Number.isInteger(id));
166967
- if (!ids || ids.length < 2) {
166968
- return err2("Error: 'ids' must include at least two memory IDs when action is 'merge'.");
167824
+ const ids = params.ids;
167825
+ if (!ids || ids.length < 2 || !ids.every(Number.isInteger)) {
167826
+ return err2("Error: 'ids' must include at least two integer memory IDs when action is 'merge'.");
167827
+ }
167828
+ if (new Set(ids).size !== ids.length) {
167829
+ return err2("Error: 'ids' must include at least two distinct memory IDs when action is 'merge'.");
166969
167830
  }
166970
167831
  const content = params.content?.trim();
166971
167832
  if (!content) {
@@ -166975,14 +167836,17 @@ function createCtxMemoryTool(deps) {
166975
167836
  if (sourceMemories.length !== ids.length) {
166976
167837
  return err2("Error: One or more source memories were not found.");
166977
167838
  }
166978
- if (sourceMemories.some((memory2) => memory2.projectPath !== projectIdentity)) {
166979
- return err2("Error: All memories to merge must belong to the current project.");
166980
- }
166981
- const requestedCategory = params.category?.trim();
166982
- if (requestedCategory && !isMemoryCategory2(requestedCategory)) {
166983
- return err2(`Error: Unknown memory category '${requestedCategory}'. Valid: ${CATEGORY_PRIORITY.join(", ")}`);
167839
+ if (!dreamerAllowed) {
167840
+ const foreign = sourceMemories.find((memory2) => !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity));
167841
+ if (foreign) {
167842
+ return err2(`Error: Memory with ID ${foreign.id} was not found.`);
167843
+ }
167844
+ const inactive = sourceMemories.find((memory2) => !isPrimaryMutableMemory(memory2));
167845
+ if (inactive) {
167846
+ return err2(inactiveMemoryError(inactive.id, "merging"));
167847
+ }
166984
167848
  }
166985
- const requestedCategoryTyped = requestedCategory && isMemoryCategory2(requestedCategory) ? requestedCategory : undefined;
167849
+ const requestedCategoryTyped = params.category;
166986
167850
  const fallbackCategory = sourceMemories[0]?.category;
166987
167851
  const category = requestedCategoryTyped ?? fallbackCategory;
166988
167852
  if (!category) {
@@ -167036,7 +167900,7 @@ function createCtxMemoryTool(deps) {
167036
167900
  }
167037
167901
  supersededMemory(deps.db, memory2.id, canonicalMemory.id);
167038
167902
  queueMemoryMutation(deps.db, {
167039
- projectPath: memory2.projectPath,
167903
+ projectPath: normalizeStoredProjectPath(memory2.projectPath),
167040
167904
  mutationType: "superseded",
167041
167905
  targetMemoryId: memory2.id,
167042
167906
  supersededById: canonicalMemory.id
@@ -167044,7 +167908,7 @@ function createCtxMemoryTool(deps) {
167044
167908
  }
167045
167909
  if (canonicalExisting && canonicalContentChanged) {
167046
167910
  queueMemoryMutation(deps.db, {
167047
- projectPath: canonicalMemory.projectPath,
167911
+ projectPath: normalizeStoredProjectPath(canonicalMemory.projectPath),
167048
167912
  mutationType: "update",
167049
167913
  targetMemoryId: canonicalMemory.id,
167050
167914
  category,
@@ -167062,29 +167926,57 @@ function createCtxMemoryTool(deps) {
167062
167926
  return ok2(`Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`);
167063
167927
  }
167064
167928
  if (params.action === "archive") {
167065
- if (typeof params.id !== "number" || !Number.isInteger(params.id)) {
167066
- return err2("Error: 'id' is required when action is 'archive'.");
167929
+ const archiveIds = params.ids;
167930
+ if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
167931
+ return err2("Error: 'ids' must contain at least one integer memory ID when action is 'archive'.");
167067
167932
  }
167068
- const memory2 = getMemoryById(deps.db, params.id);
167069
- if (!memory2 || memory2.projectPath !== projectIdentity) {
167070
- return err2(`Error: Memory with ID ${params.id} was not found.`);
167933
+ for (const memoryId of archiveIds) {
167934
+ const memory2 = getMemoryById(deps.db, memoryId);
167935
+ if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
167936
+ return err2(`Error: Memory with ID ${memoryId} was not found.`);
167937
+ }
167938
+ if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
167939
+ return err2(inactiveMemoryError(memoryId, "archiving"));
167940
+ }
167071
167941
  }
167072
167942
  deps.db.transaction(() => {
167073
- archiveMemory(deps.db, params.id, params.reason);
167074
- queueMemoryMutation(deps.db, {
167075
- projectPath: projectIdentity,
167076
- mutationType: "archive",
167077
- targetMemoryId: params.id
167078
- });
167943
+ for (const memoryId of archiveIds) {
167944
+ archiveMemory(deps.db, memoryId, params.reason);
167945
+ queueMemoryMutation(deps.db, {
167946
+ projectPath: projectIdentity,
167947
+ mutationType: "archive",
167948
+ targetMemoryId: memoryId
167949
+ });
167950
+ }
167079
167951
  })();
167080
167952
  const reasonSuffix = params.reason ? ` (${params.reason})` : "";
167081
- return ok2(`Archived memory [ID: ${params.id}]${reasonSuffix}.`);
167953
+ const idList = archiveIds.join(", ");
167954
+ const plural = archiveIds.length > 1 ? "memories" : "memory";
167955
+ return ok2(`Archived ${plural} [ID: ${idList}]${reasonSuffix}.`);
167082
167956
  }
167083
167957
  return err2("Error: Unknown action.");
167084
167958
  }
167085
167959
  };
167086
167960
  }
167087
167961
 
167962
+ // ../plugin/src/tools/ctx-note/constants.ts
167963
+ var CTX_NOTE_DESCRIPTION = `Working notes for this session's future — reminders, follow-ups, and things to revisit later.
167964
+
167965
+ Use a note when something matters LATER but not in the next few steps: "revisit the retry logic after the release", "user wants the dashboard polish batched", "flaky test to investigate when touching CI". Don't use notes for active multi-step work (use todos) or for durable project knowledge that should outlive this session (use ctx_memory). Notes resurface automatically at natural work boundaries and whenever you read them.
167966
+
167967
+ Actions:
167968
+ - write: save a note (content). Add surface_condition to make it a smart note (below).
167969
+ - read: list notes, newest first. Default: latest active session notes + ready smart notes; page older ones with limit/offset, or inspect other states with filter.
167970
+ - update / dismiss: change or retire a note by note_id.
167971
+
167972
+ Smart notes: pass surface_condition and the note stays hidden until a background checker confirms the condition — using ONLY externally verifiable signals (GitHub state via gh, files on disk, git history, web pages). It cannot see this conversation, so the condition must be checkable from outside:
167973
+ ✓ "When PR #42 in cortexkit/magic-context is merged"
167974
+ ✓ "When the latest release tag is >= v0.22.0"
167975
+ ✓ "When packages/plugin/src/foo.ts contains a function named bar"
167976
+ ✗ "When the user mentions X" / "when we revisit Y" / "after we finish this refactor" — no external signal; write a regular note instead.
167977
+
167978
+ Example: ctx_note(action="write", content="Re-run the perf benchmark once the boundary rework ships", surface_condition="When the latest release tag is >= v0.23.0")`;
167979
+
167088
167980
  // src/tools/ctx-note.ts
167089
167981
  var FILTER_VALUES = [
167090
167982
  "active",
@@ -167104,13 +167996,19 @@ var ParamsSchema3 = exports_typebox.Object({
167104
167996
  })),
167105
167997
  content: exports_typebox.Optional(exports_typebox.String({ description: "Note text to store when action is 'write'." })),
167106
167998
  surface_condition: exports_typebox.Optional(exports_typebox.String({
167107
- description: "Open-ended condition for smart notes. When provided, creates a project-scoped smart note that the dreamer evaluates nightly. The note surfaces when the condition is met."
167999
+ description: "Externally verifiable condition for smart notes. A background checker verifies it using ONLY outside signals (GitHub state via gh, files on disk, git history, web) it cannot see this conversation. Use for PR/issue state, release tags, file contents, workflow runs. NOT for 'when the user mentions X' / 'when we revisit Y' — write a regular note instead."
167108
168000
  })),
167109
168001
  note_id: exports_typebox.Optional(exports_typebox.Number({
167110
168002
  description: "Note ID (required for 'dismiss' and 'update' actions)."
167111
168003
  })),
167112
168004
  filter: exports_typebox.Optional(exports_typebox.Union(FILTER_VALUES.map((value) => exports_typebox.Literal(value)), {
167113
168005
  description: "Optional read filter. Defaults to active session notes + ready smart notes. Use 'all' to inspect every status or 'pending' to inspect unsurfaced smart notes."
168006
+ })),
168007
+ limit: exports_typebox.Optional(exports_typebox.Number({
168008
+ description: "Max notes per section for read, newest first (default: 25)"
168009
+ })),
168010
+ offset: exports_typebox.Optional(exports_typebox.Number({
168011
+ description: "Skip this many newest notes for read — page older ones (default: 0)"
167114
168012
  }))
167115
168013
  });
167116
168014
  function ok3(text) {
@@ -167147,15 +168045,20 @@ function formatNoteLine(note) {
167147
168045
  var DISMISS_FOOTER = `
167148
168046
 
167149
168047
  To dismiss a stale note: ctx_note(action="dismiss", note_id=N)`;
168048
+ var DEFAULT_READ_LIMIT = 25;
168049
+ function paginateNewestFirst(notes, limit, offset) {
168050
+ const total = notes.length;
168051
+ const newestFirst = [...notes].reverse();
168052
+ const page = newestFirst.slice(offset, offset + limit);
168053
+ const remaining = total - offset - page.length;
168054
+ const footer = remaining > 0 ? `Showing ${page.length} of ${total} (newest first) — ${remaining} older: ctx_note(action="read", offset=${offset + page.length})` : null;
168055
+ return { page, total, footer };
168056
+ }
167150
168057
  function createCtxNoteTool(deps) {
167151
168058
  return {
167152
168059
  name: "ctx_note",
167153
168060
  label: "Magic Context: Notes",
167154
- description: `Save or inspect durable session notes that persist for this session.
167155
- ` + `Use this for short goals, constraints, decisions, or reminders worth carrying forward.
167156
-
167157
- ` + `Actions:
167158
- ` + "- `write`: Append one note. Optionally provide `surface_condition` to create a smart note.\n" + "- `read`: Show current notes. Defaults to active session notes + ready smart notes; use `filter` to inspect all, pending, ready, active, or dismissed notes.\n" + "- `dismiss`: Dismiss a note by `note_id`.\n" + "- `update`: Update a note by `note_id`.\n\n" + "**Smart Notes**: When `surface_condition` is provided with `write`, the note becomes a project-scoped smart note. " + "The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met. " + 'Example: `ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")`',
168061
+ description: CTX_NOTE_DESCRIPTION,
167159
168062
  parameters: ParamsSchema3,
167160
168063
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
167161
168064
  const sessionId = ctx.sessionManager.getSessionId();
@@ -167237,11 +168140,15 @@ function createCtxNoteTool(deps) {
167237
168140
  - ${parts.join(`
167238
168141
  - `)}`);
167239
168142
  }
168143
+ const limit = typeof params.limit === "number" && params.limit > 0 ? Math.floor(params.limit) : DEFAULT_READ_LIMIT;
168144
+ const offset = typeof params.offset === "number" && params.offset > 0 ? Math.floor(params.offset) : 0;
167240
168145
  const sections = readNotes({
167241
168146
  db: deps.db,
167242
168147
  sessionId,
167243
168148
  cwd: ctx.cwd,
167244
- filter: params.filter
168149
+ filter: params.filter,
168150
+ limit,
168151
+ offset
167245
168152
  });
167246
168153
  try {
167247
168154
  setNoteLastReadAt(deps.db, sessionId);
@@ -167249,7 +168156,7 @@ function createCtxNoteTool(deps) {
167249
168156
  if (sections.length === 0) {
167250
168157
  return ok3(`## Notes
167251
168158
 
167252
- No notes for the current filter.`);
168159
+ No session notes or smart notes.`);
167253
168160
  }
167254
168161
  const body = sections.join(`
167255
168162
 
@@ -167276,10 +168183,14 @@ function readNotes(args) {
167276
168183
  }) : [];
167277
168184
  const sections2 = [];
167278
168185
  if (sessionNotes2.length > 0) {
168186
+ const { page, footer } = paginateNewestFirst(sessionNotes2, args.limit, args.offset);
168187
+ const lines = page.map(formatNoteLine).join(`
168188
+ `);
167279
168189
  sections2.push(`## Session Notes
167280
168190
 
167281
- ${sessionNotes2.map(formatNoteLine).join(`
167282
- `)}`);
168191
+ ${lines}${footer ? `
168192
+
168193
+ ${footer}` : ""}`);
167283
168194
  }
167284
168195
  if (readySmartNotes.length > 0) {
167285
168196
  sections2.push(`## \uD83D\uDD14 Ready Smart Notes
@@ -167310,17 +168221,25 @@ ${readySmartNotes.map(formatNoteLine).join(`
167310
168221
  }) : [];
167311
168222
  const sections = [];
167312
168223
  if (sessionNotes.length > 0) {
168224
+ const { page, footer } = paginateNewestFirst(sessionNotes, args.limit, args.offset);
168225
+ const lines = page.map(formatNoteLine).join(`
168226
+ `);
167313
168227
  sections.push(`## Session Notes
167314
168228
 
167315
- ${sessionNotes.map(formatNoteLine).join(`
167316
- `)}`);
168229
+ ${lines}${footer ? `
168230
+
168231
+ ${footer}` : ""}`);
167317
168232
  }
167318
168233
  if (smartNotes.length > 0) {
168234
+ const { page, footer } = paginateNewestFirst(smartNotes, args.limit, args.offset);
168235
+ const lines = page.map(formatNoteLine).join(`
168236
+
168237
+ `);
167319
168238
  sections.push(`## Smart Notes
167320
168239
 
167321
- ${smartNotes.map(formatNoteLine).join(`
168240
+ ${lines}${footer ? `
167322
168241
 
167323
- `)}`);
168242
+ ${footer}` : ""}`);
167324
168243
  }
167325
168244
  return sections;
167326
168245
  }
@@ -167328,7 +168247,7 @@ ${smartNotes.map(formatNoteLine).join(`
167328
168247
  // ../plugin/src/features/magic-context/range-parser.ts
167329
168248
  function parseRangeString(input) {
167330
168249
  const maxRangeElements = 1000;
167331
- const trimmed = input.trim();
168250
+ const trimmed = input.replace(/§/g, "").trim();
167332
168251
  if (trimmed === "") {
167333
168252
  throw new Error("Range string must not be empty");
167334
168253
  }
@@ -167476,7 +168395,11 @@ function createCtxReduceTool(deps) {
167476
168395
  };
167477
168396
  }
167478
168397
 
168398
+ // ../plugin/src/features/magic-context/search.ts
168399
+ init_logger();
168400
+
167479
168401
  // ../plugin/src/features/magic-context/git-commits/git-log-reader.ts
168402
+ init_logger();
167480
168403
  import { execFile } from "node:child_process";
167481
168404
  import { promisify } from "node:util";
167482
168405
  var execFileAsync = promisify(execFile);
@@ -167560,7 +168483,11 @@ ${body}` : subject;
167560
168483
  }
167561
168484
  return commits;
167562
168485
  }
168486
+ // ../plugin/src/features/magic-context/git-commits/indexer.ts
168487
+ init_logger();
168488
+
167563
168489
  // ../plugin/src/features/magic-context/git-commits/storage-git-commits.ts
168490
+ init_logger();
167564
168491
  var insertStatements = new WeakMap;
167565
168492
  var existingShasStatements = new WeakMap;
167566
168493
  var projectCountStatements = new WeakMap;
@@ -167773,6 +168700,7 @@ async function embedUnembeddedCommits(db, projectPath) {
167773
168700
  }
167774
168701
  }
167775
168702
  // ../plugin/src/features/magic-context/git-commits/search-git-commits.ts
168703
+ init_logger();
167776
168704
  var ftsStatements = new WeakMap;
167777
168705
  var ftsPlainStatements = new WeakMap;
167778
168706
  var getBySHAStatements = new WeakMap;
@@ -168443,6 +169371,9 @@ class BoundedSessionMap {
168443
169371
  }
168444
169372
  }
168445
169373
 
169374
+ // ../plugin/src/hooks/magic-context/inject-compartments.ts
169375
+ init_logger();
169376
+
168446
169377
  // ../plugin/src/hooks/magic-context/decay-curve.ts
168447
169378
  var H50 = 24;
168448
169379
  var D = 25;
@@ -168506,6 +169437,9 @@ function escapeXmlAttr2(s) {
168506
169437
  function escapeXmlContent2(s) {
168507
169438
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
168508
169439
  }
169440
+ function isTieredRow(c) {
169441
+ return typeof c.p1 === "string" && c.p1.length > 0;
169442
+ }
168509
169443
  function tierBody(c, tier2) {
168510
169444
  const tiers = [c.p1, c.p2, c.p3, c.p4];
168511
169445
  const requested = tiers[tier2 - 1];
@@ -168535,12 +169469,13 @@ function renderOneCompartment(c, tier2) {
168535
169469
  const baseAttrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr2(c.title)}"`;
168536
169470
  if (tier2 >= 5)
168537
169471
  return "";
168538
- if (c.legacy === 1) {
168539
- if (tier2 >= 4)
169472
+ if (c.legacy === 1 || !isTieredRow(c)) {
169473
+ const flat = (c.content ?? "").trim();
169474
+ if (tier2 >= 4 || flat.length === 0)
168540
169475
  return `<compartment ${baseAttrs} />`;
168541
169476
  return [
168542
169477
  `<compartment ${baseAttrs}>`,
168543
- escapeXmlContent2(legacyBodyForTier(c.content, tier2)),
169478
+ escapeXmlContent2(legacyBodyForTier(flat, tier2)),
168544
169479
  "</compartment>"
168545
169480
  ].join(`
168546
169481
  `);
@@ -168721,6 +169656,7 @@ function isAftAvailable() {
168721
169656
  }
168722
169657
 
168723
169658
  // ../plugin/src/hooks/magic-context/key-files-block.ts
169659
+ init_logger();
168724
169660
  var cachedKeyFilesBySession = new Map;
168725
169661
  var staleUpdates = new Map;
168726
169662
  function staleKey(update2) {
@@ -168974,6 +169910,21 @@ function renderMemoryBlockV2(memories, wrapper = "project-memory") {
168974
169910
  `);
168975
169911
  }
168976
169912
 
169913
+ // ../plugin/src/tools/ctx-search/constants.ts
169914
+ var CTX_SEARCH_DESCRIPTION = `Your long-term recall for this project — search everything that ever happened here, not just what's currently visible.
169915
+
169916
+ Reach for it when something feels familiar but isn't in view: "did we solve this before?", "what did we decide about X?", "when did this break?", "where does Y live?". Results only contain things you CANNOT currently see — memories already shown in <project-memory> and the live conversation tail are filtered out.
169917
+
169918
+ Sources (omit for a broad search across all):
169919
+ - memory: curated cross-session project knowledge — rules, constraints, conventions.
169920
+ - message: the raw conversation behind your compacted history. Hits include message ordinals — expand the surrounding exchange with ctx_expand(start=N-10, end=N+5).
169921
+ - git_commit: this repository's commit history.
169922
+
169923
+ Picking sources:
169924
+ - "when did this change / was this working before" → ["git_commit", "message"]
169925
+ - "did we discuss this earlier" → ["message"]
169926
+ - "what's our convention / rule for X" → ["memory"]`;
169927
+
168977
169928
  // src/tools/ctx-search.ts
168978
169929
  var DEFAULT_LIMIT = 10;
168979
169930
  var ParamsSchema5 = exports_typebox.Object({
@@ -169057,18 +170008,7 @@ function createCtxSearchTool(deps) {
169057
170008
  return {
169058
170009
  name: "ctx_search",
169059
170010
  label: "Magic Context: Search",
169060
- description: `Search across project memories, indexed git commits, and raw conversation history.
169061
-
169062
- ` + `Sources:
169063
- ` + `- memory: curated cross-session knowledge for this project
169064
- ` + `- message: raw user/assistant messages from older compartmentalized history
169065
- ` + `- git_commit: HEAD git commits (when git commit indexing is enabled)
169066
-
169067
- ` + "Narrow via the `sources` param when the question maps to a specific channel:\n" + `- "was this working before / when did this break" → ["git_commit", "message"]
169068
- ` + `- "when did we change this" → ["git_commit"]
169069
- ` + `- "what is our naming convention" → ["memory"]
169070
- ` + `- "did we discuss this earlier" → ["message"]
169071
- ` + "Omit sources for a broad search across all enabled channels.",
170011
+ description: CTX_SEARCH_DESCRIPTION,
169072
170012
  parameters: ParamsSchema5,
169073
170013
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
169074
170014
  const query = params.query?.trim();
@@ -169087,6 +170027,7 @@ function createCtxSearchTool(deps) {
169087
170027
  const embeddingEnabled = snapshot ? snapshot.enabled || snapshot.gitCommitEnabled : deps.embeddingEnabled;
169088
170028
  const gitCommitsEnabled = snapshot?.gitCommitEnabled ?? deps.gitCommitsEnabled ?? false;
169089
170029
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, sessionId);
170030
+ const messageOrdinalCutoff = lastCompartmentEnd >= 0 ? lastCompartmentEnd : 0;
169090
170031
  const visibleMemoryIds = getVisibleMemoryIds(deps.db, sessionId);
169091
170032
  const results = await unifiedSearch(deps.db, sessionId, projectIdentity, query, {
169092
170033
  limit: normalizeLimit3(params.limit),
@@ -169097,7 +170038,7 @@ function createCtxSearchTool(deps) {
169097
170038
  return result?.vector ?? null;
169098
170039
  },
169099
170040
  isEmbeddingRuntimeEnabled: () => embeddingEnabled === true,
169100
- maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined,
170041
+ maxMessageOrdinal: messageOrdinalCutoff,
169101
170042
  gitCommitsEnabled,
169102
170043
  sources: params.sources,
169103
170044
  visibleMemoryIds,
@@ -169184,13 +170125,15 @@ function registerMagicContextTools(pi, opts) {
169184
170125
  embeddingEnabled: opts.embeddingEnabled,
169185
170126
  gitCommitsEnabled: opts.gitCommitsEnabled
169186
170127
  }));
169187
- pi.registerTool(createCtxMemoryTool({
169188
- db: opts.db,
169189
- ensureProjectRegistered: opts.ensureProjectRegistered,
169190
- memoryEnabled: opts.memoryEnabled,
169191
- embeddingEnabled: opts.embeddingEnabled,
169192
- allowDreamerActions: opts.allowDreamerActions ?? false
169193
- }));
170128
+ if (opts.memoryToolEnabled !== false) {
170129
+ pi.registerTool(createCtxMemoryTool({
170130
+ db: opts.db,
170131
+ ensureProjectRegistered: opts.ensureProjectRegistered,
170132
+ memoryEnabled: opts.memoryEnabled,
170133
+ embeddingEnabled: opts.embeddingEnabled,
170134
+ allowDreamerActions: opts.allowDreamerActions ?? false
170135
+ }));
170136
+ }
169194
170137
  if (!opts.sessionScopedToolsDisabled) {
169195
170138
  pi.registerTool(createCtxNoteTool({
169196
170139
  db: opts.db,
@@ -169213,7 +170156,7 @@ var openedDb;
169213
170156
  function magicContextSubagentExtension(pi) {
169214
170157
  setHarness("pi");
169215
170158
  pi.registerFlag(SUBAGENT_DREAMER_ACTIONS_FLAG, {
169216
- description: "Enable dreamer-only ctx_memory actions (update, merge, archive).",
170159
+ description: "Register ctx_memory with dreamer actions for Magic Context subagents.",
169217
170160
  type: "boolean",
169218
170161
  default: false
169219
170162
  });
@@ -169231,10 +170174,11 @@ function magicContextSubagentExtension(pi) {
169231
170174
  registerMagicContextTools(pi, {
169232
170175
  db,
169233
170176
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
170177
+ memoryToolEnabled: dreamerActionsEnabled,
169234
170178
  allowDreamerActions: dreamerActionsEnabled,
169235
170179
  sessionScopedToolsDisabled: true
169236
170180
  });
169237
- log(`[pi-subagent] registered tools: ctx_search, ctx_memory` + ` (ctx_note/ctx_expand omitted: --no-session child;` + ` memory=${cfg.memory.enabled}, embedding=${cfg.embedding.provider !== "off"},` + ` git_commits=${cfg.memory.git_commit_indexing.enabled}, dreamer_actions=${dreamerActionsEnabled})`);
170181
+ log(`[pi-subagent] registered tools: ctx_search${dreamerActionsEnabled ? ", ctx_memory" : ""}` + ` (ctx_note/ctx_expand omitted: --no-session child;` + ` memory=${cfg.memory.enabled}, embedding=${cfg.embedding.provider !== "off"},` + ` git_commits=${cfg.memory.git_commit_indexing.enabled}, dreamer_actions=${dreamerActionsEnabled})`);
169238
170182
  } catch (err5) {
169239
170183
  const message = err5 instanceof Error ? err5.message : String(err5);
169240
170184
  log(`[pi-subagent] startup failed: ${message}`);