@cortexkit/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, ".opencode", "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, ".opencode", "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";
@@ -142876,6 +143020,74 @@ function migrateLegacyExperimental(rawConfig, warnings) {
142876
143020
  return patched;
142877
143021
  }
142878
143022
 
143023
+ // ../plugin/src/config/project-security.ts
143024
+ var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
143025
+ var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
143026
+ function isPlainObject2(value) {
143027
+ return typeof value === "object" && value !== null && !Array.isArray(value);
143028
+ }
143029
+ function stripUnsafeProjectConfigFields(projectRaw) {
143030
+ const warnings = [];
143031
+ if ("auto_update" in projectRaw) {
143032
+ delete projectRaw.auto_update;
143033
+ warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
143034
+ }
143035
+ if ("sqlite" in projectRaw) {
143036
+ delete projectRaw.sqlite;
143037
+ 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).");
143038
+ }
143039
+ for (const agentKey of HIDDEN_AGENT_KEYS) {
143040
+ const block = projectRaw[agentKey];
143041
+ if (!isPlainObject2(block))
143042
+ continue;
143043
+ const removed = [];
143044
+ for (const field of AGENT_ESCALATION_FIELDS) {
143045
+ if (field in block) {
143046
+ delete block[field];
143047
+ removed.push(field);
143048
+ }
143049
+ }
143050
+ if (removed.length > 0) {
143051
+ warnings.push(`Ignoring ${agentKey}.${removed.join("/")} from project config ` + "(security: a repository cannot reprogram or re-permission hidden agents).");
143052
+ }
143053
+ }
143054
+ return warnings;
143055
+ }
143056
+ function normalizeEndpoint(value) {
143057
+ if (typeof value !== "string")
143058
+ return;
143059
+ const trimmed = value.trim().replace(/\/+$/, "");
143060
+ return trimmed.length > 0 ? trimmed.toLowerCase() : undefined;
143061
+ }
143062
+ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
143063
+ const projectEmbedding = projectRaw.embedding;
143064
+ if (!isPlainObject2(projectEmbedding))
143065
+ return [];
143066
+ const redirectsEndpoint = "endpoint" in projectEmbedding;
143067
+ if (!redirectsEndpoint)
143068
+ return [];
143069
+ const userEmbedding = userRaw?.embedding;
143070
+ if (isPlainObject2(userEmbedding)) {
143071
+ const projectEndpoint = normalizeEndpoint(projectEmbedding.endpoint);
143072
+ const userEndpoint = normalizeEndpoint(userEmbedding.endpoint);
143073
+ if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
143074
+ return [];
143075
+ }
143076
+ }
143077
+ const providesOwnKey = typeof projectEmbedding.api_key === "string" && projectEmbedding.api_key.length > 0;
143078
+ if (providesOwnKey)
143079
+ return [];
143080
+ const mergedEmbedding = mergedRaw.embedding;
143081
+ if (!isPlainObject2(mergedEmbedding))
143082
+ return [];
143083
+ if (!("api_key" in mergedEmbedding))
143084
+ return [];
143085
+ delete mergedEmbedding.api_key;
143086
+ return [
143087
+ "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)."
143088
+ ];
143089
+ }
143090
+
142879
143091
  // ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
142880
143092
  var exports_external = {};
142881
143093
  __export(exports_external, {
@@ -143507,7 +143719,7 @@ __export(exports_util, {
143507
143719
  jsonStringifyReplacer: () => jsonStringifyReplacer,
143508
143720
  joinValues: () => joinValues,
143509
143721
  issue: () => issue,
143510
- isPlainObject: () => isPlainObject,
143722
+ isPlainObject: () => isPlainObject3,
143511
143723
  isObject: () => isObject,
143512
143724
  hexToUint8Array: () => hexToUint8Array,
143513
143725
  getSizableOrigin: () => getSizableOrigin,
@@ -143689,7 +143901,7 @@ var allowsEval = /* @__PURE__ */ cached(() => {
143689
143901
  return false;
143690
143902
  }
143691
143903
  });
143692
- function isPlainObject(o) {
143904
+ function isPlainObject3(o) {
143693
143905
  if (isObject(o) === false)
143694
143906
  return false;
143695
143907
  const ctor = o.constructor;
@@ -143706,7 +143918,7 @@ function isPlainObject(o) {
143706
143918
  return true;
143707
143919
  }
143708
143920
  function shallowClone(o) {
143709
- if (isPlainObject(o))
143921
+ if (isPlainObject3(o))
143710
143922
  return { ...o };
143711
143923
  if (Array.isArray(o))
143712
143924
  return [...o];
@@ -143910,7 +144122,7 @@ function omit(schema, mask) {
143910
144122
  return clone(schema, def);
143911
144123
  }
143912
144124
  function extend(schema, shape) {
143913
- if (!isPlainObject(shape)) {
144125
+ if (!isPlainObject3(shape)) {
143914
144126
  throw new Error("Invalid input to extend: expected a plain object");
143915
144127
  }
143916
144128
  const checks = schema._zod.def.checks;
@@ -143933,7 +144145,7 @@ function extend(schema, shape) {
143933
144145
  return clone(schema, def);
143934
144146
  }
143935
144147
  function safeExtend(schema, shape) {
143936
- if (!isPlainObject(shape)) {
144148
+ if (!isPlainObject3(shape)) {
143937
144149
  throw new Error("Invalid input to safeExtend: expected a plain object");
143938
144150
  }
143939
144151
  const def = mergeDefs(schema._zod.def, {
@@ -146276,7 +146488,7 @@ function mergeValues(a, b) {
146276
146488
  if (a instanceof Date && b instanceof Date && +a === +b) {
146277
146489
  return { valid: true, data: a };
146278
146490
  }
146279
- if (isPlainObject(a) && isPlainObject(b)) {
146491
+ if (isPlainObject3(a) && isPlainObject3(b)) {
146280
146492
  const bKeys = Object.keys(b);
146281
146493
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
146282
146494
  const newObj = { ...a, ...b };
@@ -146462,7 +146674,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
146462
146674
  $ZodType.init(inst, def);
146463
146675
  inst._zod.parse = (payload, ctx) => {
146464
146676
  const input = payload.value;
146465
- if (!isPlainObject(input)) {
146677
+ if (!isPlainObject3(input)) {
146466
146678
  payload.issues.push({
146467
146679
  expected: "record",
146468
146680
  code: "invalid_type",
@@ -157182,7 +157394,6 @@ var AgentOverrideConfigSchema = exports_external.object({
157182
157394
  });
157183
157395
 
157184
157396
  // ../plugin/src/config/schema/magic-context.ts
157185
- var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4;
157186
157397
  var DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65;
157187
157398
  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.";
157188
157399
  var DEFAULT_HISTORIAN_TIMEOUT_MS = 300000;
@@ -157281,7 +157492,6 @@ var MagicContextConfigSchema = exports_external.object({
157281
157492
  historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
157282
157493
  dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
157283
157494
  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" })'),
157284
- 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)"),
157285
157495
  execute_threshold_percentage: exports_external.union([
157286
157496
  exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE),
157287
157497
  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))
@@ -157290,10 +157500,7 @@ var MagicContextConfigSchema = exports_external.object({
157290
157500
  default: exports_external.number().min(5000).max(2000000).optional()
157291
157501
  }).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."),
157292
157502
  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)"),
157293
- 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)"),
157294
- drop_tool_structure: exports_external.boolean().default(true).describe("When true, dropped tool parts are fully removed instead of truncated in place (default: true)"),
157295
157503
  clear_reasoning_age: exports_external.number().min(10).default(50).describe("Clear reasoning/thinking blocks older than N tags (default: 50)"),
157296
- 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)"),
157297
157504
  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)"),
157298
157505
  historian_timeout_ms: exports_external.number().min(60000).default(DEFAULT_HISTORIAN_TIMEOUT_MS).describe("Timeout for each historian prompt call in milliseconds (default: 300000)"),
157299
157506
  commit_cluster_trigger: exports_external.object({
@@ -157416,6 +157623,21 @@ function stripJsonComments(content) {
157416
157623
  // ../plugin/src/config/variable.ts
157417
157624
  var ENV_PATTERN = /\{env:([^}]+)\}/g;
157418
157625
  var FILE_PATTERN = /\{file:([^}]+)\}/g;
157626
+ function sensitiveFilePathReason(resolvedPath) {
157627
+ const home = homedir2();
157628
+ const sensitiveDirs = [
157629
+ { dir: resolve2(home, ".ssh"), label: "SSH keys" },
157630
+ { dir: resolve2(home, ".aws"), label: "AWS credentials" },
157631
+ { dir: resolve2(home, ".gnupg"), label: "GnuPG keyring" },
157632
+ { dir: resolve2(home, ".config", "gh"), label: "GitHub CLI auth" }
157633
+ ];
157634
+ for (const { dir, label } of sensitiveDirs) {
157635
+ if (resolvedPath === dir || resolvedPath.startsWith(`${dir}/`)) {
157636
+ return label;
157637
+ }
157638
+ }
157639
+ return null;
157640
+ }
157419
157641
  function substituteConfigVariables(input) {
157420
157642
  const warnings = [];
157421
157643
  let text = input.text;
@@ -157469,6 +157691,10 @@ function substituteConfigVariables(input) {
157469
157691
  } else if (!isAbsolute(filePath)) {
157470
157692
  filePath = resolve2(configDir, filePath);
157471
157693
  }
157694
+ const sensitiveReason = sensitiveFilePathReason(filePath);
157695
+ if (sensitiveReason) {
157696
+ warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
157697
+ }
157472
157698
  if (!existsSync4(filePath)) {
157473
157699
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
157474
157700
  continue;
@@ -157507,7 +157733,8 @@ function loadConfigFile(path3, scope) {
157507
157733
  const rawText = readFileSync3(path3, "utf-8");
157508
157734
  const substituted = substituteConfigVariables({
157509
157735
  text: rawText,
157510
- configPath: path3
157736
+ configPath: path3,
157737
+ isProjectConfig: scope === "project"
157511
157738
  });
157512
157739
  return {
157513
157740
  path: path3,
@@ -157549,14 +157776,14 @@ function redactConfigValue(value) {
157549
157776
  }
157550
157777
  return typeof value;
157551
157778
  }
157552
- function isPlainObject2(value) {
157779
+ function isPlainObject4(value) {
157553
157780
  return typeof value === "object" && value !== null && !Array.isArray(value);
157554
157781
  }
157555
157782
  function mergeRawConfigs(base, override) {
157556
157783
  const merged = { ...base };
157557
157784
  for (const [key, overrideValue] of Object.entries(override)) {
157558
157785
  const baseValue = merged[key];
157559
- merged[key] = isPlainObject2(baseValue) && isPlainObject2(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
157786
+ merged[key] = isPlainObject4(baseValue) && isPlainObject4(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
157560
157787
  }
157561
157788
  return merged;
157562
157789
  }
@@ -157570,10 +157797,15 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
157570
157797
  }
157571
157798
  const defaults = MagicContextConfigSchema.parse({});
157572
157799
  const errorPaths = new Set;
157800
+ const issuePathsByKey = new Map;
157573
157801
  for (const issue2 of parsed.error.issues) {
157574
157802
  const topKey = issue2.path[0];
157575
157803
  if (topKey !== undefined) {
157576
- errorPaths.add(String(topKey));
157804
+ const key = String(topKey);
157805
+ errorPaths.add(key);
157806
+ const paths = issuePathsByKey.get(key) ?? [];
157807
+ paths.push([...issue2.path]);
157808
+ issuePathsByKey.set(key, paths);
157577
157809
  }
157578
157810
  }
157579
157811
  const patched = { ...migrated };
@@ -157581,11 +157813,32 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
157581
157813
  for (const key of errorPaths) {
157582
157814
  recoveredTopLevelKeys.push(key);
157583
157815
  const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
157584
- delete patched[key];
157585
157816
  if (isAgentConfig) {
157817
+ delete patched[key];
157586
157818
  warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
157587
157819
  continue;
157588
157820
  }
157821
+ const issuePaths = issuePathsByKey.get(key) ?? [];
157822
+ const rawValue = migrated[key];
157823
+ const allNested = issuePaths.length > 0 && issuePaths.every((p) => p.length >= 2) && typeof rawValue === "object" && rawValue !== null && !Array.isArray(rawValue);
157824
+ if (allNested) {
157825
+ let prunedBlock = {
157826
+ ...rawValue
157827
+ };
157828
+ const prunedLeaves = [];
157829
+ for (const p of issuePaths) {
157830
+ const relative = p.slice(1);
157831
+ const result = pruneNestedConfigLeaf(prunedBlock, relative);
157832
+ if (result) {
157833
+ prunedBlock = result.block;
157834
+ prunedLeaves.push(result.removed);
157835
+ }
157836
+ }
157837
+ patched[key] = prunedBlock;
157838
+ warnings.push(`"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.`);
157839
+ continue;
157840
+ }
157841
+ delete patched[key];
157589
157842
  const defaultValue = defaults[key];
157590
157843
  warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultValue)}.`);
157591
157844
  }
@@ -157618,10 +157871,22 @@ function loadPiConfig(opts = {}) {
157618
157871
  return 0;
157619
157872
  return a.scope === "user" ? -1 : 1;
157620
157873
  });
157874
+ const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
157621
157875
  for (const loaded of mergeFiles) {
157622
157876
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
157623
157877
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
157624
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157878
+ if (loaded.scope === "project") {
157879
+ const projectRaw = { ...loaded.config };
157880
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
157881
+ warnings.push(`${prefix} ${warning}`);
157882
+ }
157883
+ rawConfig = mergeRawConfigs(rawConfig, projectRaw);
157884
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
157885
+ warnings.push(`${prefix} ${warning}`);
157886
+ }
157887
+ } else {
157888
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157889
+ }
157625
157890
  }
157626
157891
  const parsed = parsePiConfig(rawConfig);
157627
157892
  warnings.push(...parsed.warnings.map((warning) => `[merged config] ${warning}`));
@@ -157696,10 +157961,22 @@ function loadPiConfigDetailed(opts = {}) {
157696
157961
  return 0;
157697
157962
  return a.scope === "user" ? -1 : 1;
157698
157963
  });
157964
+ const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
157699
157965
  for (const loaded of mergeFiles) {
157700
157966
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
157701
157967
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
157702
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157968
+ if (loaded.scope === "project") {
157969
+ const projectRaw = { ...loaded.config };
157970
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
157971
+ warnings.push(`${prefix} ${warning}`);
157972
+ }
157973
+ rawConfig = mergeRawConfigs(rawConfig, projectRaw);
157974
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
157975
+ warnings.push(`${prefix} ${warning}`);
157976
+ }
157977
+ } else {
157978
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157979
+ }
157703
157980
  }
157704
157981
  const recoveredTopLevelKeys = [];
157705
157982
  const parsed = parsePiConfig(rawConfig, recoveredTopLevelKeys);
@@ -157726,6 +158003,9 @@ function loadPiConfigDetailed(opts = {}) {
157726
158003
  };
157727
158004
  }
157728
158005
 
158006
+ // ../plugin/src/features/magic-context/memory/embedding.ts
158007
+ init_logger();
158008
+
157729
158009
  // ../plugin/src/features/magic-context/memory/cosine-similarity.ts
157730
158010
  function cosineSimilarity(a, b) {
157731
158011
  if (a.length !== b.length) {
@@ -157754,7 +158034,7 @@ function computeNormalizedHash(content) {
157754
158034
  }
157755
158035
 
157756
158036
  // ../plugin/src/features/magic-context/memory/embedding-identity.ts
157757
- function normalizeEndpoint(endpoint) {
158037
+ function normalizeEndpoint2(endpoint) {
157758
158038
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
157759
158039
  }
157760
158040
  function getEmbeddingProviderIdentity(config2) {
@@ -157764,7 +158044,7 @@ function getEmbeddingProviderIdentity(config2) {
157764
158044
  const identityInput = config2.provider === "openai-compatible" ? {
157765
158045
  provider: "openai-compatible",
157766
158046
  model: config2.model.trim(),
157767
- endpoint: normalizeEndpoint(config2.endpoint),
158047
+ endpoint: normalizeEndpoint2(config2.endpoint),
157768
158048
  apiKeyPresent: Boolean(config2.api_key?.trim()),
157769
158049
  inputType: config2.input_type?.trim() || ""
157770
158050
  } : {
@@ -157781,6 +158061,8 @@ import { mkdirSync as mkdirSync3 } from "node:fs";
157781
158061
  import { open, stat, unlink, writeFile } from "node:fs/promises";
157782
158062
  import { dirname as dirname4, join as join6 } from "node:path";
157783
158063
  import { pathToFileURL } from "node:url";
158064
+ init_data_path();
158065
+ init_logger();
157784
158066
  var LOCK_POLL_MS = 150;
157785
158067
  var STALE_LOCK_MS = 3 * 60000;
157786
158068
  var MAX_LOCK_WAIT_MS = 5 * 60000;
@@ -158116,7 +158398,63 @@ class LocalEmbeddingProvider {
158116
158398
  }
158117
158399
 
158118
158400
  // ../plugin/src/features/magic-context/memory/embedding-openai.ts
158119
- function normalizeEndpoint2(endpoint) {
158401
+ init_logger();
158402
+
158403
+ // ../plugin/src/features/magic-context/memory/embedding-ssrf.ts
158404
+ var METADATA_HOSTNAMES = new Set(["metadata.google.internal", "metadata.goog"]);
158405
+ var IPV6_METADATA_HOSTS = new Set(["fd00:ec2::254"]);
158406
+ function isLinkLocalIpv4(host) {
158407
+ return /^169\.254\.\d{1,3}\.\d{1,3}$/.test(host);
158408
+ }
158409
+ function ipv4FromMappedIpv6(host) {
158410
+ const m = /^::ffff:(.+)$/.exec(host);
158411
+ if (!m)
158412
+ return null;
158413
+ const tail = m[1];
158414
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(tail))
158415
+ return tail;
158416
+ const hex3 = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(tail);
158417
+ if (hex3) {
158418
+ const hi = Number.parseInt(hex3[1], 16);
158419
+ const lo = Number.parseInt(hex3[2], 16);
158420
+ if (Number.isNaN(hi) || Number.isNaN(lo))
158421
+ return null;
158422
+ return `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
158423
+ }
158424
+ return null;
158425
+ }
158426
+ function blockedEmbeddingEndpointReason(endpoint) {
158427
+ const trimmed = endpoint.trim();
158428
+ if (trimmed.length === 0)
158429
+ return null;
158430
+ let url2;
158431
+ try {
158432
+ url2 = new URL(trimmed);
158433
+ } catch {
158434
+ return `embedding endpoint is not a valid URL: ${trimmed}`;
158435
+ }
158436
+ const host = url2.hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
158437
+ if (METADATA_HOSTNAMES.has(host)) {
158438
+ return `embedding endpoint host ${host} is a cloud metadata service (blocked)`;
158439
+ }
158440
+ if (IPV6_METADATA_HOSTS.has(host)) {
158441
+ return `embedding endpoint host ${host} is the AWS IPv6 metadata service (blocked)`;
158442
+ }
158443
+ if (isLinkLocalIpv4(host)) {
158444
+ return `embedding endpoint host ${host} is link-local / cloud metadata (blocked)`;
158445
+ }
158446
+ const mappedV4 = ipv4FromMappedIpv6(host);
158447
+ if (mappedV4 && isLinkLocalIpv4(mappedV4)) {
158448
+ return `embedding endpoint host ${host} (IPv4-mapped ${mappedV4}) is link-local / cloud metadata (blocked)`;
158449
+ }
158450
+ if (host.startsWith("fe80:")) {
158451
+ return `embedding endpoint host ${host} is link-local / cloud metadata (blocked)`;
158452
+ }
158453
+ return null;
158454
+ }
158455
+
158456
+ // ../plugin/src/features/magic-context/memory/embedding-openai.ts
158457
+ function normalizeEndpoint3(endpoint) {
158120
158458
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
158121
158459
  }
158122
158460
  var FAILURE_THRESHOLD = 3;
@@ -158137,7 +158475,7 @@ class OpenAICompatibleEmbeddingProvider {
158137
158475
  openLogged = false;
158138
158476
  halfOpenProbeInFlight = false;
158139
158477
  constructor(options) {
158140
- this.endpoint = normalizeEndpoint2(options.endpoint);
158478
+ this.endpoint = normalizeEndpoint3(options.endpoint);
158141
158479
  this.model = options.model?.trim() ?? "";
158142
158480
  this.apiKey = options.apiKey?.trim() ?? "";
158143
158481
  this.inputType = options.inputType?.trim() ?? "";
@@ -158157,6 +158495,12 @@ class OpenAICompatibleEmbeddingProvider {
158157
158495
  this.initialized = false;
158158
158496
  return false;
158159
158497
  }
158498
+ const blockedReason = blockedEmbeddingEndpointReason(this.endpoint);
158499
+ if (blockedReason) {
158500
+ log(`[magic-context] embedding endpoint blocked: ${blockedReason}`);
158501
+ this.initialized = false;
158502
+ return false;
158503
+ }
158160
158504
  this.initialized = true;
158161
158505
  return true;
158162
158506
  }
@@ -158202,6 +158546,7 @@ class OpenAICompatibleEmbeddingProvider {
158202
158546
  ...this.inputType ? { input_type: this.inputType } : {},
158203
158547
  ...this.truncate ? { truncate: this.truncate } : {}
158204
158548
  }),
158549
+ redirect: "error",
158205
158550
  signal: internalController.signal
158206
158551
  });
158207
158552
  if (!response.ok) {
@@ -158406,6 +158751,7 @@ function getDistinctStoredModelIds(db, projectPath) {
158406
158751
 
158407
158752
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
158408
158753
  import { createHash as createHash3, randomUUID } from "node:crypto";
158754
+ init_logger();
158409
158755
 
158410
158756
  // ../plugin/src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
158411
158757
  var saveStatements = new WeakMap;
@@ -158681,11 +159027,15 @@ function resolveEmbeddingConfig(config2) {
158681
159027
  }
158682
159028
  if (config2.provider === "openai-compatible") {
158683
159029
  const apiKey = config2.api_key?.trim();
159030
+ const inputType = config2.input_type?.trim();
159031
+ const truncate = config2.truncate?.trim();
158684
159032
  return {
158685
159033
  provider: "openai-compatible",
158686
159034
  model: config2.model.trim(),
158687
159035
  endpoint: config2.endpoint.trim(),
158688
- ...apiKey ? { api_key: apiKey } : {}
159036
+ ...apiKey ? { api_key: apiKey } : {},
159037
+ ...inputType ? { input_type: inputType } : {},
159038
+ ...truncate ? { truncate } : {}
158689
159039
  };
158690
159040
  }
158691
159041
  return { provider: "off" };
@@ -158701,7 +159051,9 @@ function createProvider(config2) {
158701
159051
  return new OpenAICompatibleEmbeddingProvider({
158702
159052
  endpoint: config2.endpoint,
158703
159053
  model: config2.model,
158704
- apiKey: config2.api_key
159054
+ apiKey: config2.api_key,
159055
+ inputType: config2.input_type,
159056
+ truncate: config2.truncate
158705
159057
  });
158706
159058
  }
158707
159059
  return new LocalEmbeddingProvider(config2.model);
@@ -159140,17 +159492,41 @@ function hasGitDir(canonical) {
159140
159492
  return false;
159141
159493
  }
159142
159494
  }
159495
+ function normalizeStoredProjectPath(rawOrStored) {
159496
+ if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
159497
+ return rawOrStored;
159498
+ }
159499
+ try {
159500
+ return resolveProjectIdentity(rawOrStored);
159501
+ } catch {
159502
+ return directoryFallback(rawOrStored);
159503
+ }
159504
+ }
159505
+ function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
159506
+ return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
159507
+ }
159143
159508
 
159144
159509
  // ../plugin/src/plugin/embedding-bootstrap-helpers.ts
159145
159510
  import { createHash as createHash5 } from "node:crypto";
159511
+ init_logger();
159146
159512
  var EMBEDDING_AFFECTING_KEYS = new Set([
159147
159513
  "embedding.api_key",
159148
159514
  "embedding.endpoint",
159149
159515
  "embedding.model",
159150
- "embedding.provider"
159516
+ "embedding.provider",
159517
+ "embedding.input_type",
159518
+ "embedding.truncate"
159151
159519
  ]);
159152
159520
  var EMBEDDING_AFFECTING_TOP_LEVEL_KEYS = new Set(["embedding", "memory", "experimental"]);
159153
- var EMBEDDING_WARNING_TERMS = ["api_key", "endpoint", "model", "provider", "embedding"];
159521
+ var EMBEDDING_WARNING_TERMS = [
159522
+ "api_key",
159523
+ "endpoint",
159524
+ "model",
159525
+ "provider",
159526
+ "embedding",
159527
+ "input_type",
159528
+ "truncate"
159529
+ ];
159154
159530
  var loggedFailureSignatures = new Map;
159155
159531
  function sha256Prefix2(value, length = 16) {
159156
159532
  return createHash5("sha256").update(value).digest("hex").slice(0, length);
@@ -159230,7 +159606,6 @@ async function ensureProjectRegisteredFromPiDirectory(directory, db) {
159230
159606
  };
159231
159607
  registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, detailed.config.embedding, features, directory);
159232
159608
  }
159233
-
159234
159609
  // ../plugin/src/features/magic-context/compartment-lease.ts
159235
159610
  var COMPARTMENT_LEASE_TTL_MS = 5 * 60 * 1000;
159236
159611
  var COMPARTMENT_LEASE_RENEWAL_MS = 60 * 1000;
@@ -159328,6 +159703,14 @@ var SESSION_META_SELECT_COLUMNS = [
159328
159703
  "cached_m0_tool_set_hash",
159329
159704
  "cached_m0_model_key",
159330
159705
  "last_observed_model_key",
159706
+ "last_usage_context_limit",
159707
+ "prior_boundary_ordinal",
159708
+ "protected_tail_policy_version",
159709
+ "protected_tail_drain_window_started_at",
159710
+ "protected_tail_drain_tokens",
159711
+ "recovery_no_eligible_head_count",
159712
+ "force_emergency_bypass_window_start",
159713
+ "force_emergency_bypass_used",
159331
159714
  "upgrade_reminded_at",
159332
159715
  "pi_stable_id_scheme"
159333
159716
  ];
@@ -159367,6 +159750,14 @@ var META_COLUMNS = {
159367
159750
  cachedM0ToolSetHash: "cached_m0_tool_set_hash",
159368
159751
  cachedM0ModelKey: "cached_m0_model_key",
159369
159752
  lastObservedModelKey: "last_observed_model_key",
159753
+ lastUsageContextLimit: "last_usage_context_limit",
159754
+ priorBoundaryOrdinal: "prior_boundary_ordinal",
159755
+ protectedTailPolicyVersion: "protected_tail_policy_version",
159756
+ protectedTailDrainWindowStartedAt: "protected_tail_drain_window_started_at",
159757
+ protectedTailDrainTokens: "protected_tail_drain_tokens",
159758
+ recoveryNoEligibleHeadCount: "recovery_no_eligible_head_count",
159759
+ forceEmergencyBypassWindowStart: "force_emergency_bypass_window_start",
159760
+ forceEmergencyBypassUsed: "force_emergency_bypass_used",
159370
159761
  upgradeRemindedAt: "upgrade_reminded_at",
159371
159762
  piStableIdScheme: "pi_stable_id_scheme"
159372
159763
  };
@@ -159408,7 +159799,7 @@ function isSessionMetaRow(row) {
159408
159799
  if (row === null || typeof row !== "object")
159409
159800
  return false;
159410
159801
  const r = row;
159411
- 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);
159802
+ 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);
159412
159803
  }
159413
159804
  function getDefaultSessionMeta(sessionId) {
159414
159805
  return {
@@ -159448,6 +159839,14 @@ function getDefaultSessionMeta(sessionId) {
159448
159839
  cachedM0ToolSetHash: null,
159449
159840
  cachedM0ModelKey: null,
159450
159841
  lastObservedModelKey: null,
159842
+ lastUsageContextLimit: 0,
159843
+ priorBoundaryOrdinal: 1,
159844
+ protectedTailPolicyVersion: 0,
159845
+ protectedTailDrainWindowStartedAt: 0,
159846
+ protectedTailDrainTokens: 0,
159847
+ recoveryNoEligibleHeadCount: 0,
159848
+ forceEmergencyBypassWindowStart: 0,
159849
+ forceEmergencyBypassUsed: 0,
159451
159850
  upgradeRemindedAt: null,
159452
159851
  piStableIdScheme: null
159453
159852
  };
@@ -159502,6 +159901,14 @@ function toSessionMeta(row) {
159502
159901
  cachedM0ToolSetHash: stringOrNull(row.cached_m0_tool_set_hash),
159503
159902
  cachedM0ModelKey: stringOrNull(row.cached_m0_model_key),
159504
159903
  lastObservedModelKey: stringOrNull(row.last_observed_model_key),
159904
+ lastUsageContextLimit: numOrZero(row.last_usage_context_limit),
159905
+ priorBoundaryOrdinal: Math.max(1, numOrZero(row.prior_boundary_ordinal) || 1),
159906
+ protectedTailPolicyVersion: numOrZero(row.protected_tail_policy_version),
159907
+ protectedTailDrainWindowStartedAt: numOrZero(row.protected_tail_drain_window_started_at),
159908
+ protectedTailDrainTokens: numOrZero(row.protected_tail_drain_tokens),
159909
+ recoveryNoEligibleHeadCount: numOrZero(row.recovery_no_eligible_head_count),
159910
+ forceEmergencyBypassWindowStart: numOrZero(row.force_emergency_bypass_window_start),
159911
+ forceEmergencyBypassUsed: numOrZero(row.force_emergency_bypass_used),
159505
159912
  upgradeRemindedAt: numOrNull(row.upgrade_reminded_at),
159506
159913
  piStableIdScheme: numOrNull(row.pi_stable_id_scheme)
159507
159914
  };
@@ -159586,12 +159993,6 @@ function isCompartmentRow(row) {
159586
159993
  const candidate = row;
159587
159994
  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";
159588
159995
  }
159589
- function isSessionFactRow(row) {
159590
- if (row === null || typeof row !== "object")
159591
- return false;
159592
- const candidate = row;
159593
- 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";
159594
- }
159595
159996
  function insertCompartmentRows(db, sessionId, compartments, now) {
159596
159997
  const stmt = getInsertCompartmentStatement(db);
159597
159998
  for (const compartment of compartments) {
@@ -159620,16 +160021,6 @@ function toCompartment(row) {
159620
160021
  createdAt: row.created_at
159621
160022
  };
159622
160023
  }
159623
- function toSessionFact(row) {
159624
- return {
159625
- id: row.id,
159626
- sessionId: row.session_id,
159627
- category: row.category,
159628
- content: row.content,
159629
- createdAt: row.created_at,
159630
- updatedAt: row.updated_at
159631
- };
159632
- }
159633
160024
  function getCompartments(db, sessionId) {
159634
160025
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? ORDER BY sequence ASC").all(sessionId).filter(isCompartmentRow);
159635
160026
  return rows.map(toCompartment);
@@ -159638,6 +160029,11 @@ function getLastCompartmentEndMessage(db, sessionId) {
159638
160029
  const row = db.prepare("SELECT MAX(end_message) as max_end FROM compartments WHERE session_id = ?").get(sessionId);
159639
160030
  return row?.max_end ?? -1;
159640
160031
  }
160032
+ function getLastCompartmentEndMessageId(db, sessionId) {
160033
+ const row = db.prepare("SELECT end_message_id FROM compartments WHERE session_id = ? ORDER BY sequence DESC LIMIT 1").get(sessionId);
160034
+ const id = row?.end_message_id;
160035
+ return id && id.length > 0 ? id : null;
160036
+ }
159641
160037
  function getCompartmentsByEndMessageId(db, sessionId, endMessageId) {
159642
160038
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? AND end_message_id = ? ORDER BY sequence ASC").all(sessionId, endMessageId).filter(isCompartmentRow);
159643
160039
  return rows.map(toCompartment);
@@ -159650,10 +160046,6 @@ function appendCompartments(db, sessionId, compartments) {
159650
160046
  insertCompartmentRows(db, sessionId, compartments, now);
159651
160047
  })();
159652
160048
  }
159653
- function getSessionFacts(db, sessionId) {
159654
- const rows = db.prepare("SELECT * FROM session_facts WHERE session_id = ? ORDER BY category ASC, id ASC").all(sessionId).filter(isSessionFactRow);
159655
- return rows.map(toSessionFact);
159656
- }
159657
160049
  function saveRecompStagingPass(db, sessionId, passNumber, compartments, facts) {
159658
160050
  const now = Date.now();
159659
160051
  db.transaction(() => {
@@ -159697,14 +160089,6 @@ function getRecompStaging(db, sessionId) {
159697
160089
  lastEndMessage: lastEnd
159698
160090
  };
159699
160091
  }
159700
- function invalidateAllMemoryBlockCaches(db) {
159701
- try {
159702
- const rows = db.prepare("SELECT session_id FROM session_meta").all();
159703
- for (const row of rows) {
159704
- clearCachedM0M1(db, row.session_id);
159705
- }
159706
- } catch {}
159707
- }
159708
160092
  function clearRecompStaging(db, sessionId) {
159709
160093
  db.transaction(() => {
159710
160094
  db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
@@ -159752,6 +160136,8 @@ function escapeXmlContent(s) {
159752
160136
  }
159753
160137
 
159754
160138
  // ../plugin/src/hooks/magic-context/read-session-db.ts
160139
+ init_data_path();
160140
+ init_logger();
159755
160141
  import { existsSync as existsSync6 } from "node:fs";
159756
160142
  import { join as join7 } from "node:path";
159757
160143
  function getOpenCodeDbPath() {
@@ -159824,13 +160210,27 @@ function parseJsonUnknown(value) {
159824
160210
  return null;
159825
160211
  }
159826
160212
  }
160213
+ function attachRawPartVersion(value, timeUpdated) {
160214
+ if (value === null || typeof value !== "object" || Array.isArray(value))
160215
+ return value;
160216
+ if (typeof timeUpdated !== "number")
160217
+ return value;
160218
+ try {
160219
+ Object.defineProperty(value, "__magicContextPartUpdatedAt", {
160220
+ value: timeUpdated,
160221
+ enumerable: false,
160222
+ configurable: true
160223
+ });
160224
+ } catch {}
160225
+ return value;
160226
+ }
159827
160227
  function readRawSessionMessagesFromDb(db, sessionId) {
159828
- const messageRows = db.prepare("SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
159829
- const partRows = db.prepare("SELECT message_id, data FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
160228
+ 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);
160229
+ 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);
159830
160230
  const partsByMessageId = new Map;
159831
160231
  for (const part of partRows) {
159832
160232
  const list = partsByMessageId.get(part.message_id) ?? [];
159833
- list.push(parseJsonUnknown(part.data));
160233
+ list.push(attachRawPartVersion(parseJsonUnknown(part.data), part.time_updated));
159834
160234
  partsByMessageId.set(part.message_id, list);
159835
160235
  }
159836
160236
  const filtered = messageRows.filter((row) => {
@@ -159846,10 +160246,63 @@ function readRawSessionMessagesFromDb(db, sessionId) {
159846
160246
  ordinal: index + 1,
159847
160247
  id: row.id,
159848
160248
  role,
159849
- parts: partsByMessageId.get(row.id) ?? []
160249
+ parts: partsByMessageId.get(row.id) ?? [],
160250
+ version: row.time_updated ?? null
159850
160251
  };
159851
160252
  });
159852
160253
  }
160254
+ function isAnchorRow(row) {
160255
+ return row !== null && typeof row === "object" && typeof row.time_created === "number" && typeof row.id === "string";
160256
+ }
160257
+ function readRawSessionTailFromDb(db, sessionId, baseOrdinal, anchorMessageId) {
160258
+ const anchorRow = db.prepare("SELECT time_created, id, data FROM message WHERE id = ? AND session_id = ?").get(anchorMessageId, sessionId);
160259
+ if (!isAnchorRow(anchorRow))
160260
+ return null;
160261
+ const anchorInfo = parseJsonRecord(anchorRow.data ?? "");
160262
+ if (anchorInfo?.summary === true && anchorInfo?.finish === "stop")
160263
+ return null;
160264
+ const messageRows = db.prepare(`SELECT id, data, time_updated FROM message
160265
+ WHERE session_id = ?
160266
+ AND (time_created > ? OR (time_created = ? AND id >= ?))
160267
+ ORDER BY time_created ASC, id ASC`).all(sessionId, anchorRow.time_created, anchorRow.time_created, anchorRow.id).filter(isRawMessageRow);
160268
+ const filtered = messageRows.filter((row) => {
160269
+ const info = parseJsonRecord(row.data);
160270
+ return !(info?.summary === true && info?.finish === "stop");
160271
+ });
160272
+ const ids = filtered.map((row) => row.id);
160273
+ const partsByMessageId = new Map;
160274
+ if (ids.length > 0) {
160275
+ const CHUNK = 800;
160276
+ for (let i = 0;i < ids.length; i += CHUNK) {
160277
+ const slice = ids.slice(i, i + CHUNK);
160278
+ const placeholders = slice.map(() => "?").join(",");
160279
+ 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);
160280
+ for (const part of partRows) {
160281
+ const list = partsByMessageId.get(part.message_id) ?? [];
160282
+ list.push(attachRawPartVersion(parseJsonUnknown(part.data), part.time_updated));
160283
+ partsByMessageId.set(part.message_id, list);
160284
+ }
160285
+ }
160286
+ }
160287
+ const messages = [];
160288
+ let ord = baseOrdinal;
160289
+ for (const row of filtered) {
160290
+ const info = parseJsonRecord(row.data);
160291
+ if (!info) {
160292
+ ord += 1;
160293
+ continue;
160294
+ }
160295
+ messages.push({
160296
+ ordinal: ord,
160297
+ id: row.id,
160298
+ role: typeof info.role === "string" ? info.role : "unknown",
160299
+ parts: partsByMessageId.get(row.id) ?? [],
160300
+ version: row.time_updated ?? null
160301
+ });
160302
+ ord += 1;
160303
+ }
160304
+ return { messages, absoluteMessageCount: Math.max(0, ord - 1) };
160305
+ }
159853
160306
 
159854
160307
  // ../plugin/src/hooks/magic-context/tag-content-primitives.ts
159855
160308
  var encoder = new TextEncoder;
@@ -159994,6 +160447,7 @@ class ToolMutationBatch {
159994
160447
 
159995
160448
  // ../plugin/src/hooks/magic-context/read-session-chunk.ts
159996
160449
  var activeRawMessageCache = null;
160450
+ var activeAbsoluteCountCache = null;
159997
160451
  var sessionProviders = new Map;
159998
160452
  function setRawMessageProvider(sessionId, provider2) {
159999
160453
  sessionProviders.set(sessionId, provider2);
@@ -160025,12 +160479,14 @@ function withRawSessionMessageCache(fn) {
160025
160479
  const outerCache = activeRawMessageCache;
160026
160480
  if (!outerCache) {
160027
160481
  activeRawMessageCache = new Map;
160482
+ activeAbsoluteCountCache = new Map;
160028
160483
  }
160029
160484
  try {
160030
160485
  return fn();
160031
160486
  } finally {
160032
160487
  if (!outerCache) {
160033
160488
  activeRawMessageCache = null;
160489
+ activeAbsoluteCountCache = null;
160034
160490
  }
160035
160491
  }
160036
160492
  }
@@ -160046,6 +160502,40 @@ function readRawSessionMessages(sessionId) {
160046
160502
  }
160047
160503
  return readRawSessionMessagesFromSource(sessionId);
160048
160504
  }
160505
+ function primeTailRawMessageCache(args) {
160506
+ const { sessionId, lastCompartmentEnd, anchorMessageId } = args;
160507
+ if (!activeRawMessageCache)
160508
+ return false;
160509
+ if (activeRawMessageCache.has(sessionId))
160510
+ return false;
160511
+ if (sessionProviders.has(sessionId))
160512
+ return false;
160513
+ if (!openCodeDbExists())
160514
+ return false;
160515
+ if (lastCompartmentEnd < 1 || !anchorMessageId)
160516
+ return false;
160517
+ const result = withReadOnlySessionDb((db) => readRawSessionTailFromDb(db, sessionId, lastCompartmentEnd, anchorMessageId));
160518
+ if (!result)
160519
+ return false;
160520
+ activeRawMessageCache.set(sessionId, result.messages);
160521
+ activeAbsoluteCountCache?.set(sessionId, result.absoluteMessageCount);
160522
+ return true;
160523
+ }
160524
+ function getCachedAbsoluteMessageCount(sessionId) {
160525
+ return activeAbsoluteCountCache?.get(sessionId) ?? null;
160526
+ }
160527
+ function primeInMemoryTailRawMessageCache(args) {
160528
+ const { sessionId, messages, absoluteMessageCount } = args;
160529
+ if (!activeRawMessageCache)
160530
+ return false;
160531
+ if (activeRawMessageCache.has(sessionId))
160532
+ return false;
160533
+ if (sessionProviders.has(sessionId))
160534
+ return false;
160535
+ activeRawMessageCache.set(sessionId, messages);
160536
+ activeAbsoluteCountCache?.set(sessionId, absoluteMessageCount);
160537
+ return true;
160538
+ }
160049
160539
  function readRawSessionMessagesFromSource(sessionId) {
160050
160540
  const provider2 = sessionProviders.get(sessionId);
160051
160541
  if (provider2)
@@ -160110,7 +160600,7 @@ function getRawSessionTagKeysThrough(sessionId, upToMessageIndex) {
160110
160600
  return { messageFileKeys, toolObservations };
160111
160601
  }
160112
160602
  var PROTECTED_TAIL_USER_TURNS = 5;
160113
- function getProtectedTailStartOrdinal(sessionId) {
160603
+ function getLegacyProtectedTailStartOrdinal(sessionId) {
160114
160604
  const messages = readRawSessionMessages(sessionId);
160115
160605
  const userOrdinals = messages.filter((m) => m.role === "user" && hasMeaningfulUserText(m.parts)).map((m) => m.ordinal);
160116
160606
  if (userOrdinals.length < PROTECTED_TAIL_USER_TURNS) {
@@ -160120,6 +160610,7 @@ function getProtectedTailStartOrdinal(sessionId) {
160120
160610
  }
160121
160611
  function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal) {
160122
160612
  const messages = readRawSessionMessages(sessionId);
160613
+ const totalMessageCount = getCachedAbsoluteMessageCount(sessionId) ?? messages.length;
160123
160614
  const startOrdinal = Math.max(1, offset);
160124
160615
  const lines = [];
160125
160616
  const lineMeta = [];
@@ -160247,7 +160738,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
160247
160738
  endMessageId: lastMessageId,
160248
160739
  messageCount: messagesProcessed,
160249
160740
  tokenEstimate: totalTokens,
160250
- hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, messages.length) : messages.length),
160741
+ hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
160251
160742
  text: lines.join(`
160252
160743
  `),
160253
160744
  lines: lineMeta,
@@ -160257,7 +160748,13 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
160257
160748
  }
160258
160749
 
160259
160750
  // ../plugin/src/tools/ctx-expand/constants.ts
160260
- 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.";
160751
+ var CTX_EXPAND_DESCRIPTION = `Recover the original conversation from your compacted history.
160752
+
160753
+ 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:
160754
+
160755
+ ctx_expand(start=120, end=245) ← the compartment's own start/end attributes
160756
+
160757
+ 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.`;
160261
160758
  var CTX_EXPAND_TOKEN_BUDGET = 15000;
160262
160759
 
160263
160760
  // ../../node_modules/.bun/typebox@1.1.38/node_modules/typebox/build/system/memory/memory.mjs
@@ -164426,11 +164923,31 @@ function findLastModelKeyFromBranch(entries) {
164426
164923
  }
164427
164924
  return;
164428
164925
  }
164926
+ function rawEntryVersion(entry) {
164927
+ const record4 = entry;
164928
+ const updated = record4.updatedAt ?? record4.updated_at ?? record4.timestamp;
164929
+ return typeof updated === "string" || typeof updated === "number" ? updated : entry.id;
164930
+ }
164931
+ function attachPiPartVersion(parts, version2) {
164932
+ return parts.map((part) => {
164933
+ if (part === null || typeof part !== "object" || Array.isArray(part))
164934
+ return part;
164935
+ try {
164936
+ Object.defineProperty(part, "__magicContextPartUpdatedAt", {
164937
+ value: version2,
164938
+ enumerable: false,
164939
+ configurable: true
164940
+ });
164941
+ } catch {}
164942
+ return part;
164943
+ });
164944
+ }
164429
164945
  function convertEntriesToRawMessages(entries) {
164430
164946
  const result = [];
164431
164947
  let nextOrdinal = 1;
164432
164948
  let pendingToolParts = [];
164433
164949
  let pendingFirstRealId = "";
164950
+ let pendingFirstRealVersion = "";
164434
164951
  for (const entry of entries) {
164435
164952
  if (!isMessageEntry(entry)) {
164436
164953
  continue;
@@ -164438,24 +164955,29 @@ function convertEntriesToRawMessages(entries) {
164438
164955
  const msg = entry.message;
164439
164956
  const role = msg.role;
164440
164957
  if (role === "toolResult") {
164441
- pendingToolParts.push(...synthesizeToolResultParts(msg));
164958
+ const version2 = rawEntryVersion(entry);
164959
+ pendingToolParts.push(...attachPiPartVersion(synthesizeToolResultParts(msg), version2));
164442
164960
  if (pendingFirstRealId === "") {
164443
164961
  pendingFirstRealId = entry.id;
164962
+ pendingFirstRealVersion = version2;
164444
164963
  }
164445
164964
  continue;
164446
164965
  }
164447
164966
  if (role === "user") {
164967
+ const version2 = rawEntryVersion(entry);
164448
164968
  const parts = [
164449
164969
  ...pendingToolParts,
164450
- ...synthesizeUserParts(msg)
164970
+ ...attachPiPartVersion(synthesizeUserParts(msg), version2)
164451
164971
  ];
164452
164972
  pendingToolParts = [];
164453
164973
  pendingFirstRealId = "";
164974
+ pendingFirstRealVersion = "";
164454
164975
  result.push({
164455
164976
  ordinal: nextOrdinal++,
164456
164977
  id: entry.id,
164457
164978
  role: "user",
164458
- parts
164979
+ parts,
164980
+ version: version2
164459
164981
  });
164460
164982
  continue;
164461
164983
  }
@@ -164465,16 +164987,20 @@ function convertEntriesToRawMessages(entries) {
164465
164987
  ordinal: nextOrdinal++,
164466
164988
  id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
164467
164989
  role: "user",
164468
- parts: pendingToolParts
164990
+ parts: pendingToolParts,
164991
+ version: pendingFirstRealVersion
164469
164992
  });
164470
164993
  pendingToolParts = [];
164471
164994
  pendingFirstRealId = "";
164995
+ pendingFirstRealVersion = "";
164472
164996
  }
164997
+ const version2 = rawEntryVersion(entry);
164473
164998
  result.push({
164474
164999
  ordinal: nextOrdinal++,
164475
165000
  id: entry.id,
164476
165001
  role: "assistant",
164477
- parts: synthesizeAssistantParts(msg)
165002
+ parts: attachPiPartVersion(synthesizeAssistantParts(msg), version2),
165003
+ version: version2
164478
165004
  });
164479
165005
  continue;
164480
165006
  }
@@ -164482,7 +165008,8 @@ function convertEntriesToRawMessages(entries) {
164482
165008
  ordinal: nextOrdinal++,
164483
165009
  id: entry.id,
164484
165010
  role: typeof role === "string" ? role : "unknown",
164485
- parts: []
165011
+ parts: [],
165012
+ version: rawEntryVersion(entry)
164486
165013
  });
164487
165014
  }
164488
165015
  if (pendingToolParts.length > 0) {
@@ -164490,7 +165017,8 @@ function convertEntriesToRawMessages(entries) {
164490
165017
  ordinal: nextOrdinal,
164491
165018
  id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
164492
165019
  role: "user",
164493
- parts: pendingToolParts
165020
+ parts: pendingToolParts,
165021
+ version: pendingFirstRealVersion
164494
165022
  });
164495
165023
  }
164496
165024
  return result;
@@ -164647,6 +165175,13 @@ function createCtxExpandTool(deps) {
164647
165175
  }
164648
165176
 
164649
165177
  // ../plugin/src/features/magic-context/memory/constants.ts
165178
+ var V2_MEMORY_CATEGORIES = [
165179
+ "PROJECT_RULES",
165180
+ "ARCHITECTURE",
165181
+ "CONSTRAINTS",
165182
+ "CONFIG_VALUES",
165183
+ "NAMING"
165184
+ ];
164650
165185
  var PROMOTABLE_CATEGORIES = [
164651
165186
  "PROJECT_RULES",
164652
165187
  "ARCHITECTURE",
@@ -164686,6 +165221,7 @@ var CATEGORY_DEFAULT_TTL = {
164686
165221
  KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
164687
165222
  };
164688
165223
  // ../plugin/src/features/magic-context/memory/embedding-backfill.ts
165224
+ init_logger();
164689
165225
  async function ensureMemoryEmbeddings(args) {
164690
165226
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
164691
165227
  if (!snapshot?.enabled) {
@@ -164723,6 +165259,9 @@ async function ensureMemoryEmbeddings(args) {
164723
165259
  }
164724
165260
  return args.existingEmbeddings;
164725
165261
  }
165262
+ // ../plugin/src/features/magic-context/memory/promotion.ts
165263
+ init_logger();
165264
+
164726
165265
  // ../plugin/src/features/magic-context/memory/storage-memory.ts
164727
165266
  var COLUMN_MAP = {
164728
165267
  id: "id",
@@ -165394,7 +165933,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
165394
165933
  }
165395
165934
  // ../plugin/src/features/magic-context/project-docs-hash.ts
165396
165935
  import { createHash as createHash6 } from "node:crypto";
165397
- import { readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
165936
+ import { lstatSync, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
165398
165937
  import path4 from "node:path";
165399
165938
  var PROJECT_DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
165400
165939
  var PROJECT_DOCS_DELIMITER = `
@@ -165402,6 +165941,7 @@ var PROJECT_DOCS_DELIMITER = `
165402
165941
  ---
165403
165942
 
165404
165943
  `;
165944
+ var MAX_PROJECT_DOC_BYTES = 256 * 1024;
165405
165945
  var docsCache = new Map;
165406
165946
  function canonicalizeDocContent(raw) {
165407
165947
  return raw.replace(/^\uFEFF/, "").replace(/\r\n/g, `
@@ -165411,9 +165951,10 @@ function canonicalizeDocContent(raw) {
165411
165951
  }
165412
165952
  function fingerprintFile(filePath) {
165413
165953
  try {
165414
- const stat2 = statSync2(filePath);
165954
+ const stat2 = lstatSync(filePath);
165955
+ const isReadableDoc = stat2.isFile() && stat2.size <= MAX_PROJECT_DOC_BYTES;
165415
165956
  return {
165416
- exists: stat2.isFile(),
165957
+ exists: isReadableDoc,
165417
165958
  mtimeMs: stat2.mtimeMs,
165418
165959
  size: stat2.size
165419
165960
  };
@@ -165456,6 +165997,16 @@ function readCanonicalPieces(projectDirectory, files) {
165456
165997
  if (!fingerprint?.exists) {
165457
165998
  continue;
165458
165999
  }
166000
+ let safeToRead = false;
166001
+ try {
166002
+ const st = lstatSync(filePath);
166003
+ safeToRead = st.isFile() && st.size <= MAX_PROJECT_DOC_BYTES;
166004
+ } catch {
166005
+ safeToRead = false;
166006
+ }
166007
+ if (!safeToRead) {
166008
+ continue;
166009
+ }
165459
166010
  const canonicalContent = canonicalizeDocContent(readFileSync4(filePath, "utf8"));
165460
166011
  hashPieces.push(`file:${filename}
165461
166012
  ${canonicalContent}`);
@@ -165622,6 +166173,7 @@ function getMaxMemoryMutationId(db, projectPath) {
165622
166173
  return row?.max_id ?? null;
165623
166174
  }
165624
166175
  // ../plugin/src/features/magic-context/storage-meta-persisted.ts
166176
+ init_logger();
165625
166177
  var CAS_RETRY_LIMIT = 5;
165626
166178
  var AUTO_SEARCH_NO_HINT_REASONS = new Set([
165627
166179
  "below-threshold",
@@ -165631,12 +166183,6 @@ var AUTO_SEARCH_NO_HINT_REASONS = new Set([
165631
166183
  "stacked",
165632
166184
  "too-short"
165633
166185
  ]);
165634
- function isPersistedStickyTurnReminderRow(row) {
165635
- if (row === null || typeof row !== "object")
165636
- return false;
165637
- const r = row;
165638
- return typeof r.sticky_turn_reminder_text === "string" && typeof r.sticky_turn_reminder_message_id === "string";
165639
- }
165640
166186
  function isPersistedNoteNudgeRow(row) {
165641
166187
  if (row === null || typeof row !== "object")
165642
166188
  return false;
@@ -165702,6 +166248,139 @@ function getDefaultHistorianFailureState() {
165702
166248
  lastFailureAt: null
165703
166249
  };
165704
166250
  }
166251
+ var DEFAULT_PROTECTED_TAIL_META = {
166252
+ priorBoundaryOrdinal: 1,
166253
+ protectedTailPolicyVersion: 0,
166254
+ protectedTailDrainWindowStartedAt: 0,
166255
+ protectedTailDrainTokens: 0,
166256
+ recoveryNoEligibleHeadCount: 0,
166257
+ forceEmergencyBypassWindowStart: 0,
166258
+ forceEmergencyBypassUsed: 0
166259
+ };
166260
+ function toProtectedTailMeta(row) {
166261
+ if (row === null || typeof row !== "object")
166262
+ return { ...DEFAULT_PROTECTED_TAIL_META };
166263
+ const r = row;
166264
+ const numberOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
166265
+ return {
166266
+ priorBoundaryOrdinal: Math.max(1, numberOr(r.prior_boundary_ordinal, 1)),
166267
+ protectedTailPolicyVersion: numberOr(r.protected_tail_policy_version, 0),
166268
+ protectedTailDrainWindowStartedAt: numberOr(r.protected_tail_drain_window_started_at, 0),
166269
+ protectedTailDrainTokens: numberOr(r.protected_tail_drain_tokens, 0),
166270
+ recoveryNoEligibleHeadCount: numberOr(r.recovery_no_eligible_head_count, 0),
166271
+ forceEmergencyBypassWindowStart: numberOr(r.force_emergency_bypass_window_start, 0),
166272
+ forceEmergencyBypassUsed: numberOr(r.force_emergency_bypass_used, 0)
166273
+ };
166274
+ }
166275
+ function loadProtectedTailMeta(db, sessionId) {
166276
+ ensureSessionMetaRow(db, sessionId);
166277
+ const row = db.prepare(`SELECT prior_boundary_ordinal, protected_tail_policy_version,
166278
+ protected_tail_drain_window_started_at, protected_tail_drain_tokens,
166279
+ recovery_no_eligible_head_count, force_emergency_bypass_window_start,
166280
+ force_emergency_bypass_used
166281
+ FROM session_meta WHERE session_id = ?`).get(sessionId);
166282
+ return toProtectedTailMeta(row);
166283
+ }
166284
+ function markProtectedTailPolicyV3Seeded(db, sessionId, priorBoundaryOrdinal) {
166285
+ let seeded = false;
166286
+ db.transaction(() => {
166287
+ ensureSessionMetaRow(db, sessionId);
166288
+ const existing = loadProtectedTailMeta(db, sessionId);
166289
+ if (existing.protectedTailPolicyVersion < 3) {
166290
+ db.prepare(`UPDATE session_meta
166291
+ SET prior_boundary_ordinal = ?, protected_tail_policy_version = 3
166292
+ WHERE session_id = ? AND protected_tail_policy_version < 3`).run(Math.max(1, Math.floor(priorBoundaryOrdinal)), sessionId);
166293
+ seeded = true;
166294
+ }
166295
+ })();
166296
+ return { ...loadProtectedTailMeta(db, sessionId), seeded };
166297
+ }
166298
+ function recordProtectedTailPublicationFloor(db, sessionId, floorOrdinal) {
166299
+ db.transaction(() => {
166300
+ ensureSessionMetaRow(db, sessionId);
166301
+ db.prepare(`UPDATE session_meta
166302
+ SET prior_boundary_ordinal = MAX(COALESCE(prior_boundary_ordinal, 1), ?),
166303
+ recovery_no_eligible_head_count = 0
166304
+ WHERE session_id = ?`).run(Math.max(1, Math.floor(floorOrdinal)), sessionId);
166305
+ })();
166306
+ }
166307
+ function recordProtectedTailNoEligibleHead(db, sessionId) {
166308
+ db.transaction(() => {
166309
+ ensureSessionMetaRow(db, sessionId);
166310
+ db.prepare(`UPDATE session_meta
166311
+ SET recovery_no_eligible_head_count = COALESCE(recovery_no_eligible_head_count, 0) + 1
166312
+ WHERE session_id = ?`).run(sessionId);
166313
+ })();
166314
+ return loadProtectedTailMeta(db, sessionId).recoveryNoEligibleHeadCount;
166315
+ }
166316
+ var DRAIN_WINDOW_MS = 10 * 60 * 1000;
166317
+ function protectedTailWindowBudget(usagePercentage, usable, perRunCap) {
166318
+ if (usagePercentage >= 95)
166319
+ return Math.min(1e6, Math.max(4 * perRunCap, Math.round(0.5 * usable)));
166320
+ if (usagePercentage >= 80)
166321
+ return Math.min(750000, Math.max(3 * perRunCap, Math.round(0.35 * usable)));
166322
+ return Math.min(500000, Math.max(perRunCap, Math.round(0.2 * usable)));
166323
+ }
166324
+ function reserveProtectedTailDrainTokens(args) {
166325
+ const now = args.now ?? Date.now();
166326
+ const requested = Math.max(0, Math.floor(args.trueRawTokens));
166327
+ if (requested === 0) {
166328
+ return { ok: true, reservedTokens: 0, overQuotaBypass: false, reservation: null };
166329
+ }
166330
+ let result = {
166331
+ ok: false,
166332
+ reservedTokens: 0,
166333
+ overQuotaBypass: false,
166334
+ reservation: null,
166335
+ skippedReason: "quota exhausted"
166336
+ };
166337
+ args.db.transaction(() => {
166338
+ ensureSessionMetaRow(args.db, args.sessionId);
166339
+ let meta3 = loadProtectedTailMeta(args.db, args.sessionId);
166340
+ if (now - meta3.protectedTailDrainWindowStartedAt > DRAIN_WINDOW_MS) {
166341
+ args.db.prepare(`UPDATE session_meta
166342
+ SET protected_tail_drain_window_started_at = ?, protected_tail_drain_tokens = 0,
166343
+ force_emergency_bypass_window_start = ?, force_emergency_bypass_used = 0
166344
+ WHERE session_id = ?`).run(now, now, args.sessionId);
166345
+ meta3 = loadProtectedTailMeta(args.db, args.sessionId);
166346
+ }
166347
+ const budget = protectedTailWindowBudget(args.usagePercentage, args.usable, args.perRunCap);
166348
+ const remaining = Math.max(0, budget - meta3.protectedTailDrainTokens);
166349
+ let reserved = Math.min(requested, args.perRunCap, remaining);
166350
+ let bypass = false;
166351
+ const bypassWindowExpired = now - meta3.forceEmergencyBypassWindowStart > DRAIN_WINDOW_MS;
166352
+ const bypassUsed = bypassWindowExpired ? 0 : meta3.forceEmergencyBypassUsed;
166353
+ if (reserved <= 0 && args.usagePercentage >= 95 && bypassUsed === 0) {
166354
+ reserved = Math.min(requested, args.perRunCap);
166355
+ bypass = true;
166356
+ }
166357
+ if (reserved <= 0)
166358
+ return;
166359
+ args.db.prepare(`UPDATE session_meta
166360
+ 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,
166361
+ protected_tail_drain_tokens = COALESCE(protected_tail_drain_tokens, 0) + ?,
166362
+ force_emergency_bypass_window_start = CASE WHEN ? THEN ? ELSE force_emergency_bypass_window_start END,
166363
+ force_emergency_bypass_used = CASE WHEN ? THEN 1 ELSE force_emergency_bypass_used END
166364
+ WHERE session_id = ?`).run(now, reserved, bypass ? 1 : 0, now, bypass ? 1 : 0, args.sessionId);
166365
+ result = {
166366
+ ok: true,
166367
+ reservedTokens: reserved,
166368
+ overQuotaBypass: bypass,
166369
+ reservation: { sessionId: args.sessionId, runId: args.runId, tokens: reserved }
166370
+ };
166371
+ })();
166372
+ return result;
166373
+ }
166374
+ function rollbackProtectedTailDrainReservation(db, reservation) {
166375
+ if (!reservation || reservation.tokens <= 0)
166376
+ return;
166377
+ db.transaction(() => {
166378
+ ensureSessionMetaRow(db, reservation.sessionId);
166379
+ db.prepare(`UPDATE session_meta
166380
+ SET protected_tail_drain_tokens = MAX(0, COALESCE(protected_tail_drain_tokens, 0) - ?)
166381
+ WHERE session_id = ?`).run(reservation.tokens, reservation.sessionId);
166382
+ })();
166383
+ }
165705
166384
  function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
165706
166385
  ensureSessionMetaRow(db, sessionId);
165707
166386
  db.prepare("UPDATE session_meta SET cleared_reasoning_through_tag = ? WHERE session_id = ?").run(tagNumber, sessionId);
@@ -165709,27 +166388,95 @@ function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
165709
166388
  function clearPersistedReasoningWatermark(db, sessionId) {
165710
166389
  setPersistedReasoningWatermark(db, sessionId, 0);
165711
166390
  }
165712
- function getPersistedStickyTurnReminder(db, sessionId) {
165713
- const result = db.prepare("SELECT sticky_turn_reminder_text, sticky_turn_reminder_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
165714
- if (!isPersistedStickyTurnReminderRow(result)) {
165715
- return null;
165716
- }
165717
- if (result.sticky_turn_reminder_text.length === 0) {
165718
- return null;
165719
- }
165720
- return {
165721
- text: result.sticky_turn_reminder_text,
165722
- messageId: result.sticky_turn_reminder_message_id.length > 0 ? result.sticky_turn_reminder_message_id : null
165723
- };
166391
+ function isEmergencyInputSampleRow(row) {
166392
+ return typeof row === "object" && row !== null && typeof row.last_emergency_input_sample === "number";
166393
+ }
166394
+ function getEmergencyInputSample(db, sessionId) {
166395
+ const result = db.prepare("SELECT last_emergency_input_sample FROM session_meta WHERE session_id = ?").get(sessionId);
166396
+ return isEmergencyInputSampleRow(result) ? result.last_emergency_input_sample : 0;
166397
+ }
166398
+ function setEmergencyDropSample(db, sessionId, inputSample) {
166399
+ db.transaction(() => {
166400
+ ensureSessionMetaRow(db, sessionId);
166401
+ db.prepare("UPDATE session_meta SET last_emergency_input_sample = ? WHERE session_id = ?").run(Math.max(0, Math.round(inputSample)), sessionId);
166402
+ })();
165724
166403
  }
165725
- function setPersistedStickyTurnReminder(db, sessionId, text, messageId = "") {
166404
+ function clearEmergencyDropSample(db, sessionId) {
165726
166405
  db.transaction(() => {
165727
166406
  ensureSessionMetaRow(db, sessionId);
165728
- db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = ?, sticky_turn_reminder_message_id = ? WHERE session_id = ?").run(text, messageId, sessionId);
166407
+ db.prepare("UPDATE session_meta SET last_emergency_input_sample = 0 WHERE session_id = ?").run(sessionId);
165729
166408
  })();
165730
166409
  }
165731
- function clearPersistedStickyTurnReminder(db, sessionId) {
165732
- db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = '', sticky_turn_reminder_message_id = '' WHERE session_id = ?").run(sessionId);
166410
+ function isLastNudgeUndroppedRow(row) {
166411
+ return typeof row === "object" && row !== null && typeof row.last_nudge_undropped === "number";
166412
+ }
166413
+ function isLastNudgeLevelRow(row) {
166414
+ return typeof row === "object" && row !== null && typeof row.last_nudge_level === "string";
166415
+ }
166416
+ function normalizeLastNudgeLevel(value) {
166417
+ return value === "gentle" || value === "firm" || value === "urgent" ? value : "";
166418
+ }
166419
+ function getLastNudgeUndropped(db, sessionId) {
166420
+ const result = db.prepare("SELECT last_nudge_undropped FROM session_meta WHERE session_id = ?").get(sessionId);
166421
+ return isLastNudgeUndroppedRow(result) ? result.last_nudge_undropped : 0;
166422
+ }
166423
+ function setLastNudgeUndropped(db, sessionId, value) {
166424
+ db.transaction(() => {
166425
+ ensureSessionMetaRow(db, sessionId);
166426
+ db.prepare("UPDATE session_meta SET last_nudge_undropped = ? WHERE session_id = ?").run(Math.max(0, Math.round(value)), sessionId);
166427
+ })();
166428
+ }
166429
+ function getLastNudgeLevel(db, sessionId) {
166430
+ const result = db.prepare("SELECT last_nudge_level FROM session_meta WHERE session_id = ?").get(sessionId);
166431
+ return isLastNudgeLevelRow(result) ? normalizeLastNudgeLevel(result.last_nudge_level) : "";
166432
+ }
166433
+ function setLastNudgeLevel(db, sessionId, value) {
166434
+ db.transaction(() => {
166435
+ ensureSessionMetaRow(db, sessionId);
166436
+ db.prepare("UPDATE session_meta SET last_nudge_level = ? WHERE session_id = ?").run(normalizeLastNudgeLevel(value), sessionId);
166437
+ })();
166438
+ }
166439
+ function resetLastNudgeCycle(db, sessionId) {
166440
+ db.transaction(() => {
166441
+ ensureSessionMetaRow(db, sessionId);
166442
+ db.prepare("UPDATE session_meta SET last_nudge_undropped = 0, last_nudge_level = '' WHERE session_id = ?").run(sessionId);
166443
+ })();
166444
+ }
166445
+ function resetLastNudgeCycleIfTailShrank(db, sessionId, measuredUndropped) {
166446
+ let changed = false;
166447
+ db.transaction(() => {
166448
+ ensureSessionMetaRow(db, sessionId);
166449
+ 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)));
166450
+ changed = (result.changes ?? 0) > 0;
166451
+ })();
166452
+ return changed;
166453
+ }
166454
+ function isChannel2StateRow(row) {
166455
+ return typeof row === "object" && row !== null && typeof row.channel2_nudge_state === "string";
166456
+ }
166457
+ function getChannel2NudgeState(db, sessionId) {
166458
+ const result = db.prepare("SELECT channel2_nudge_state FROM session_meta WHERE session_id = ?").get(sessionId);
166459
+ if (!isChannel2StateRow(result))
166460
+ return "";
166461
+ const raw = result.channel2_nudge_state;
166462
+ return raw === "pending" || raw === "claimed" || raw === "delivered" ? raw : "";
166463
+ }
166464
+ function setChannel2NudgeState(db, sessionId, state) {
166465
+ db.transaction(() => {
166466
+ ensureSessionMetaRow(db, sessionId);
166467
+ const claimedAt = state === "claimed" ? Date.now() : 0;
166468
+ db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ?").run(state, claimedAt, sessionId);
166469
+ })();
166470
+ }
166471
+ function casChannel2NudgeState(db, sessionId, from, to) {
166472
+ let changed = false;
166473
+ db.transaction(() => {
166474
+ ensureSessionMetaRow(db, sessionId);
166475
+ const claimedAt = to === "claimed" ? Date.now() : 0;
166476
+ 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);
166477
+ changed = (result.changes ?? 0) > 0;
166478
+ })();
166479
+ return changed;
165733
166480
  }
165734
166481
  function getPersistedNoteNudge(db, sessionId) {
165735
166482
  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);
@@ -165776,7 +166523,8 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
165776
166523
  }
165777
166524
  for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
165778
166525
  const row = db.prepare(`SELECT ${column} FROM session_meta WHERE session_id = ?`).get(sessionId);
165779
- const currentBlob = row?.[column] ?? "[]";
166526
+ const rawCurrent = row?.[column] ?? null;
166527
+ const currentBlob = rawCurrent ?? "[]";
165780
166528
  const current = parseJsonArray(currentBlob, validator);
165781
166529
  const next = mutate(current);
165782
166530
  if (next === null)
@@ -165784,7 +166532,7 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
165784
166532
  const nextBlob = stableStringify(next);
165785
166533
  if (nextBlob === currentBlob)
165786
166534
  return true;
165787
- const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} = ?`).run(nextBlob, sessionId, currentBlob);
166535
+ const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} IS ?`).run(nextBlob, sessionId, rawCurrent);
165788
166536
  if (result.changes > 0)
165789
166537
  return true;
165790
166538
  }
@@ -165913,14 +166661,16 @@ function getHistorianFailureState(db, sessionId) {
165913
166661
  };
165914
166662
  }
165915
166663
  function incrementHistorianFailure(db, sessionId, error51) {
166664
+ let nextCount = 1;
165916
166665
  db.transaction(() => {
165917
166666
  ensureSessionMetaRow(db, sessionId);
165918
166667
  const current = getHistorianFailureState(db, sessionId);
165919
- const nextCount = current.failureCount + 1;
166668
+ nextCount = current.failureCount + 1;
165920
166669
  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);
165921
166670
  const reason = error51.replace(/\s+/g, " ").trim().slice(0, 300);
165922
166671
  sessionLog(sessionId, `historian failure recorded: count=${nextCount} reason="${reason}"`);
165923
166672
  })();
166673
+ return nextCount;
165924
166674
  }
165925
166675
  function clearHistorianFailureState(db, sessionId) {
165926
166676
  db.transaction(() => {
@@ -165950,7 +166700,11 @@ function recordOverflowDetected(db, sessionId, reportedLimit) {
165950
166700
  function clearEmergencyRecovery(db, sessionId) {
165951
166701
  db.transaction(() => {
165952
166702
  ensureSessionMetaRow(db, sessionId);
165953
- db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0 WHERE session_id = ?").run(sessionId);
166703
+ try {
166704
+ db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0, recovery_no_eligible_head_count = 0 WHERE session_id = ?").run(sessionId);
166705
+ } catch {
166706
+ db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0 WHERE session_id = ?").run(sessionId);
166707
+ }
165954
166708
  })();
165955
166709
  }
165956
166710
  function clearDetectedContextLimit(db, sessionId) {
@@ -165989,10 +166743,39 @@ function getStrippedPlaceholderIds(db, sessionId) {
165989
166743
  } catch {}
165990
166744
  return new Set;
165991
166745
  }
165992
- function setStrippedPlaceholderIds(db, sessionId, ids) {
166746
+ function applyStrippedPlaceholderDelta(db, sessionId, delta) {
166747
+ const add = delta.add ? [...delta.add] : [];
166748
+ const remove = delta.remove ? [...delta.remove] : [];
166749
+ if (add.length === 0 && remove.length === 0)
166750
+ return true;
165993
166751
  ensureSessionMetaRow(db, sessionId);
165994
- const json2 = ids.size > 0 ? JSON.stringify([...ids]) : "";
165995
- db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ?").run(json2, sessionId);
166752
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
166753
+ const row = db.prepare("SELECT stripped_placeholder_ids FROM session_meta WHERE session_id = ?").get(sessionId);
166754
+ const rawStored = row ? row.stripped_placeholder_ids ?? null : null;
166755
+ const current = new Set(parseStrippedBlob(rawStored));
166756
+ for (const id of add)
166757
+ current.add(id);
166758
+ for (const id of remove)
166759
+ current.delete(id);
166760
+ const nextBlob = current.size > 0 ? JSON.stringify([...current]) : "";
166761
+ if (nextBlob === (rawStored ?? ""))
166762
+ return true;
166763
+ const result = db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ? AND stripped_placeholder_ids IS ?").run(nextBlob, sessionId, rawStored);
166764
+ if (result.changes > 0)
166765
+ return true;
166766
+ }
166767
+ sessionLog(sessionId, `stripped_placeholder_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
166768
+ return false;
166769
+ }
166770
+ function parseStrippedBlob(raw) {
166771
+ if (!raw || raw.length === 0)
166772
+ return [];
166773
+ try {
166774
+ const parsed = JSON.parse(raw);
166775
+ if (Array.isArray(parsed))
166776
+ return parsed.filter((v) => typeof v === "string");
166777
+ } catch {}
166778
+ return [];
165996
166779
  }
165997
166780
  function isPendingCompactionMarker(value) {
165998
166781
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
@@ -166084,6 +166867,7 @@ function setSessionWorkMetrics(db, sessionId, newWorkTokens, totalInputTokens) {
166084
166867
  import { Buffer as Buffer3 } from "node:buffer";
166085
166868
 
166086
166869
  // ../plugin/src/features/magic-context/resolve-subagent-fallback.ts
166870
+ init_logger();
166087
166871
  function resolveIsSubagentFromOpenCodeDb(sessionId) {
166088
166872
  try {
166089
166873
  return withReadOnlySessionDb((openCodeDb) => {
@@ -166302,7 +167086,8 @@ function updateNote(db, noteId, updates, scope) {
166302
167086
  sets.push("session_id = ?");
166303
167087
  params.push(updates.sessionId);
166304
167088
  }
166305
- if (updates.status !== undefined) {
167089
+ const smartConditionChanged = existing.type === "smart" && updates.surfaceCondition !== undefined && updates.surfaceCondition !== existing.surfaceCondition;
167090
+ if (updates.status !== undefined && !smartConditionChanged) {
166306
167091
  sets.push("status = ?");
166307
167092
  params.push(updates.status);
166308
167093
  }
@@ -166315,17 +167100,21 @@ function updateNote(db, noteId, updates, scope) {
166315
167100
  sets.push("surface_condition = ?");
166316
167101
  params.push(updates.surfaceCondition);
166317
167102
  }
166318
- if (updates.lastCheckedAt !== undefined) {
166319
- sets.push("last_checked_at = ?");
166320
- params.push(updates.lastCheckedAt);
166321
- }
166322
- if (updates.readyAt !== undefined) {
166323
- sets.push("ready_at = ?");
166324
- params.push(updates.readyAt);
166325
- }
166326
- if (updates.readyReason !== undefined) {
166327
- sets.push("ready_reason = ?");
166328
- params.push(updates.readyReason);
167103
+ if (smartConditionChanged) {
167104
+ sets.push("status = 'pending'", "last_checked_at = NULL", "ready_at = NULL", "ready_reason = NULL");
167105
+ } else {
167106
+ if (updates.lastCheckedAt !== undefined) {
167107
+ sets.push("last_checked_at = ?");
167108
+ params.push(updates.lastCheckedAt);
167109
+ }
167110
+ if (updates.readyAt !== undefined) {
167111
+ sets.push("ready_at = ?");
167112
+ params.push(updates.readyAt);
167113
+ }
167114
+ if (updates.readyReason !== undefined) {
167115
+ sets.push("ready_reason = ?");
167116
+ params.push(updates.readyReason);
167117
+ }
166329
167118
  }
166330
167119
  }
166331
167120
  if (sets.length === 1) {
@@ -166352,6 +167141,7 @@ function markNoteChecked(db, noteId) {
166352
167141
  db.prepare("UPDATE notes SET last_checked_at = ?, updated_at = ? WHERE id = ? AND type = 'smart'").run(now, now, noteId);
166353
167142
  }
166354
167143
  // ../plugin/src/features/magic-context/storage-ops.ts
167144
+ init_logger();
166355
167145
  var queuePendingOpStatements = new WeakMap;
166356
167146
  var getPendingOpsStatements = new WeakMap;
166357
167147
  var clearPendingOpsStatements = new WeakMap;
@@ -166514,7 +167304,7 @@ var getTagNumberByMessageIdStatements = new WeakMap;
166514
167304
  function getInsertTagStatement(db) {
166515
167305
  let stmt = insertTagStatements.get(db);
166516
167306
  if (!stmt) {
166517
- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
167307
+ 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
166518
167308
  insertTagStatements.set(db, stmt);
166519
167309
  }
166520
167310
  return stmt;
@@ -166556,9 +167346,95 @@ function getUpdateTagInputByteSizeStatement(db) {
166556
167346
  function updateTagByteSize(db, sessionId, tagNumber, newByteSize) {
166557
167347
  getUpdateTagByteSizeStatement(db).run(newByteSize, sessionId, tagNumber);
166558
167348
  }
167349
+ var CONTENT_ID_SUFFIX = /:(?:p|file)\d+$/;
167350
+ function ownerMessageIdForTagRow(row) {
167351
+ if (row.type === "tool") {
167352
+ return row.tool_owner_message_id ?? row.message_id;
167353
+ }
167354
+ return row.message_id.replace(CONTENT_ID_SUFFIX, "");
167355
+ }
167356
+ function getActiveTagTokenAggregate(db, sessionId) {
167357
+ const row = db.prepare(`SELECT
167358
+ COALESCE(SUM(CASE WHEN type != 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)
167359
+ + COALESCE(SUM(COALESCE(reasoning_token_count, 0)), 0) AS conversation,
167360
+ COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) + COALESCE(input_token_count, 0) ELSE 0 END), 0) AS tool_call,
167361
+ COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0) AS tool_output,
167362
+ COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
167363
+ FROM tags
167364
+ WHERE session_id = ? AND status = 'active'`).get(sessionId);
167365
+ return {
167366
+ conversation: row?.conversation ?? 0,
167367
+ toolCall: row?.tool_call ?? 0,
167368
+ toolOutput: row?.tool_output ?? 0,
167369
+ nullCount: row?.null_count ?? 0
167370
+ };
167371
+ }
167372
+ function getTriggerTagTokenUpperBound(db, sessionId) {
167373
+ const row = db.prepare(`SELECT
167374
+ COALESCE(SUM(COALESCE(token_count, 0) + COALESCE(input_token_count, 0) + COALESCE(reasoning_token_count, 0)), 0) AS bound,
167375
+ COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
167376
+ FROM tags
167377
+ WHERE session_id = ? AND status IN ('active', 'dropped')`).get(sessionId);
167378
+ return { bound: row?.bound ?? 0, nullCount: row?.null_count ?? 0 };
167379
+ }
166559
167380
  function updateTagInputByteSize(db, sessionId, tagNumber, newInputByteSize) {
166560
167381
  getUpdateTagInputByteSizeStatement(db).run(newInputByteSize, sessionId, tagNumber);
166561
167382
  }
167383
+ var updateTagTokenCountStatements = new WeakMap;
167384
+ var updateTagInputTokenCountStatements = new WeakMap;
167385
+ function getUpdateTagTokenCountStatement(db) {
167386
+ let stmt = updateTagTokenCountStatements.get(db);
167387
+ if (!stmt) {
167388
+ stmt = db.prepare("UPDATE tags SET token_count = ? WHERE session_id = ? AND tag_number = ?");
167389
+ updateTagTokenCountStatements.set(db, stmt);
167390
+ }
167391
+ return stmt;
167392
+ }
167393
+ function getUpdateTagInputTokenCountStatement(db) {
167394
+ let stmt = updateTagInputTokenCountStatements.get(db);
167395
+ if (!stmt) {
167396
+ stmt = db.prepare("UPDATE tags SET input_token_count = ? WHERE session_id = ? AND tag_number = ?");
167397
+ updateTagInputTokenCountStatements.set(db, stmt);
167398
+ }
167399
+ return stmt;
167400
+ }
167401
+ function updateTagTokenCount(db, sessionId, tagNumber, newTokenCount) {
167402
+ getUpdateTagTokenCountStatement(db).run(newTokenCount, sessionId, tagNumber);
167403
+ }
167404
+ function getAllStatusTagTokenTotalsFlat(db, sessionId) {
167405
+ const rows = db.prepare(`SELECT type, message_id, tool_owner_message_id, token_count, input_token_count, reasoning_token_count
167406
+ FROM tags
167407
+ WHERE session_id = ?`).all(sessionId);
167408
+ const totals = new Map;
167409
+ const nullMessageIds = new Set;
167410
+ for (const row of rows) {
167411
+ if (row.type === "tool" && row.tool_owner_message_id === null)
167412
+ continue;
167413
+ const owner = ownerMessageIdForTagRow(row);
167414
+ if (row.token_count === null) {
167415
+ nullMessageIds.add(owner);
167416
+ totals.delete(owner);
167417
+ continue;
167418
+ }
167419
+ if (nullMessageIds.has(owner))
167420
+ continue;
167421
+ const weight = (row.token_count ?? 0) + (row.input_token_count ?? 0) + (row.reasoning_token_count ?? 0);
167422
+ totals.set(owner, (totals.get(owner) ?? 0) + weight);
167423
+ }
167424
+ return { totals, nullMessageIds };
167425
+ }
167426
+ function updateTagInputTokenCount(db, sessionId, tagNumber, newInputTokenCount) {
167427
+ getUpdateTagInputTokenCountStatement(db).run(newInputTokenCount, sessionId, tagNumber);
167428
+ }
167429
+ function tagTokenCountIsNull(db, sessionId, tagNumber) {
167430
+ const row = db.prepare("SELECT token_count FROM tags WHERE session_id = ? AND tag_number = ?").get(sessionId, tagNumber);
167431
+ return row !== undefined && row.token_count === null;
167432
+ }
167433
+ function backfillTagTokenCounts(db, sessionId, tagNumber, counts) {
167434
+ db.prepare(`UPDATE tags
167435
+ SET token_count = ?, input_token_count = ?, reasoning_token_count = ?
167436
+ WHERE session_id = ? AND tag_number = ? AND token_count IS NULL`).run(counts.tokenCount ?? null, counts.inputTokenCount ?? null, counts.reasoningTokenCount ?? null, sessionId, tagNumber);
167437
+ }
166562
167438
  function getMaxTagNumberBySessionStatement(db) {
166563
167439
  let stmt = getMaxTagNumberBySessionStatements.get(db);
166564
167440
  if (!stmt) {
@@ -166611,8 +167487,8 @@ function isMaxTagNumberRow(row) {
166611
167487
  const r = row;
166612
167488
  return typeof r.max_tag_number === "number";
166613
167489
  }
166614
- function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber, reasoningByteSize = 0, toolName = null, inputByteSize = 0, toolOwnerMessageId = null, entryFingerprint = null) {
166615
- getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, reasoningByteSize, tagNumber, toolName, inputByteSize, getHarness(), toolOwnerMessageId, entryFingerprint);
167490
+ function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber, reasoningByteSize = 0, toolName = null, inputByteSize = 0, toolOwnerMessageId = null, entryFingerprint = null, tokenCounts = null) {
167491
+ getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, reasoningByteSize, tagNumber, toolName, inputByteSize, getHarness(), toolOwnerMessageId, entryFingerprint, tokenCounts?.tokenCount ?? null, tokenCounts?.inputTokenCount ?? null, tokenCounts?.reasoningTokenCount ?? null);
166616
167492
  return tagNumber;
166617
167493
  }
166618
167494
  function updateTagStatus(db, sessionId, tagId, status) {
@@ -166680,13 +167556,6 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
166680
167556
  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);
166681
167557
  return rows.map(toTagEntry);
166682
167558
  }
166683
- function getTopNBySize(db, sessionId, n) {
166684
- if (n <= 0) {
166685
- return [];
166686
- }
166687
- 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);
166688
- return rows.map(toTagEntry);
166689
- }
166690
167559
  var getToolTagNumberByOwnerStatements = new WeakMap;
166691
167560
  var getNullOwnerToolTagStatements = new WeakMap;
166692
167561
  var adoptNullOwnerToolTagStatements = new WeakMap;
@@ -166752,44 +167621,43 @@ var ERROR_CLASSES = new Set([
166752
167621
  "permission_denied",
166753
167622
  "unknown"
166754
167623
  ]);
167624
+ // src/tools/ctx-memory.ts
167625
+ init_logger();
167626
+
167627
+ // ../plugin/src/tools/ctx-memory/constants.ts
167628
+ var CTX_MEMORY_DESCRIPTION = `Durable project knowledge shared across every session on this project.
167629
+
167630
+ 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.
167631
+
167632
+ Actions:
167633
+ - write: save a new memory (content + category).
167634
+ - update: rewrite one memory whose fact changed (ids: [one], content).
167635
+ - archive: retire wrong or obsolete memories (ids: [one or more], optional reason).
167636
+ - merge: collapse duplicates into one memory (ids: [two or more], content).
167637
+
167638
+ Example: ctx_memory(action="write", category="CONSTRAINTS", content="Pi stores sessions as JSONL under ~/.pi/agent/sessions/, not SQLite")`;
167639
+
166755
167640
  // src/tools/ctx-memory.ts
166756
167641
  var DEFAULT_LIST_LIMIT = 10;
166757
- var VALID_CATEGORIES = new Set(CATEGORY_PRIORITY);
166758
- function isMemoryCategory2(value) {
166759
- return VALID_CATEGORIES.has(value);
166760
- }
166761
- var ALL_ACTIONS = [
166762
- "write",
166763
- "delete",
166764
- "list",
166765
- "update",
166766
- "merge",
166767
- "archive"
166768
- ];
166769
- var DREAMER_ONLY_ACTIONS = new Set([
166770
- "list",
166771
- "update",
166772
- "merge",
166773
- "archive"
166774
- ]);
167642
+ var ALL_ACTIONS = ["write", "archive", "update", "merge", "list"];
167643
+ var DREAMER_ONLY_ACTIONS = new Set(["list"]);
166775
167644
  var ParamsSchema2 = exports_typebox.Object({
166776
- action: exports_typebox.Union(ALL_ACTIONS.map((a) => exports_typebox.Literal(a)), { description: "Action to perform on memories" }),
167645
+ action: exports_typebox.Union(ALL_ACTIONS.map((a) => exports_typebox.Literal(a)), { description: "What to do: write, update, archive, or merge" }),
166777
167646
  content: exports_typebox.Optional(exports_typebox.String({
166778
- description: "Memory content (required for write, update, merge)"
166779
- })),
166780
- category: exports_typebox.Optional(exports_typebox.String({
166781
- description: "Memory category (required for write, optional filter for list, optional override for merge). One of: " + CATEGORY_PRIORITY.join(", ")
167647
+ description: "The memory text — one standalone fact (required for write, update, merge)"
166782
167648
  })),
166783
- id: exports_typebox.Optional(exports_typebox.Number({
166784
- description: "Memory ID (required for delete, update, archive)"
167649
+ category: exports_typebox.Optional(exports_typebox.Union(V2_MEMORY_CATEGORIES.map((c) => exports_typebox.Literal(c)), {
167650
+ description: "What kind of fact this is (required for write; optional merge override)"
166785
167651
  })),
166786
167652
  ids: exports_typebox.Optional(exports_typebox.Array(exports_typebox.Number(), {
166787
- description: "Memory IDs to merge (required for merge)"
167653
+ description: "Target memory id(s) from <project-memory>: update takes exactly one, archive one or more, merge two or more"
166788
167654
  })),
166789
167655
  limit: exports_typebox.Optional(exports_typebox.Number({
166790
- description: "Maximum results to return for list (default: 10)"
167656
+ description: "Max results for list (default: 10)"
166791
167657
  })),
166792
- reason: exports_typebox.Optional(exports_typebox.String({ description: "Archive reason (optional for archive)" }))
167658
+ reason: exports_typebox.Optional(exports_typebox.String({
167659
+ description: "Why the memory is being archived (optional, recommended)"
167660
+ }))
166793
167661
  });
166794
167662
  function ok2(text) {
166795
167663
  return { content: [{ type: "text", text }], details: undefined };
@@ -166836,6 +167704,12 @@ function formatMemoryList(memories) {
166836
167704
  ].join(`
166837
167705
  `);
166838
167706
  }
167707
+ function isPrimaryMutableMemory(memory2) {
167708
+ return (memory2.status === "active" || memory2.status === "permanent") && memory2.supersededByMemoryId === null;
167709
+ }
167710
+ function inactiveMemoryError(id, action2) {
167711
+ return `Error: Memory with ID ${id} is archived or superseded; restore it before ${action2}.`;
167712
+ }
166839
167713
  function queueEmbedding(args) {
166840
167714
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
166841
167715
  if (!snapshot?.enabled)
@@ -166856,7 +167730,8 @@ function queueEmbedding(args) {
166856
167730
  }
166857
167731
  function createCtxMemoryTool(deps) {
166858
167732
  const dreamerAllowed = deps.allowDreamerActions === true;
166859
- 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.";
167733
+ const description = dreamerAllowed ? `${CTX_MEMORY_DESCRIPTION}
167734
+ - list: enumerate stored memories (maintenance sessions).` : CTX_MEMORY_DESCRIPTION;
166860
167735
  return {
166861
167736
  name: "ctx_memory",
166862
167737
  label: "Magic Context: Memory",
@@ -166877,13 +167752,10 @@ function createCtxMemoryTool(deps) {
166877
167752
  const content = params.content?.trim();
166878
167753
  if (!content)
166879
167754
  return err2("Error: 'content' is required when action is 'write'.");
166880
- const rawCategory = params.category?.trim();
167755
+ const rawCategory = params.category;
166881
167756
  if (!rawCategory) {
166882
167757
  return err2("Error: 'category' is required when action is 'write'.");
166883
167758
  }
166884
- if (!isMemoryCategory2(rawCategory)) {
166885
- return err2(`Error: Unknown memory category '${rawCategory}'. Valid: ${CATEGORY_PRIORITY.join(", ")}`);
166886
- }
166887
167759
  const existing = getMemoryByHash(deps.db, projectIdentity, rawCategory, computeNormalizedHash(content));
166888
167760
  if (existing) {
166889
167761
  updateMemorySeenCount(deps.db, existing.id);
@@ -166897,45 +167769,31 @@ function createCtxMemoryTool(deps) {
166897
167769
  sourceType: dreamerAllowed ? "dreamer" : "agent"
166898
167770
  });
166899
167771
  queueEmbedding({ deps, projectIdentity, memoryId: memory2.id, content });
166900
- invalidateAllMemoryBlockCaches(deps.db);
166901
167772
  return ok2(`Saved memory [ID: ${memory2.id}] in ${rawCategory}.`);
166902
167773
  }
166903
- if (params.action === "delete") {
166904
- if (typeof params.id !== "number" || !Number.isInteger(params.id)) {
166905
- return err2("Error: 'id' is required when action is 'delete'.");
166906
- }
166907
- const memory2 = getMemoryById(deps.db, params.id);
166908
- if (!memory2 || memory2.projectPath !== projectIdentity) {
166909
- return err2(`Error: Memory with ID ${params.id} was not found.`);
166910
- }
166911
- deps.db.transaction(() => {
166912
- archiveMemory(deps.db, params.id);
166913
- queueMemoryMutation(deps.db, {
166914
- projectPath: projectIdentity,
166915
- mutationType: "delete",
166916
- targetMemoryId: params.id
166917
- });
166918
- })();
166919
- return ok2(`Archived memory [ID: ${params.id}].`);
166920
- }
166921
167774
  if (params.action === "list") {
166922
167775
  const limit = normalizeLimit(params.limit);
166923
167776
  const filtered = getMemoriesByProject(deps.db, projectIdentity);
166924
- const category = params.category?.trim();
167777
+ const category = params.category;
166925
167778
  const filtered2 = category ? filtered.filter((m) => m.category === category) : filtered;
166926
167779
  return ok2(formatMemoryList(filtered2.slice(0, limit)));
166927
167780
  }
166928
167781
  if (params.action === "update") {
166929
- if (typeof params.id !== "number" || !Number.isInteger(params.id)) {
166930
- return err2("Error: 'id' is required when action is 'update'.");
167782
+ const updateIds = params.ids;
167783
+ if (!updateIds || updateIds.length !== 1 || !updateIds.every(Number.isInteger)) {
167784
+ return err2("Error: 'ids' must contain exactly one integer memory ID when action is 'update'.");
166931
167785
  }
167786
+ const updateId = updateIds[0];
166932
167787
  const content = params.content?.trim();
166933
167788
  if (!content) {
166934
167789
  return err2("Error: 'content' is required when action is 'update'.");
166935
167790
  }
166936
- const memory2 = getMemoryById(deps.db, params.id);
166937
- if (!memory2 || memory2.projectPath !== projectIdentity) {
166938
- return err2(`Error: Memory with ID ${params.id} was not found.`);
167791
+ const memory2 = getMemoryById(deps.db, updateId);
167792
+ if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
167793
+ return err2(`Error: Memory with ID ${updateId} was not found.`);
167794
+ }
167795
+ if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
167796
+ return err2(inactiveMemoryError(updateId, "updating"));
166939
167797
  }
166940
167798
  const normalizedHash = computeNormalizedHash(content);
166941
167799
  const duplicate = getMemoryByHash(deps.db, projectIdentity, memory2.category, normalizedHash);
@@ -166956,9 +167814,12 @@ function createCtxMemoryTool(deps) {
166956
167814
  return ok2(`Updated memory [ID: ${memory2.id}] in ${memory2.category}.`);
166957
167815
  }
166958
167816
  if (params.action === "merge") {
166959
- const ids = params.ids?.filter((id) => Number.isInteger(id));
166960
- if (!ids || ids.length < 2) {
166961
- return err2("Error: 'ids' must include at least two memory IDs when action is 'merge'.");
167817
+ const ids = params.ids;
167818
+ if (!ids || ids.length < 2 || !ids.every(Number.isInteger)) {
167819
+ return err2("Error: 'ids' must include at least two integer memory IDs when action is 'merge'.");
167820
+ }
167821
+ if (new Set(ids).size !== ids.length) {
167822
+ return err2("Error: 'ids' must include at least two distinct memory IDs when action is 'merge'.");
166962
167823
  }
166963
167824
  const content = params.content?.trim();
166964
167825
  if (!content) {
@@ -166968,14 +167829,17 @@ function createCtxMemoryTool(deps) {
166968
167829
  if (sourceMemories.length !== ids.length) {
166969
167830
  return err2("Error: One or more source memories were not found.");
166970
167831
  }
166971
- if (sourceMemories.some((memory2) => memory2.projectPath !== projectIdentity)) {
166972
- return err2("Error: All memories to merge must belong to the current project.");
166973
- }
166974
- const requestedCategory = params.category?.trim();
166975
- if (requestedCategory && !isMemoryCategory2(requestedCategory)) {
166976
- return err2(`Error: Unknown memory category '${requestedCategory}'. Valid: ${CATEGORY_PRIORITY.join(", ")}`);
167832
+ if (!dreamerAllowed) {
167833
+ const foreign = sourceMemories.find((memory2) => !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity));
167834
+ if (foreign) {
167835
+ return err2(`Error: Memory with ID ${foreign.id} was not found.`);
167836
+ }
167837
+ const inactive = sourceMemories.find((memory2) => !isPrimaryMutableMemory(memory2));
167838
+ if (inactive) {
167839
+ return err2(inactiveMemoryError(inactive.id, "merging"));
167840
+ }
166977
167841
  }
166978
- const requestedCategoryTyped = requestedCategory && isMemoryCategory2(requestedCategory) ? requestedCategory : undefined;
167842
+ const requestedCategoryTyped = params.category;
166979
167843
  const fallbackCategory = sourceMemories[0]?.category;
166980
167844
  const category = requestedCategoryTyped ?? fallbackCategory;
166981
167845
  if (!category) {
@@ -167029,7 +167893,7 @@ function createCtxMemoryTool(deps) {
167029
167893
  }
167030
167894
  supersededMemory(deps.db, memory2.id, canonicalMemory.id);
167031
167895
  queueMemoryMutation(deps.db, {
167032
- projectPath: memory2.projectPath,
167896
+ projectPath: normalizeStoredProjectPath(memory2.projectPath),
167033
167897
  mutationType: "superseded",
167034
167898
  targetMemoryId: memory2.id,
167035
167899
  supersededById: canonicalMemory.id
@@ -167037,7 +167901,7 @@ function createCtxMemoryTool(deps) {
167037
167901
  }
167038
167902
  if (canonicalExisting && canonicalContentChanged) {
167039
167903
  queueMemoryMutation(deps.db, {
167040
- projectPath: canonicalMemory.projectPath,
167904
+ projectPath: normalizeStoredProjectPath(canonicalMemory.projectPath),
167041
167905
  mutationType: "update",
167042
167906
  targetMemoryId: canonicalMemory.id,
167043
167907
  category,
@@ -167055,29 +167919,57 @@ function createCtxMemoryTool(deps) {
167055
167919
  return ok2(`Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`);
167056
167920
  }
167057
167921
  if (params.action === "archive") {
167058
- if (typeof params.id !== "number" || !Number.isInteger(params.id)) {
167059
- return err2("Error: 'id' is required when action is 'archive'.");
167922
+ const archiveIds = params.ids;
167923
+ if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
167924
+ return err2("Error: 'ids' must contain at least one integer memory ID when action is 'archive'.");
167060
167925
  }
167061
- const memory2 = getMemoryById(deps.db, params.id);
167062
- if (!memory2 || memory2.projectPath !== projectIdentity) {
167063
- return err2(`Error: Memory with ID ${params.id} was not found.`);
167926
+ for (const memoryId of archiveIds) {
167927
+ const memory2 = getMemoryById(deps.db, memoryId);
167928
+ if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
167929
+ return err2(`Error: Memory with ID ${memoryId} was not found.`);
167930
+ }
167931
+ if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
167932
+ return err2(inactiveMemoryError(memoryId, "archiving"));
167933
+ }
167064
167934
  }
167065
167935
  deps.db.transaction(() => {
167066
- archiveMemory(deps.db, params.id, params.reason);
167067
- queueMemoryMutation(deps.db, {
167068
- projectPath: projectIdentity,
167069
- mutationType: "archive",
167070
- targetMemoryId: params.id
167071
- });
167936
+ for (const memoryId of archiveIds) {
167937
+ archiveMemory(deps.db, memoryId, params.reason);
167938
+ queueMemoryMutation(deps.db, {
167939
+ projectPath: projectIdentity,
167940
+ mutationType: "archive",
167941
+ targetMemoryId: memoryId
167942
+ });
167943
+ }
167072
167944
  })();
167073
167945
  const reasonSuffix = params.reason ? ` (${params.reason})` : "";
167074
- return ok2(`Archived memory [ID: ${params.id}]${reasonSuffix}.`);
167946
+ const idList = archiveIds.join(", ");
167947
+ const plural = archiveIds.length > 1 ? "memories" : "memory";
167948
+ return ok2(`Archived ${plural} [ID: ${idList}]${reasonSuffix}.`);
167075
167949
  }
167076
167950
  return err2("Error: Unknown action.");
167077
167951
  }
167078
167952
  };
167079
167953
  }
167080
167954
 
167955
+ // ../plugin/src/tools/ctx-note/constants.ts
167956
+ var CTX_NOTE_DESCRIPTION = `Working notes for this session's future — reminders, follow-ups, and things to revisit later.
167957
+
167958
+ 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.
167959
+
167960
+ Actions:
167961
+ - write: save a note (content). Add surface_condition to make it a smart note (below).
167962
+ - 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.
167963
+ - update / dismiss: change or retire a note by note_id.
167964
+
167965
+ 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:
167966
+ ✓ "When PR #42 in cortexkit/magic-context is merged"
167967
+ ✓ "When the latest release tag is >= v0.22.0"
167968
+ ✓ "When packages/plugin/src/foo.ts contains a function named bar"
167969
+ ✗ "When the user mentions X" / "when we revisit Y" / "after we finish this refactor" — no external signal; write a regular note instead.
167970
+
167971
+ 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")`;
167972
+
167081
167973
  // src/tools/ctx-note.ts
167082
167974
  var FILTER_VALUES = [
167083
167975
  "active",
@@ -167097,13 +167989,19 @@ var ParamsSchema3 = exports_typebox.Object({
167097
167989
  })),
167098
167990
  content: exports_typebox.Optional(exports_typebox.String({ description: "Note text to store when action is 'write'." })),
167099
167991
  surface_condition: exports_typebox.Optional(exports_typebox.String({
167100
- 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."
167992
+ 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."
167101
167993
  })),
167102
167994
  note_id: exports_typebox.Optional(exports_typebox.Number({
167103
167995
  description: "Note ID (required for 'dismiss' and 'update' actions)."
167104
167996
  })),
167105
167997
  filter: exports_typebox.Optional(exports_typebox.Union(FILTER_VALUES.map((value) => exports_typebox.Literal(value)), {
167106
167998
  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."
167999
+ })),
168000
+ limit: exports_typebox.Optional(exports_typebox.Number({
168001
+ description: "Max notes per section for read, newest first (default: 25)"
168002
+ })),
168003
+ offset: exports_typebox.Optional(exports_typebox.Number({
168004
+ description: "Skip this many newest notes for read — page older ones (default: 0)"
167107
168005
  }))
167108
168006
  });
167109
168007
  function ok3(text) {
@@ -167140,15 +168038,20 @@ function formatNoteLine(note) {
167140
168038
  var DISMISS_FOOTER = `
167141
168039
 
167142
168040
  To dismiss a stale note: ctx_note(action="dismiss", note_id=N)`;
168041
+ var DEFAULT_READ_LIMIT = 25;
168042
+ function paginateNewestFirst(notes, limit, offset) {
168043
+ const total = notes.length;
168044
+ const newestFirst = [...notes].reverse();
168045
+ const page = newestFirst.slice(offset, offset + limit);
168046
+ const remaining = total - offset - page.length;
168047
+ const footer = remaining > 0 ? `Showing ${page.length} of ${total} (newest first) — ${remaining} older: ctx_note(action="read", offset=${offset + page.length})` : null;
168048
+ return { page, total, footer };
168049
+ }
167143
168050
  function createCtxNoteTool(deps) {
167144
168051
  return {
167145
168052
  name: "ctx_note",
167146
168053
  label: "Magic Context: Notes",
167147
- description: `Save or inspect durable session notes that persist for this session.
167148
- ` + `Use this for short goals, constraints, decisions, or reminders worth carrying forward.
167149
-
167150
- ` + `Actions:
167151
- ` + "- `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")`',
168054
+ description: CTX_NOTE_DESCRIPTION,
167152
168055
  parameters: ParamsSchema3,
167153
168056
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
167154
168057
  const sessionId = ctx.sessionManager.getSessionId();
@@ -167230,11 +168133,15 @@ function createCtxNoteTool(deps) {
167230
168133
  - ${parts.join(`
167231
168134
  - `)}`);
167232
168135
  }
168136
+ const limit = typeof params.limit === "number" && params.limit > 0 ? Math.floor(params.limit) : DEFAULT_READ_LIMIT;
168137
+ const offset = typeof params.offset === "number" && params.offset > 0 ? Math.floor(params.offset) : 0;
167233
168138
  const sections = readNotes({
167234
168139
  db: deps.db,
167235
168140
  sessionId,
167236
168141
  cwd: ctx.cwd,
167237
- filter: params.filter
168142
+ filter: params.filter,
168143
+ limit,
168144
+ offset
167238
168145
  });
167239
168146
  try {
167240
168147
  setNoteLastReadAt(deps.db, sessionId);
@@ -167242,7 +168149,7 @@ function createCtxNoteTool(deps) {
167242
168149
  if (sections.length === 0) {
167243
168150
  return ok3(`## Notes
167244
168151
 
167245
- No notes for the current filter.`);
168152
+ No session notes or smart notes.`);
167246
168153
  }
167247
168154
  const body = sections.join(`
167248
168155
 
@@ -167269,10 +168176,14 @@ function readNotes(args) {
167269
168176
  }) : [];
167270
168177
  const sections2 = [];
167271
168178
  if (sessionNotes2.length > 0) {
168179
+ const { page, footer } = paginateNewestFirst(sessionNotes2, args.limit, args.offset);
168180
+ const lines = page.map(formatNoteLine).join(`
168181
+ `);
167272
168182
  sections2.push(`## Session Notes
167273
168183
 
167274
- ${sessionNotes2.map(formatNoteLine).join(`
167275
- `)}`);
168184
+ ${lines}${footer ? `
168185
+
168186
+ ${footer}` : ""}`);
167276
168187
  }
167277
168188
  if (readySmartNotes.length > 0) {
167278
168189
  sections2.push(`## \uD83D\uDD14 Ready Smart Notes
@@ -167303,17 +168214,25 @@ ${readySmartNotes.map(formatNoteLine).join(`
167303
168214
  }) : [];
167304
168215
  const sections = [];
167305
168216
  if (sessionNotes.length > 0) {
168217
+ const { page, footer } = paginateNewestFirst(sessionNotes, args.limit, args.offset);
168218
+ const lines = page.map(formatNoteLine).join(`
168219
+ `);
167306
168220
  sections.push(`## Session Notes
167307
168221
 
167308
- ${sessionNotes.map(formatNoteLine).join(`
167309
- `)}`);
168222
+ ${lines}${footer ? `
168223
+
168224
+ ${footer}` : ""}`);
167310
168225
  }
167311
168226
  if (smartNotes.length > 0) {
168227
+ const { page, footer } = paginateNewestFirst(smartNotes, args.limit, args.offset);
168228
+ const lines = page.map(formatNoteLine).join(`
168229
+
168230
+ `);
167312
168231
  sections.push(`## Smart Notes
167313
168232
 
167314
- ${smartNotes.map(formatNoteLine).join(`
168233
+ ${lines}${footer ? `
167315
168234
 
167316
- `)}`);
168235
+ ${footer}` : ""}`);
167317
168236
  }
167318
168237
  return sections;
167319
168238
  }
@@ -167321,7 +168240,7 @@ ${smartNotes.map(formatNoteLine).join(`
167321
168240
  // ../plugin/src/features/magic-context/range-parser.ts
167322
168241
  function parseRangeString(input) {
167323
168242
  const maxRangeElements = 1000;
167324
- const trimmed = input.trim();
168243
+ const trimmed = input.replace(/§/g, "").trim();
167325
168244
  if (trimmed === "") {
167326
168245
  throw new Error("Range string must not be empty");
167327
168246
  }
@@ -167469,7 +168388,11 @@ function createCtxReduceTool(deps) {
167469
168388
  };
167470
168389
  }
167471
168390
 
168391
+ // ../plugin/src/features/magic-context/search.ts
168392
+ init_logger();
168393
+
167472
168394
  // ../plugin/src/features/magic-context/git-commits/git-log-reader.ts
168395
+ init_logger();
167473
168396
  import { execFile } from "node:child_process";
167474
168397
  import { promisify } from "node:util";
167475
168398
  var execFileAsync = promisify(execFile);
@@ -167553,7 +168476,11 @@ ${body}` : subject;
167553
168476
  }
167554
168477
  return commits;
167555
168478
  }
168479
+ // ../plugin/src/features/magic-context/git-commits/indexer.ts
168480
+ init_logger();
168481
+
167556
168482
  // ../plugin/src/features/magic-context/git-commits/storage-git-commits.ts
168483
+ init_logger();
167557
168484
  var insertStatements = new WeakMap;
167558
168485
  var existingShasStatements = new WeakMap;
167559
168486
  var projectCountStatements = new WeakMap;
@@ -167766,6 +168693,7 @@ async function embedUnembeddedCommits(db, projectPath) {
167766
168693
  }
167767
168694
  }
167768
168695
  // ../plugin/src/features/magic-context/git-commits/search-git-commits.ts
168696
+ init_logger();
167769
168697
  var ftsStatements = new WeakMap;
167770
168698
  var ftsPlainStatements = new WeakMap;
167771
168699
  var getBySHAStatements = new WeakMap;
@@ -168436,6 +169364,9 @@ class BoundedSessionMap {
168436
169364
  }
168437
169365
  }
168438
169366
 
169367
+ // ../plugin/src/hooks/magic-context/inject-compartments.ts
169368
+ init_logger();
169369
+
168439
169370
  // ../plugin/src/hooks/magic-context/decay-curve.ts
168440
169371
  var H50 = 24;
168441
169372
  var D = 25;
@@ -168499,6 +169430,9 @@ function escapeXmlAttr2(s) {
168499
169430
  function escapeXmlContent2(s) {
168500
169431
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
168501
169432
  }
169433
+ function isTieredRow(c) {
169434
+ return typeof c.p1 === "string" && c.p1.length > 0;
169435
+ }
168502
169436
  function tierBody(c, tier2) {
168503
169437
  const tiers = [c.p1, c.p2, c.p3, c.p4];
168504
169438
  const requested = tiers[tier2 - 1];
@@ -168528,12 +169462,13 @@ function renderOneCompartment(c, tier2) {
168528
169462
  const baseAttrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr2(c.title)}"`;
168529
169463
  if (tier2 >= 5)
168530
169464
  return "";
168531
- if (c.legacy === 1) {
168532
- if (tier2 >= 4)
169465
+ if (c.legacy === 1 || !isTieredRow(c)) {
169466
+ const flat = (c.content ?? "").trim();
169467
+ if (tier2 >= 4 || flat.length === 0)
168533
169468
  return `<compartment ${baseAttrs} />`;
168534
169469
  return [
168535
169470
  `<compartment ${baseAttrs}>`,
168536
- escapeXmlContent2(legacyBodyForTier(c.content, tier2)),
169471
+ escapeXmlContent2(legacyBodyForTier(flat, tier2)),
168537
169472
  "</compartment>"
168538
169473
  ].join(`
168539
169474
  `);
@@ -168714,6 +169649,7 @@ function isAftAvailable() {
168714
169649
  }
168715
169650
 
168716
169651
  // ../plugin/src/hooks/magic-context/key-files-block.ts
169652
+ init_logger();
168717
169653
  var cachedKeyFilesBySession = new Map;
168718
169654
  var staleUpdates = new Map;
168719
169655
  function staleKey(update2) {
@@ -168967,6 +169903,21 @@ function renderMemoryBlockV2(memories, wrapper = "project-memory") {
168967
169903
  `);
168968
169904
  }
168969
169905
 
169906
+ // ../plugin/src/tools/ctx-search/constants.ts
169907
+ var CTX_SEARCH_DESCRIPTION = `Your long-term recall for this project — search everything that ever happened here, not just what's currently visible.
169908
+
169909
+ 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.
169910
+
169911
+ Sources (omit for a broad search across all):
169912
+ - memory: curated cross-session project knowledge — rules, constraints, conventions.
169913
+ - 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).
169914
+ - git_commit: this repository's commit history.
169915
+
169916
+ Picking sources:
169917
+ - "when did this change / was this working before" → ["git_commit", "message"]
169918
+ - "did we discuss this earlier" → ["message"]
169919
+ - "what's our convention / rule for X" → ["memory"]`;
169920
+
168970
169921
  // src/tools/ctx-search.ts
168971
169922
  var DEFAULT_LIMIT = 10;
168972
169923
  var ParamsSchema5 = exports_typebox.Object({
@@ -169050,18 +170001,7 @@ function createCtxSearchTool(deps) {
169050
170001
  return {
169051
170002
  name: "ctx_search",
169052
170003
  label: "Magic Context: Search",
169053
- description: `Search across project memories, indexed git commits, and raw conversation history.
169054
-
169055
- ` + `Sources:
169056
- ` + `- memory: curated cross-session knowledge for this project
169057
- ` + `- message: raw user/assistant messages from older compartmentalized history
169058
- ` + `- git_commit: HEAD git commits (when git commit indexing is enabled)
169059
-
169060
- ` + "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"]
169061
- ` + `- "when did we change this" → ["git_commit"]
169062
- ` + `- "what is our naming convention" → ["memory"]
169063
- ` + `- "did we discuss this earlier" → ["message"]
169064
- ` + "Omit sources for a broad search across all enabled channels.",
170004
+ description: CTX_SEARCH_DESCRIPTION,
169065
170005
  parameters: ParamsSchema5,
169066
170006
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
169067
170007
  const query = params.query?.trim();
@@ -169080,6 +170020,7 @@ function createCtxSearchTool(deps) {
169080
170020
  const embeddingEnabled = snapshot ? snapshot.enabled || snapshot.gitCommitEnabled : deps.embeddingEnabled;
169081
170021
  const gitCommitsEnabled = snapshot?.gitCommitEnabled ?? deps.gitCommitsEnabled ?? false;
169082
170022
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, sessionId);
170023
+ const messageOrdinalCutoff = lastCompartmentEnd >= 0 ? lastCompartmentEnd : 0;
169083
170024
  const visibleMemoryIds = getVisibleMemoryIds(deps.db, sessionId);
169084
170025
  const results = await unifiedSearch(deps.db, sessionId, projectIdentity, query, {
169085
170026
  limit: normalizeLimit3(params.limit),
@@ -169090,7 +170031,7 @@ function createCtxSearchTool(deps) {
169090
170031
  return result?.vector ?? null;
169091
170032
  },
169092
170033
  isEmbeddingRuntimeEnabled: () => embeddingEnabled === true,
169093
- maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined,
170034
+ maxMessageOrdinal: messageOrdinalCutoff,
169094
170035
  gitCommitsEnabled,
169095
170036
  sources: params.sources,
169096
170037
  visibleMemoryIds,
@@ -169177,13 +170118,15 @@ function registerMagicContextTools(pi, opts) {
169177
170118
  embeddingEnabled: opts.embeddingEnabled,
169178
170119
  gitCommitsEnabled: opts.gitCommitsEnabled
169179
170120
  }));
169180
- pi.registerTool(createCtxMemoryTool({
169181
- db: opts.db,
169182
- ensureProjectRegistered: opts.ensureProjectRegistered,
169183
- memoryEnabled: opts.memoryEnabled,
169184
- embeddingEnabled: opts.embeddingEnabled,
169185
- allowDreamerActions: opts.allowDreamerActions ?? false
169186
- }));
170121
+ if (opts.memoryToolEnabled !== false) {
170122
+ pi.registerTool(createCtxMemoryTool({
170123
+ db: opts.db,
170124
+ ensureProjectRegistered: opts.ensureProjectRegistered,
170125
+ memoryEnabled: opts.memoryEnabled,
170126
+ embeddingEnabled: opts.embeddingEnabled,
170127
+ allowDreamerActions: opts.allowDreamerActions ?? false
170128
+ }));
170129
+ }
169187
170130
  if (!opts.sessionScopedToolsDisabled) {
169188
170131
  pi.registerTool(createCtxNoteTool({
169189
170132
  db: opts.db,
@@ -169206,7 +170149,7 @@ var openedDb;
169206
170149
  function magicContextSubagentExtension(pi) {
169207
170150
  setHarness("pi");
169208
170151
  pi.registerFlag(SUBAGENT_DREAMER_ACTIONS_FLAG, {
169209
- description: "Enable dreamer-only ctx_memory actions (update, merge, archive).",
170152
+ description: "Register ctx_memory with dreamer actions for Magic Context subagents.",
169210
170153
  type: "boolean",
169211
170154
  default: false
169212
170155
  });
@@ -169224,10 +170167,11 @@ function magicContextSubagentExtension(pi) {
169224
170167
  registerMagicContextTools(pi, {
169225
170168
  db,
169226
170169
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
170170
+ memoryToolEnabled: dreamerActionsEnabled,
169227
170171
  allowDreamerActions: dreamerActionsEnabled,
169228
170172
  sessionScopedToolsDisabled: true
169229
170173
  });
169230
- 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})`);
170174
+ 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})`);
169231
170175
  } catch (err5) {
169232
170176
  const message = err5 instanceof Error ? err5.message : String(err5);
169233
170177
  log(`[pi-subagent] startup failed: ${message}`);