@cortexkit/pi-magic-context 0.16.3 → 0.17.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.
@@ -15479,12 +15479,12 @@ var require_esprima = __commonJS((exports, module) => {
15479
15479
  }
15480
15480
  exports2.parseScript = parseScript;
15481
15481
  function tokenize(code, options, delegate) {
15482
- var tokenizer = new tokenizer_1.Tokenizer(code, options);
15482
+ var tokenizer2 = new tokenizer_1.Tokenizer(code, options);
15483
15483
  var tokens;
15484
15484
  tokens = [];
15485
15485
  try {
15486
15486
  while (true) {
15487
- var token = tokenizer.getNextToken();
15487
+ var token = tokenizer2.getNextToken();
15488
15488
  if (!token) {
15489
15489
  break;
15490
15490
  }
@@ -15494,10 +15494,10 @@ var require_esprima = __commonJS((exports, module) => {
15494
15494
  tokens.push(token);
15495
15495
  }
15496
15496
  } catch (e) {
15497
- tokenizer.errorHandler.tolerate(e);
15497
+ tokenizer2.errorHandler.tolerate(e);
15498
15498
  }
15499
- if (tokenizer.errorHandler.tolerant) {
15500
- tokens.errors = tokenizer.errors();
15499
+ if (tokenizer2.errorHandler.tolerant) {
15500
+ tokens.errors = tokenizer2.errors();
15501
15501
  }
15502
15502
  return tokens;
15503
15503
  }
@@ -16072,13 +16072,13 @@ var require_esprima = __commonJS((exports, module) => {
16072
16072
  };
16073
16073
  JSXParser2.prototype.parseJSXNameValueAttribute = function() {
16074
16074
  var node = this.createJSXNode();
16075
- var name = this.parseJSXAttributeName();
16075
+ var name2 = this.parseJSXAttributeName();
16076
16076
  var value = null;
16077
16077
  if (this.matchJSX("=")) {
16078
16078
  this.expectJSX("=");
16079
16079
  value = this.parseJSXAttributeValue();
16080
16080
  }
16081
- return this.finalize(node, new JSXNode.JSXAttribute(name, value));
16081
+ return this.finalize(node, new JSXNode.JSXAttribute(name2, value));
16082
16082
  };
16083
16083
  JSXParser2.prototype.parseJSXSpreadAttribute = function() {
16084
16084
  var node = this.createJSXNode();
@@ -16100,14 +16100,14 @@ var require_esprima = __commonJS((exports, module) => {
16100
16100
  JSXParser2.prototype.parseJSXOpeningElement = function() {
16101
16101
  var node = this.createJSXNode();
16102
16102
  this.expectJSX("<");
16103
- var name = this.parseJSXElementName();
16103
+ var name2 = this.parseJSXElementName();
16104
16104
  var attributes = this.parseJSXAttributes();
16105
16105
  var selfClosing = this.matchJSX("/");
16106
16106
  if (selfClosing) {
16107
16107
  this.expectJSX("/");
16108
16108
  }
16109
16109
  this.expectJSX(">");
16110
- return this.finalize(node, new JSXNode.JSXOpeningElement(name, selfClosing, attributes));
16110
+ return this.finalize(node, new JSXNode.JSXOpeningElement(name2, selfClosing, attributes));
16111
16111
  };
16112
16112
  JSXParser2.prototype.parseJSXBoundaryElement = function() {
16113
16113
  var node = this.createJSXNode();
@@ -16118,14 +16118,14 @@ var require_esprima = __commonJS((exports, module) => {
16118
16118
  this.expectJSX(">");
16119
16119
  return this.finalize(node, new JSXNode.JSXClosingElement(name_3));
16120
16120
  }
16121
- var name = this.parseJSXElementName();
16121
+ var name2 = this.parseJSXElementName();
16122
16122
  var attributes = this.parseJSXAttributes();
16123
16123
  var selfClosing = this.matchJSX("/");
16124
16124
  if (selfClosing) {
16125
16125
  this.expectJSX("/");
16126
16126
  }
16127
16127
  this.expectJSX(">");
16128
- return this.finalize(node, new JSXNode.JSXOpeningElement(name, selfClosing, attributes));
16128
+ return this.finalize(node, new JSXNode.JSXOpeningElement(name2, selfClosing, attributes));
16129
16129
  };
16130
16130
  JSXParser2.prototype.parseJSXEmptyExpression = function() {
16131
16131
  var node = this.createJSXChildNode();
@@ -16268,9 +16268,9 @@ var require_esprima = __commonJS((exports, module) => {
16268
16268
  Object.defineProperty(exports2, "__esModule", { value: true });
16269
16269
  var jsx_syntax_1 = __webpack_require__(6);
16270
16270
  var JSXClosingElement = function() {
16271
- function JSXClosingElement2(name) {
16271
+ function JSXClosingElement2(name2) {
16272
16272
  this.type = jsx_syntax_1.JSXSyntax.JSXClosingElement;
16273
- this.name = name;
16273
+ this.name = name2;
16274
16274
  }
16275
16275
  return JSXClosingElement2;
16276
16276
  }();
@@ -16301,9 +16301,9 @@ var require_esprima = __commonJS((exports, module) => {
16301
16301
  }();
16302
16302
  exports2.JSXExpressionContainer = JSXExpressionContainer;
16303
16303
  var JSXIdentifier = function() {
16304
- function JSXIdentifier2(name) {
16304
+ function JSXIdentifier2(name2) {
16305
16305
  this.type = jsx_syntax_1.JSXSyntax.JSXIdentifier;
16306
- this.name = name;
16306
+ this.name = name2;
16307
16307
  }
16308
16308
  return JSXIdentifier2;
16309
16309
  }();
@@ -16318,27 +16318,27 @@ var require_esprima = __commonJS((exports, module) => {
16318
16318
  }();
16319
16319
  exports2.JSXMemberExpression = JSXMemberExpression;
16320
16320
  var JSXAttribute = function() {
16321
- function JSXAttribute2(name, value) {
16321
+ function JSXAttribute2(name2, value) {
16322
16322
  this.type = jsx_syntax_1.JSXSyntax.JSXAttribute;
16323
- this.name = name;
16323
+ this.name = name2;
16324
16324
  this.value = value;
16325
16325
  }
16326
16326
  return JSXAttribute2;
16327
16327
  }();
16328
16328
  exports2.JSXAttribute = JSXAttribute;
16329
16329
  var JSXNamespacedName = function() {
16330
- function JSXNamespacedName2(namespace, name) {
16330
+ function JSXNamespacedName2(namespace, name2) {
16331
16331
  this.type = jsx_syntax_1.JSXSyntax.JSXNamespacedName;
16332
16332
  this.namespace = namespace;
16333
- this.name = name;
16333
+ this.name = name2;
16334
16334
  }
16335
16335
  return JSXNamespacedName2;
16336
16336
  }();
16337
16337
  exports2.JSXNamespacedName = JSXNamespacedName;
16338
16338
  var JSXOpeningElement = function() {
16339
- function JSXOpeningElement2(name, selfClosing, attributes) {
16339
+ function JSXOpeningElement2(name2, selfClosing, attributes) {
16340
16340
  this.type = jsx_syntax_1.JSXSyntax.JSXOpeningElement;
16341
- this.name = name;
16341
+ this.name = name2;
16342
16342
  this.selfClosing = selfClosing;
16343
16343
  this.attributes = attributes;
16344
16344
  }
@@ -16712,9 +16712,9 @@ var require_esprima = __commonJS((exports, module) => {
16712
16712
  }();
16713
16713
  exports2.FunctionExpression = FunctionExpression;
16714
16714
  var Identifier = function() {
16715
- function Identifier2(name) {
16715
+ function Identifier2(name2) {
16716
16716
  this.type = syntax_1.Syntax.Identifier;
16717
- this.name = name;
16717
+ this.name = name2;
16718
16718
  }
16719
16719
  return Identifier2;
16720
16720
  }();
@@ -19220,10 +19220,10 @@ var require_esprima = __commonJS((exports, module) => {
19220
19220
  this.context.inFunctionBody = previousInFunctionBody;
19221
19221
  return this.finalize(node, new Node.BlockStatement(body));
19222
19222
  };
19223
- Parser2.prototype.validateParam = function(options, param, name) {
19224
- var key = "$" + name;
19223
+ Parser2.prototype.validateParam = function(options, param, name2) {
19224
+ var key = "$" + name2;
19225
19225
  if (this.context.strict) {
19226
- if (this.scanner.isRestrictedWord(name)) {
19226
+ if (this.scanner.isRestrictedWord(name2)) {
19227
19227
  options.stricted = param;
19228
19228
  options.message = messages_1.Messages.StrictParamName;
19229
19229
  }
@@ -19232,10 +19232,10 @@ var require_esprima = __commonJS((exports, module) => {
19232
19232
  options.message = messages_1.Messages.StrictParamDupe;
19233
19233
  }
19234
19234
  } else if (!options.firstRestricted) {
19235
- if (this.scanner.isRestrictedWord(name)) {
19235
+ if (this.scanner.isRestrictedWord(name2)) {
19236
19236
  options.firstRestricted = param;
19237
19237
  options.message = messages_1.Messages.StrictParamName;
19238
- } else if (this.scanner.isStrictModeReservedWord(name)) {
19238
+ } else if (this.scanner.isStrictModeReservedWord(name2)) {
19239
19239
  options.firstRestricted = param;
19240
19240
  options.message = messages_1.Messages.StrictReservedWord;
19241
19241
  } else if (Object.prototype.hasOwnProperty.call(options.paramSet, key)) {
@@ -21443,8 +21443,8 @@ var require_esprima = __commonJS((exports, module) => {
21443
21443
  };
21444
21444
  return Reader2;
21445
21445
  }();
21446
- var Tokenizer = function() {
21447
- function Tokenizer2(code, config2) {
21446
+ var Tokenizer2 = function() {
21447
+ function Tokenizer3(code, config2) {
21448
21448
  this.errorHandler = new error_handler_1.ErrorHandler;
21449
21449
  this.errorHandler.tolerant = config2 ? typeof config2.tolerant === "boolean" && config2.tolerant : false;
21450
21450
  this.scanner = new scanner_1.Scanner(code, this.errorHandler);
@@ -21454,10 +21454,10 @@ var require_esprima = __commonJS((exports, module) => {
21454
21454
  this.buffer = [];
21455
21455
  this.reader = new Reader;
21456
21456
  }
21457
- Tokenizer2.prototype.errors = function() {
21457
+ Tokenizer3.prototype.errors = function() {
21458
21458
  return this.errorHandler.errors;
21459
21459
  };
21460
- Tokenizer2.prototype.getNextToken = function() {
21460
+ Tokenizer3.prototype.getNextToken = function() {
21461
21461
  if (this.buffer.length === 0) {
21462
21462
  var comments = this.scanner.scanComments();
21463
21463
  if (this.scanner.trackComment) {
@@ -21515,9 +21515,9 @@ var require_esprima = __commonJS((exports, module) => {
21515
21515
  }
21516
21516
  return this.buffer.shift();
21517
21517
  };
21518
- return Tokenizer2;
21518
+ return Tokenizer3;
21519
21519
  }();
21520
- exports2.Tokenizer = Tokenizer;
21520
+ exports2.Tokenizer = Tokenizer2;
21521
21521
  }
21522
21522
  ]);
21523
21523
  });
@@ -22742,7 +22742,7 @@ var require_parse = __commonJS((exports, module) => {
22742
22742
  set_comments_host(obj);
22743
22743
  set_prop(UNDEFINED, true);
22744
22744
  let started = false;
22745
- let name;
22745
+ let name2;
22746
22746
  parse_comments();
22747
22747
  while (!is(CURLY_BRACKET_CLOSE)) {
22748
22748
  if (started) {
@@ -22757,15 +22757,15 @@ var require_parse = __commonJS((exports, module) => {
22757
22757
  }
22758
22758
  started = true;
22759
22759
  expect("String");
22760
- name = JSON.parse(current.value);
22761
- set_prop(name);
22760
+ name2 = JSON.parse(current.value);
22761
+ set_prop(name2);
22762
22762
  assign_comments(PREFIX_BEFORE);
22763
22763
  next();
22764
22764
  parse_comments(PREFIX_AFTER_PROP);
22765
22765
  expect(COLON);
22766
22766
  next();
22767
22767
  parse_comments(PREFIX_AFTER_COLON);
22768
- obj[name] = transform2(name, walk());
22768
+ obj[name2] = transform2(name2, walk());
22769
22769
  parse_comments();
22770
22770
  }
22771
22771
  if (started) {
@@ -22990,10 +22990,10 @@ var require_stringify = __commonJS((exports, module) => {
22990
22990
  replacer = null;
22991
22991
  indent = EMPTY;
22992
22992
  };
22993
- var join6 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
22993
+ var join7 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
22994
22994
  var join_content = (inside, value, gap) => {
22995
22995
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
22996
- return join6(comment, inside, gap);
22996
+ return join7(comment, inside, gap);
22997
22997
  };
22998
22998
  var stringify_string = (holder, key, value) => {
22999
22999
  const raw = get_raw_string_literal(holder, key);
@@ -23015,13 +23015,13 @@ var require_stringify = __commonJS((exports, module) => {
23015
23015
  if (i !== 0) {
23016
23016
  inside += COMMA;
23017
23017
  }
23018
- const before = join6(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
23018
+ const before = join7(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
23019
23019
  inside += before || LF + deeper_gap;
23020
23020
  inside += stringify(i, value, deeper_gap) || STR_NULL;
23021
23021
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
23022
23022
  after_comma = process_comments(value, AFTER(i), deeper_gap);
23023
23023
  }
23024
- inside += join6(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23024
+ inside += join7(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23025
23025
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
23026
23026
  };
23027
23027
  var object_stringify = (value, gap) => {
@@ -23042,13 +23042,13 @@ var require_stringify = __commonJS((exports, module) => {
23042
23042
  inside += COMMA;
23043
23043
  }
23044
23044
  first = false;
23045
- const before = join6(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
23045
+ const before = join7(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
23046
23046
  inside += before || LF + deeper_gap;
23047
23047
  inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
23048
23048
  after_comma = process_comments(value, AFTER(key), deeper_gap);
23049
23049
  };
23050
23050
  keys.forEach(iteratee);
23051
- inside += join6(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23051
+ inside += join7(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23052
23052
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
23053
23053
  };
23054
23054
  function stringify(key, holder, gap) {
@@ -23149,9 +23149,9 @@ __export(exports_read_session_db, {
23149
23149
  findLastAssistantModelFromOpenCodeDb: () => findLastAssistantModelFromOpenCodeDb,
23150
23150
  closeReadOnlySessionDb: () => closeReadOnlySessionDb
23151
23151
  });
23152
- import { join as join7 } from "node:path";
23152
+ import { join as join8 } from "node:path";
23153
23153
  function getOpenCodeDbPath() {
23154
- return join7(getDataDir(), "opencode", "opencode.db");
23154
+ return join8(getDataDir(), "opencode", "opencode.db");
23155
23155
  }
23156
23156
  function closeCachedReadOnlyDb() {
23157
23157
  if (!cachedReadOnlyDb) {
@@ -23210,7 +23210,8 @@ function findLastAssistantModelFromOpenCodeDb(sessionId) {
23210
23210
  try {
23211
23211
  return withReadOnlySessionDb((db) => {
23212
23212
  const row = db.prepare(`SELECT json_extract(data, '$.providerID') as providerID,
23213
- json_extract(data, '$.modelID') as modelID
23213
+ json_extract(data, '$.modelID') as modelID,
23214
+ json_extract(data, '$.agent') as agent
23214
23215
  FROM message
23215
23216
  WHERE session_id = ?
23216
23217
  AND json_extract(data, '$.role') = 'assistant'
@@ -23221,7 +23222,12 @@ function findLastAssistantModelFromOpenCodeDb(sessionId) {
23221
23222
  if (!row || typeof row.providerID !== "string" || typeof row.modelID !== "string") {
23222
23223
  return null;
23223
23224
  }
23224
- return { providerID: row.providerID, modelID: row.modelID };
23225
+ const agent = typeof row.agent === "string" && row.agent.length > 0 ? row.agent : undefined;
23226
+ return {
23227
+ providerID: row.providerID,
23228
+ modelID: row.modelID,
23229
+ ...agent ? { agent } : {}
23230
+ };
23225
23231
  });
23226
23232
  } catch (error48) {
23227
23233
  log("[magic-context] failed to recover live model from OpenCode DB:", error48);
@@ -23240,8 +23246,8 @@ init_embedding();
23240
23246
 
23241
23247
  // ../plugin/src/features/magic-context/storage-db.ts
23242
23248
  init_data_path();
23243
- import { copyFileSync, cpSync, existsSync as existsSync2, mkdirSync as mkdirSync3 } from "node:fs";
23244
- import { join as join5 } from "node:path";
23249
+ import { copyFileSync, cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
23250
+ import { join as join6 } from "node:path";
23245
23251
 
23246
23252
  // ../plugin/src/shared/error-message.ts
23247
23253
  function getErrorMessage(error48) {
@@ -23526,6 +23532,58 @@ var MIGRATIONS = [
23526
23532
  db.exec("ALTER TABLE notes ADD COLUMN harness TEXT NOT NULL DEFAULT 'opencode'");
23527
23533
  }
23528
23534
  }
23535
+ },
23536
+ {
23537
+ version: 8,
23538
+ description: "Add partial indexes on tags(session_id, tag_number) for active and dropped",
23539
+ up: (db) => {
23540
+ db.exec(`
23541
+ CREATE INDEX IF NOT EXISTS idx_tags_active_session_tag_number
23542
+ ON tags(session_id, tag_number)
23543
+ WHERE status = 'active';
23544
+
23545
+ CREATE INDEX IF NOT EXISTS idx_tags_dropped_session_tag_number
23546
+ ON tags(session_id, tag_number)
23547
+ WHERE status = 'dropped';
23548
+ `);
23549
+ db.exec("ANALYZE tags;");
23550
+ }
23551
+ },
23552
+ {
23553
+ version: 9,
23554
+ description: "Persist tool_definition_measurements across plugin restarts",
23555
+ up: (db) => {
23556
+ db.exec(`
23557
+ CREATE TABLE IF NOT EXISTS tool_definition_measurements (
23558
+ provider_id TEXT NOT NULL,
23559
+ model_id TEXT NOT NULL,
23560
+ agent_name TEXT NOT NULL,
23561
+ tool_id TEXT NOT NULL,
23562
+ token_count INTEGER NOT NULL,
23563
+ recorded_at INTEGER NOT NULL,
23564
+ PRIMARY KEY (provider_id, model_id, agent_name, tool_id)
23565
+ );
23566
+ `);
23567
+ }
23568
+ },
23569
+ {
23570
+ version: 10,
23571
+ description: "Add tool_owner_message_id column to tags + composite identity indexes",
23572
+ up: (db) => {
23573
+ const cols = db.prepare("PRAGMA table_info(tags)").all();
23574
+ if (!cols.some((c) => c.name === "tool_owner_message_id")) {
23575
+ db.exec("ALTER TABLE tags ADD COLUMN tool_owner_message_id TEXT DEFAULT NULL");
23576
+ }
23577
+ db.exec(`
23578
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_tool_composite
23579
+ ON tags(session_id, message_id, tool_owner_message_id)
23580
+ WHERE type = 'tool' AND tool_owner_message_id IS NOT NULL;
23581
+
23582
+ CREATE INDEX IF NOT EXISTS idx_tags_tool_null_owner
23583
+ ON tags(session_id, message_id)
23584
+ WHERE type = 'tool' AND tool_owner_message_id IS NULL;
23585
+ `);
23586
+ }
23529
23587
  }
23530
23588
  ];
23531
23589
  function ensureMigrationsTable(db) {
@@ -23541,22 +23599,51 @@ function getCurrentVersion(db) {
23541
23599
  const row = db.prepare("SELECT MAX(version) as version FROM schema_migrations").get();
23542
23600
  return row?.version ?? 0;
23543
23601
  }
23602
+ function isSiblingMigrationConflict(error48, version2) {
23603
+ if (!(error48 instanceof Error))
23604
+ return false;
23605
+ const code = error48.code;
23606
+ if (code !== "SQLITE_CONSTRAINT_PRIMARYKEY" && code !== "SQLITE_CONSTRAINT_UNIQUE") {
23607
+ return false;
23608
+ }
23609
+ const msg = error48.message;
23610
+ if (!msg.includes("schema_migrations"))
23611
+ return false;
23612
+ if (!msg.toLowerCase().includes("version"))
23613
+ return false;
23614
+ return true;
23615
+ }
23544
23616
  function runMigrations(db) {
23545
23617
  ensureMigrationsTable(db);
23546
- const currentVersion = getCurrentVersion(db);
23547
- const pendingMigrations = MIGRATIONS.filter((m) => m.version > currentVersion);
23618
+ let currentVersion = getCurrentVersion(db);
23619
+ let pendingMigrations = MIGRATIONS.filter((m) => m.version > currentVersion);
23548
23620
  if (pendingMigrations.length === 0) {
23549
23621
  return;
23550
23622
  }
23551
23623
  log(`[migrations] current schema version: ${currentVersion}, applying ${pendingMigrations.length} migration(s)`);
23552
- for (const migration of pendingMigrations) {
23624
+ let migrationIndex = 0;
23625
+ while (migrationIndex < pendingMigrations.length) {
23626
+ const migration = pendingMigrations[migrationIndex];
23553
23627
  try {
23554
23628
  db.transaction(() => {
23555
23629
  migration.up(db);
23556
23630
  db.prepare("INSERT INTO schema_migrations (version, description, applied_at) VALUES (?, ?, ?)").run(migration.version, migration.description, Date.now());
23557
23631
  })();
23558
23632
  log(`[migrations] applied v${migration.version}: ${migration.description}`);
23633
+ migrationIndex += 1;
23559
23634
  } catch (error48) {
23635
+ if (isSiblingMigrationConflict(error48, migration.version)) {
23636
+ log(`[migrations] v${migration.version} already applied by sibling instance — resuming with re-read version`);
23637
+ const reReadVersion = getCurrentVersion(db);
23638
+ if (reReadVersion <= currentVersion) {
23639
+ log(`[migrations] FAILED v${migration.version}: sibling-conflict shape but version not advanced (${reReadVersion} <= ${currentVersion}) — failing closed`);
23640
+ throw new Error(`Migration v${migration.version} failed: sibling conflict reported but version did not advance. Database may need manual repair.`);
23641
+ }
23642
+ currentVersion = reReadVersion;
23643
+ pendingMigrations = MIGRATIONS.filter((m) => m.version > currentVersion);
23644
+ migrationIndex = 0;
23645
+ continue;
23646
+ }
23560
23647
  log(`[migrations] FAILED v${migration.version}: ${migration.description} — ${error48 instanceof Error ? error48.message : String(error48)}`);
23561
23648
  throw new Error(`Migration v${migration.version} failed: ${error48 instanceof Error ? error48.message : String(error48)}. Database may need manual repair.`);
23562
23649
  }
@@ -23564,655 +23651,6 @@ function runMigrations(db) {
23564
23651
  log(`[migrations] schema version now: ${MIGRATIONS[MIGRATIONS.length - 1].version}`);
23565
23652
  }
23566
23653
 
23567
- // ../plugin/src/features/magic-context/storage-db.ts
23568
- var databases = new Map;
23569
- var persistenceByDatabase = new WeakMap;
23570
- var persistenceErrorByDatabase = new WeakMap;
23571
- function resolveDatabasePath() {
23572
- const dbDir = getMagicContextStorageDir();
23573
- return { dbDir, dbPath: join5(dbDir, "context.db") };
23574
- }
23575
- function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
23576
- if (existsSync2(targetDbPath))
23577
- return;
23578
- const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
23579
- const legacyDbPath = join5(legacyDir, "context.db");
23580
- if (!existsSync2(legacyDbPath))
23581
- return;
23582
- log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
23583
- mkdirSync3(targetDbDir, { recursive: true });
23584
- for (const suffix of ["", "-wal", "-shm"]) {
23585
- const src = `${legacyDbPath}${suffix}`;
23586
- const dst = join5(targetDbDir, `context.db${suffix}`);
23587
- if (existsSync2(src)) {
23588
- try {
23589
- copyFileSync(src, dst);
23590
- } catch (error48) {
23591
- log(`[magic-context] failed to copy ${src}:`, getErrorMessage(error48));
23592
- }
23593
- }
23594
- }
23595
- const legacyModelsDir = join5(legacyDir, "models");
23596
- const targetModelsDir = join5(targetDbDir, "models");
23597
- if (existsSync2(legacyModelsDir) && !existsSync2(targetModelsDir)) {
23598
- try {
23599
- cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
23600
- } catch (error48) {
23601
- log("[magic-context] failed to copy embedding model cache:", getErrorMessage(error48));
23602
- }
23603
- }
23604
- }
23605
- function initializeDatabase(db) {
23606
- db.exec("PRAGMA journal_mode=WAL");
23607
- db.exec("PRAGMA busy_timeout=5000");
23608
- db.exec("PRAGMA foreign_keys=ON");
23609
- db.exec(`
23610
- CREATE TABLE IF NOT EXISTS tags (
23611
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23612
- session_id TEXT,
23613
- message_id TEXT,
23614
- type TEXT,
23615
- status TEXT DEFAULT 'active',
23616
- byte_size INTEGER,
23617
- tag_number INTEGER,
23618
- harness TEXT NOT NULL DEFAULT 'opencode',
23619
- UNIQUE(session_id, tag_number)
23620
- );
23621
-
23622
- CREATE TABLE IF NOT EXISTS pending_ops (
23623
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23624
- session_id TEXT,
23625
- tag_id INTEGER,
23626
- operation TEXT,
23627
- queued_at INTEGER,
23628
- harness TEXT NOT NULL DEFAULT 'opencode'
23629
- );
23630
-
23631
- CREATE TABLE IF NOT EXISTS source_contents (
23632
- tag_id INTEGER,
23633
- session_id TEXT,
23634
- content TEXT,
23635
- created_at INTEGER,
23636
- harness TEXT NOT NULL DEFAULT 'opencode',
23637
- PRIMARY KEY(session_id, tag_id)
23638
- );
23639
-
23640
- CREATE TABLE IF NOT EXISTS compartments (
23641
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23642
- session_id TEXT NOT NULL,
23643
- sequence INTEGER NOT NULL,
23644
- start_message INTEGER NOT NULL,
23645
- end_message INTEGER NOT NULL,
23646
- start_message_id TEXT DEFAULT '',
23647
- end_message_id TEXT DEFAULT '',
23648
- title TEXT NOT NULL,
23649
- content TEXT NOT NULL,
23650
- created_at INTEGER NOT NULL,
23651
- harness TEXT NOT NULL DEFAULT 'opencode',
23652
- UNIQUE(session_id, sequence)
23653
- );
23654
- CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
23655
-
23656
- CREATE TABLE IF NOT EXISTS compression_depth (
23657
- session_id TEXT NOT NULL,
23658
- message_ordinal INTEGER NOT NULL,
23659
- depth INTEGER NOT NULL DEFAULT 0,
23660
- harness TEXT NOT NULL DEFAULT 'opencode',
23661
- PRIMARY KEY(session_id, message_ordinal)
23662
- );
23663
- CREATE INDEX IF NOT EXISTS idx_compression_depth_session ON compression_depth(session_id);
23664
-
23665
- CREATE TABLE IF NOT EXISTS session_facts (
23666
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23667
- session_id TEXT NOT NULL,
23668
- category TEXT NOT NULL,
23669
- content TEXT NOT NULL,
23670
- created_at INTEGER NOT NULL,
23671
- updated_at INTEGER NOT NULL,
23672
- harness TEXT NOT NULL DEFAULT 'opencode'
23673
- );
23674
-
23675
- -- session_notes and smart_notes were merged into the unified notes table
23676
- -- by migration v1 (see features/magic-context/migrations.ts). The old tables
23677
- -- are never recreated; fresh DBs create only notes, upgraded DBs have
23678
- -- their old tables migrated and dropped by the migration runner.
23679
-
23680
- CREATE TABLE IF NOT EXISTS memories (
23681
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23682
- project_path TEXT NOT NULL,
23683
- category TEXT NOT NULL,
23684
- content TEXT NOT NULL,
23685
- normalized_hash TEXT NOT NULL,
23686
- source_session_id TEXT,
23687
- source_type TEXT DEFAULT 'historian',
23688
- seen_count INTEGER DEFAULT 1,
23689
- retrieval_count INTEGER DEFAULT 0,
23690
- first_seen_at INTEGER NOT NULL,
23691
- created_at INTEGER NOT NULL,
23692
- updated_at INTEGER NOT NULL,
23693
- last_seen_at INTEGER NOT NULL,
23694
- last_retrieved_at INTEGER,
23695
- status TEXT DEFAULT 'active',
23696
- expires_at INTEGER,
23697
- verification_status TEXT DEFAULT 'unverified',
23698
- verified_at INTEGER,
23699
- superseded_by_memory_id INTEGER,
23700
- merged_from TEXT,
23701
- metadata_json TEXT,
23702
- UNIQUE(project_path, category, normalized_hash)
23703
- );
23704
-
23705
- CREATE TABLE IF NOT EXISTS memory_embeddings (
23706
- memory_id INTEGER PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
23707
- embedding BLOB NOT NULL,
23708
- model_id TEXT
23709
- );
23710
-
23711
- CREATE TABLE IF NOT EXISTS dream_state (
23712
- key TEXT PRIMARY KEY,
23713
- value TEXT NOT NULL
23714
- );
23715
-
23716
- CREATE TABLE IF NOT EXISTS dream_queue (
23717
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23718
- project_path TEXT NOT NULL,
23719
- reason TEXT NOT NULL,
23720
- enqueued_at INTEGER NOT NULL,
23721
- started_at INTEGER,
23722
- retry_count INTEGER DEFAULT 0
23723
- );
23724
- CREATE INDEX IF NOT EXISTS idx_dream_queue_project ON dream_queue(project_path);
23725
- CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, enqueued_at);
23726
-
23727
- CREATE TABLE IF NOT EXISTS dream_runs (
23728
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23729
- project_path TEXT NOT NULL,
23730
- started_at INTEGER NOT NULL,
23731
- finished_at INTEGER NOT NULL,
23732
- holder_id TEXT NOT NULL,
23733
- tasks_json TEXT NOT NULL,
23734
- tasks_succeeded INTEGER NOT NULL DEFAULT 0,
23735
- tasks_failed INTEGER NOT NULL DEFAULT 0,
23736
- smart_notes_surfaced INTEGER NOT NULL DEFAULT 0,
23737
- smart_notes_pending INTEGER NOT NULL DEFAULT 0,
23738
- memory_changes_json TEXT
23739
- );
23740
- CREATE INDEX IF NOT EXISTS idx_dream_runs_project ON dream_runs(project_path, finished_at DESC);
23741
-
23742
- -- (smart_notes: see note above; merged into unified notes table by migration v1)
23743
-
23744
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
23745
- content,
23746
- category,
23747
- content='memories',
23748
- content_rowid='id',
23749
- tokenize='porter unicode61'
23750
- );
23751
-
23752
- CREATE VIRTUAL TABLE IF NOT EXISTS message_history_fts USING fts5(
23753
- session_id UNINDEXED,
23754
- message_ordinal UNINDEXED,
23755
- message_id UNINDEXED,
23756
- role,
23757
- content,
23758
- tokenize='porter unicode61'
23759
- );
23760
-
23761
- CREATE TABLE IF NOT EXISTS message_history_index (
23762
- session_id TEXT PRIMARY KEY,
23763
- last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
23764
- updated_at INTEGER NOT NULL,
23765
- harness TEXT NOT NULL DEFAULT 'opencode'
23766
- );
23767
-
23768
- CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
23769
- INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
23770
- END;
23771
-
23772
- CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
23773
- INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
23774
- END;
23775
-
23776
- CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
23777
- INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
23778
- INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
23779
- END;
23780
-
23781
- CREATE TABLE IF NOT EXISTS session_meta (
23782
- session_id TEXT PRIMARY KEY,
23783
- harness TEXT NOT NULL DEFAULT 'opencode',
23784
- last_response_time INTEGER,
23785
- cache_ttl TEXT,
23786
- counter INTEGER DEFAULT 0,
23787
- last_nudge_tokens INTEGER DEFAULT 0,
23788
- last_nudge_band TEXT DEFAULT '',
23789
- last_transform_error TEXT DEFAULT '',
23790
- nudge_anchor_message_id TEXT DEFAULT '',
23791
- nudge_anchor_text TEXT DEFAULT '',
23792
- sticky_turn_reminder_text TEXT DEFAULT '',
23793
- sticky_turn_reminder_message_id TEXT DEFAULT '',
23794
- note_nudge_trigger_pending INTEGER DEFAULT 0,
23795
- note_nudge_trigger_message_id TEXT DEFAULT '',
23796
- note_nudge_sticky_text TEXT DEFAULT '',
23797
- note_nudge_sticky_message_id TEXT DEFAULT '',
23798
- is_subagent INTEGER DEFAULT 0,
23799
- last_context_percentage REAL DEFAULT 0,
23800
- last_input_tokens INTEGER DEFAULT 0,
23801
- times_execute_threshold_reached INTEGER DEFAULT 0,
23802
- compartment_in_progress INTEGER DEFAULT 0,
23803
- historian_failure_count INTEGER DEFAULT 0,
23804
- historian_last_error TEXT DEFAULT NULL,
23805
- historian_last_failure_at INTEGER DEFAULT NULL,
23806
- system_prompt_hash TEXT DEFAULT '',
23807
- memory_block_cache TEXT DEFAULT '',
23808
- memory_block_count INTEGER DEFAULT 0,
23809
- memory_block_ids TEXT DEFAULT ''
23810
- );
23811
-
23812
- CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
23813
- CREATE INDEX IF NOT EXISTS idx_tags_session_message_id ON tags(session_id, message_id);
23814
- CREATE INDEX IF NOT EXISTS idx_pending_ops_session ON pending_ops(session_id);
23815
- CREATE INDEX IF NOT EXISTS idx_pending_ops_session_tag_id ON pending_ops(session_id, tag_id);
23816
- CREATE INDEX IF NOT EXISTS idx_source_contents_session ON source_contents(session_id);
23817
-
23818
- CREATE TABLE IF NOT EXISTS recomp_compartments (
23819
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23820
- session_id TEXT NOT NULL,
23821
- sequence INTEGER NOT NULL,
23822
- start_message INTEGER NOT NULL,
23823
- end_message INTEGER NOT NULL,
23824
- start_message_id TEXT DEFAULT '',
23825
- end_message_id TEXT DEFAULT '',
23826
- title TEXT NOT NULL,
23827
- content TEXT NOT NULL,
23828
- pass_number INTEGER NOT NULL,
23829
- created_at INTEGER NOT NULL,
23830
- harness TEXT NOT NULL DEFAULT 'opencode',
23831
- UNIQUE(session_id, sequence)
23832
- );
23833
-
23834
- CREATE TABLE IF NOT EXISTS recomp_facts (
23835
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23836
- session_id TEXT NOT NULL,
23837
- category TEXT NOT NULL,
23838
- content TEXT NOT NULL,
23839
- pass_number INTEGER NOT NULL,
23840
- created_at INTEGER NOT NULL,
23841
- harness TEXT NOT NULL DEFAULT 'opencode'
23842
- );
23843
-
23844
- CREATE INDEX IF NOT EXISTS idx_session_facts_session ON session_facts(session_id);
23845
- CREATE INDEX IF NOT EXISTS idx_recomp_compartments_session ON recomp_compartments(session_id);
23846
- CREATE INDEX IF NOT EXISTS idx_recomp_facts_session ON recomp_facts(session_id);
23847
- CREATE INDEX IF NOT EXISTS idx_memories_project_status_category ON memories(project_path, status, category);
23848
- CREATE INDEX IF NOT EXISTS idx_memories_project_status_expires ON memories(project_path, status, expires_at);
23849
- CREATE INDEX IF NOT EXISTS idx_memories_project_category_hash ON memories(project_path, category, normalized_hash);
23850
- CREATE INDEX IF NOT EXISTS idx_message_history_index_updated_at ON message_history_index(updated_at);
23851
- `);
23852
- ensureColumn(db, "session_meta", "last_nudge_band", "TEXT DEFAULT ''");
23853
- ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
23854
- ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
23855
- ensureColumn(db, "session_meta", "nudge_anchor_text", "TEXT DEFAULT ''");
23856
- ensureColumn(db, "session_meta", "sticky_turn_reminder_text", "TEXT DEFAULT ''");
23857
- ensureColumn(db, "session_meta", "sticky_turn_reminder_message_id", "TEXT DEFAULT ''");
23858
- ensureColumn(db, "session_meta", "note_nudge_trigger_pending", "INTEGER DEFAULT 0");
23859
- ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
23860
- ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
23861
- ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
23862
- ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
23863
- ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
23864
- ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
23865
- ensureColumn(db, "session_meta", "historian_failure_count", "INTEGER DEFAULT 0");
23866
- ensureColumn(db, "session_meta", "historian_last_error", "TEXT DEFAULT NULL");
23867
- ensureColumn(db, "session_meta", "historian_last_failure_at", "INTEGER DEFAULT NULL");
23868
- ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
23869
- ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
23870
- ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
23871
- ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
23872
- ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
23873
- ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
23874
- ensureColumn(db, "session_meta", "memory_block_cache", "TEXT DEFAULT ''");
23875
- ensureColumn(db, "session_meta", "memory_block_count", "INTEGER DEFAULT 0");
23876
- ensureColumn(db, "session_meta", "memory_block_ids", "TEXT DEFAULT ''");
23877
- ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
23878
- ensureColumn(db, "tags", "reasoning_byte_size", "INTEGER DEFAULT 0");
23879
- ensureColumn(db, "tags", "drop_mode", "TEXT DEFAULT 'full'");
23880
- ensureColumn(db, "tags", "tool_name", "TEXT");
23881
- ensureColumn(db, "tags", "input_byte_size", "INTEGER DEFAULT 0");
23882
- ensureColumn(db, "tags", "caveman_depth", "INTEGER DEFAULT 0");
23883
- ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
23884
- ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
23885
- ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
23886
- ensureColumn(db, "session_meta", "conversation_tokens", "INTEGER DEFAULT 0");
23887
- ensureColumn(db, "session_meta", "tool_call_tokens", "INTEGER DEFAULT 0");
23888
- ensureColumn(db, "session_meta", "recomp_partial_range_start", "INTEGER DEFAULT 0");
23889
- ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
23890
- ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
23891
- ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
23892
- ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
23893
- ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
23894
- ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
23895
- ensureColumn(db, "compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
23896
- ensureColumn(db, "compression_depth", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
23897
- ensureColumn(db, "session_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
23898
- ensureColumn(db, "session_meta", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
23899
- ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
23900
- ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
23901
- ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
23902
- }
23903
- function healAllNullColumns(db) {
23904
- healNullTextColumns(db);
23905
- healNullIntegerColumns(db);
23906
- healMissingMemoryBlockIds(db);
23907
- }
23908
- function healMissingMemoryBlockIds(db) {
23909
- try {
23910
- 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();
23911
- } catch {}
23912
- }
23913
- function healNullTextColumns(db) {
23914
- const columns = [
23915
- ["cache_ttl", ""],
23916
- ["last_nudge_band", ""],
23917
- ["last_transform_error", ""],
23918
- ["nudge_anchor_message_id", ""],
23919
- ["nudge_anchor_text", ""],
23920
- ["sticky_turn_reminder_text", ""],
23921
- ["sticky_turn_reminder_message_id", ""],
23922
- ["note_nudge_trigger_message_id", ""],
23923
- ["note_nudge_sticky_text", ""],
23924
- ["note_nudge_sticky_message_id", ""],
23925
- ["system_prompt_hash", ""],
23926
- ["stripped_placeholder_ids", ""],
23927
- ["memory_block_cache", ""],
23928
- ["memory_block_ids", ""],
23929
- ["compaction_marker_state", ""],
23930
- ["key_files", ""]
23931
- ];
23932
- for (const [column, fallback] of columns) {
23933
- try {
23934
- db.prepare(`UPDATE session_meta SET ${column} = ? WHERE ${column} IS NULL`).run(fallback);
23935
- } catch (_error) {}
23936
- }
23937
- }
23938
- function healNullIntegerColumns(db) {
23939
- const columns = [
23940
- ["times_execute_threshold_reached", 0],
23941
- ["compartment_in_progress", 0],
23942
- ["historian_failure_count", 0],
23943
- ["cleared_reasoning_through_tag", 0],
23944
- ["memory_block_count", 0],
23945
- ["system_prompt_tokens", 0],
23946
- ["conversation_tokens", 0],
23947
- ["tool_call_tokens", 0],
23948
- ["note_nudge_trigger_pending", 0]
23949
- ];
23950
- for (const [column, fallback] of columns) {
23951
- try {
23952
- db.prepare(`UPDATE session_meta SET ${column} = ? WHERE ${column} IS NULL`).run(fallback);
23953
- } catch (_error) {}
23954
- }
23955
- }
23956
- function ensureColumn(db, table, column, definition) {
23957
- if (!/^[a-z_]+$/.test(table) || !/^[a-z_]+$/.test(column) || !/^[A-Z0-9_'(),\s]+$/i.test(definition)) {
23958
- throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
23959
- }
23960
- const rows = db.prepare(`PRAGMA table_info(${table})`).all();
23961
- if (rows.some((row) => row.name === column)) {
23962
- return;
23963
- }
23964
- db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
23965
- }
23966
- function openDatabase() {
23967
- const { dbDir, dbPath } = resolveDatabasePath();
23968
- const existing = databases.get(dbPath);
23969
- if (existing) {
23970
- if (!persistenceByDatabase.has(existing)) {
23971
- persistenceByDatabase.set(existing, true);
23972
- }
23973
- return existing;
23974
- }
23975
- try {
23976
- migrateLegacyStorageIfNeeded(dbPath, dbDir);
23977
- mkdirSync3(dbDir, { recursive: true });
23978
- const db = new Database(dbPath);
23979
- initializeDatabase(db);
23980
- runMigrations(db);
23981
- databases.set(dbPath, db);
23982
- persistenceByDatabase.set(db, true);
23983
- persistenceErrorByDatabase.delete(db);
23984
- return db;
23985
- } catch (error48) {
23986
- const detail = getErrorMessage(error48);
23987
- log(`[magic-context] storage fatal: failed to open ${dbPath}: ${detail}`);
23988
- throw new Error(`[magic-context] storage unavailable: ${detail}. Magic Context is disabled for this run; check log for details.`);
23989
- }
23990
- }
23991
-
23992
- // ../plugin/src/shared/harness.ts
23993
- var currentHarness = "opencode";
23994
- var harnessLocked = false;
23995
- function setHarness(value) {
23996
- if (harnessLocked && currentHarness !== value) {
23997
- throw new Error(`Magic Context: harness already locked to "${currentHarness}"; cannot change to "${value}"`);
23998
- }
23999
- currentHarness = value;
24000
- harnessLocked = true;
24001
- }
24002
- function getHarness() {
24003
- return currentHarness;
24004
- }
24005
-
24006
- // src/subagent-entry.ts
24007
- init_logger();
24008
-
24009
- // src/config/index.ts
24010
- init_magic_context();
24011
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
24012
- import { homedir as homedir3 } from "node:os";
24013
- import { join as join6 } from "node:path";
24014
-
24015
- // ../plugin/src/config/variable.ts
24016
- import { existsSync as existsSync3, readFileSync } from "node:fs";
24017
- import { homedir as homedir2 } from "node:os";
24018
- import { dirname as dirname2, isAbsolute, resolve } from "node:path";
24019
- var ENV_PATTERN = /\{env:([^}]+)\}/g;
24020
- var FILE_PATTERN = /\{file:([^}]+)\}/g;
24021
- function substituteConfigVariables(input) {
24022
- const warnings = [];
24023
- let text = input.text;
24024
- text = text.replace(ENV_PATTERN, (_, rawName) => {
24025
- const varName = rawName.trim();
24026
- const value = varName ? process.env[varName] : undefined;
24027
- if (value === undefined || value === "") {
24028
- warnings.push(`Environment variable ${varName} is not set (referenced via {env:${varName}}); using empty string`);
24029
- return "";
24030
- }
24031
- return value;
24032
- });
24033
- const fileMatches = Array.from(text.matchAll(FILE_PATTERN));
24034
- if (fileMatches.length === 0) {
24035
- return { text, warnings };
24036
- }
24037
- const configDir = input.configPath ? dirname2(input.configPath) : process.cwd();
24038
- let output = "";
24039
- let cursor = 0;
24040
- for (const match of fileMatches) {
24041
- const token = match[0];
24042
- const rawPath = match[1] ?? "";
24043
- const index = match.index ?? 0;
24044
- output += text.slice(cursor, index);
24045
- cursor = index + token.length;
24046
- const lineStart = text.lastIndexOf(`
24047
- `, index - 1) + 1;
24048
- const prefix = text.slice(lineStart, index).trimStart();
24049
- if (prefix.startsWith("//")) {
24050
- output += token;
24051
- continue;
24052
- }
24053
- let filePath = rawPath.trim();
24054
- if (filePath.startsWith("~/")) {
24055
- filePath = resolve(homedir2(), filePath.slice(2));
24056
- } else if (!isAbsolute(filePath)) {
24057
- filePath = resolve(configDir, filePath);
24058
- }
24059
- if (!existsSync3(filePath)) {
24060
- warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
24061
- continue;
24062
- }
24063
- let contents;
24064
- try {
24065
- contents = readFileSync(filePath, "utf-8").trim();
24066
- } catch (error48) {
24067
- const message = error48 instanceof Error ? error48.message : String(error48);
24068
- warnings.push(`Failed to read file for ${token} (${filePath}): ${message}; using empty string`);
24069
- continue;
24070
- }
24071
- output += JSON.stringify(contents).slice(1, -1);
24072
- }
24073
- output += text.slice(cursor);
24074
- return { text: output, warnings };
24075
- }
24076
-
24077
- // src/config/index.ts
24078
- var import_comment_json = __toESM(require_src2(), 1);
24079
- var CONFIG_FILE_NAME = "magic-context";
24080
- function getProjectConfigPaths(cwd) {
24081
- const basePath = join6(cwd, ".pi", CONFIG_FILE_NAME);
24082
- return [`${basePath}.jsonc`, `${basePath}.json`];
24083
- }
24084
- function getUserConfigPaths() {
24085
- const home = process.env.HOME ?? homedir3();
24086
- const basePath = join6(home, ".pi", "agent", CONFIG_FILE_NAME);
24087
- return [`${basePath}.jsonc`, `${basePath}.json`];
24088
- }
24089
- function resolveFirstExisting(paths) {
24090
- return paths.find((path4) => existsSync4(path4));
24091
- }
24092
- function loadConfigFile(path4, scope) {
24093
- try {
24094
- const rawText = readFileSync2(path4, "utf-8");
24095
- const substituted = substituteConfigVariables({
24096
- text: rawText,
24097
- configPath: path4
24098
- });
24099
- return {
24100
- path: path4,
24101
- scope,
24102
- config: import_comment_json.parse(substituted.text),
24103
- warnings: substituted.warnings.map((warning) => `${path4}: ${warning}`)
24104
- };
24105
- } catch (error48) {
24106
- const message = error48 instanceof Error ? error48.message : String(error48);
24107
- return {
24108
- path: path4,
24109
- scope,
24110
- config: {},
24111
- warnings: [
24112
- `${path4}: failed to load config: ${message}; using defaults for this file.`
24113
- ]
24114
- };
24115
- }
24116
- }
24117
- function redactConfigValue(value) {
24118
- if (value === undefined)
24119
- return "<missing>";
24120
- if (value === null)
24121
- return "null";
24122
- if (typeof value === "string") {
24123
- return `string, ${value.length} char${value.length === 1 ? "" : "s"}`;
24124
- }
24125
- if (typeof value === "number")
24126
- return `number ${value}`;
24127
- if (typeof value === "boolean")
24128
- return `boolean ${value}`;
24129
- if (Array.isArray(value))
24130
- return `array, ${value.length} item${value.length === 1 ? "" : "s"}`;
24131
- if (typeof value === "object") {
24132
- const keys = Object.keys(value);
24133
- return `object with keys [${keys.join(", ")}]`;
24134
- }
24135
- return typeof value;
24136
- }
24137
- function isPlainObject2(value) {
24138
- return typeof value === "object" && value !== null && !Array.isArray(value);
24139
- }
24140
- function mergeRawConfigs(base, override) {
24141
- const merged = { ...base };
24142
- for (const [key, overrideValue] of Object.entries(override)) {
24143
- const baseValue = merged[key];
24144
- merged[key] = isPlainObject2(baseValue) && isPlainObject2(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
24145
- }
24146
- return merged;
24147
- }
24148
- function parsePiConfig(rawConfig) {
24149
- const parsed = MagicContextConfigSchema.safeParse(rawConfig);
24150
- if (parsed.success) {
24151
- return { config: parsed.data, warnings: [] };
24152
- }
24153
- const defaults = MagicContextConfigSchema.parse({});
24154
- const errorPaths = new Set;
24155
- for (const issue2 of parsed.error.issues) {
24156
- const topKey = issue2.path[0];
24157
- if (topKey !== undefined) {
24158
- errorPaths.add(String(topKey));
24159
- }
24160
- }
24161
- const patched = { ...rawConfig };
24162
- const warnings = [];
24163
- for (const key of errorPaths) {
24164
- const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
24165
- delete patched[key];
24166
- if (isAgentConfig) {
24167
- warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
24168
- continue;
24169
- }
24170
- const defaultValue = defaults[key];
24171
- warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultValue)}.`);
24172
- }
24173
- const retryParsed = MagicContextConfigSchema.safeParse(patched);
24174
- if (retryParsed.success) {
24175
- return { config: retryParsed.data, warnings };
24176
- }
24177
- warnings.push("Config recovery failed, using all defaults.");
24178
- return { config: defaults, warnings };
24179
- }
24180
- function loadPiConfig(opts = {}) {
24181
- const cwd = opts.cwd ?? process.cwd();
24182
- const loadedFiles = [];
24183
- const warnings = [];
24184
- const projectPath = resolveFirstExisting(getProjectConfigPaths(cwd));
24185
- if (projectPath) {
24186
- const loaded = loadConfigFile(projectPath, "project");
24187
- if (loaded)
24188
- loadedFiles.push(loaded);
24189
- }
24190
- const userPath = resolveFirstExisting(getUserConfigPaths());
24191
- if (userPath) {
24192
- const loaded = loadConfigFile(userPath, "user");
24193
- if (loaded)
24194
- loadedFiles.push(loaded);
24195
- }
24196
- let rawConfig = {};
24197
- const mergeFiles = [...loadedFiles].sort((a, b) => {
24198
- if (a.scope === b.scope)
24199
- return 0;
24200
- return a.scope === "user" ? -1 : 1;
24201
- });
24202
- for (const loaded of mergeFiles) {
24203
- const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
24204
- warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
24205
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
24206
- }
24207
- const parsed = parsePiConfig(rawConfig);
24208
- warnings.push(...parsed.warnings.map((warning) => `[merged config] ${warning}`));
24209
- return {
24210
- config: parsed.config,
24211
- warnings,
24212
- loadedFromPaths: loadedFiles.map((loaded) => loaded.path)
24213
- };
24214
- }
24215
-
24216
23654
  // ../plugin/src/shared/internal-initiator-marker.ts
24217
23655
  var OMO_INTERNAL_INITIATOR_MARKER = "<!-- OMO_INTERNAL_INITIATOR -->";
24218
23656
 
@@ -24225,9 +23663,6 @@ function removeSystemReminders(text) {
24225
23663
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, "").trim();
24226
23664
  }
24227
23665
 
24228
- // ../plugin/src/hooks/magic-context/read-session-chunk.ts
24229
- await init_read_session_db();
24230
-
24231
23666
  // ../../node_modules/.bun/ai-tokenizer@1.0.6/node_modules/ai-tokenizer/dist/index.js
24232
23667
  function _typeof(o) {
24233
23668
  "@babel/helpers - typeof";
@@ -156808,6 +156243,939 @@ function mergeCommitHashes(existing, next) {
156808
156243
  return merged;
156809
156244
  }
156810
156245
 
156246
+ // ../plugin/src/features/magic-context/tool-definition-tokens.ts
156247
+ var measurements = new Map;
156248
+ var persistenceDb = null;
156249
+ function keyFor(providerID, modelID, agentName) {
156250
+ const agent = agentName && agentName.length > 0 ? agentName : "default";
156251
+ return `${providerID}/${modelID}/${agent}`;
156252
+ }
156253
+ function setDatabase(db) {
156254
+ persistenceDb = db;
156255
+ }
156256
+ function loadToolDefinitionMeasurements(db) {
156257
+ let rows = [];
156258
+ try {
156259
+ rows = db.prepare("SELECT provider_id, model_id, agent_name, tool_id, token_count FROM tool_definition_measurements").all();
156260
+ } catch {
156261
+ return;
156262
+ }
156263
+ for (const row of rows) {
156264
+ const key = keyFor(row.provider_id, row.model_id, row.agent_name);
156265
+ let inner = measurements.get(key);
156266
+ if (!inner) {
156267
+ inner = new Map;
156268
+ measurements.set(key, inner);
156269
+ }
156270
+ inner.set(row.tool_id, row.token_count);
156271
+ }
156272
+ }
156273
+
156274
+ // ../plugin/src/features/magic-context/tool-owner-backfill.ts
156275
+ init_data_path();
156276
+ init_logger();
156277
+ import { existsSync as existsSync2 } from "node:fs";
156278
+ import { join as join5 } from "node:path";
156279
+ var LEASE_DURATION_MS = 5 * 60 * 1000;
156280
+ var LEASE_RENEWAL_MS = 60 * 1000;
156281
+ function resolveOpencodeDbPath() {
156282
+ return join5(getDataDir(), "opencode", "opencode.db");
156283
+ }
156284
+ function ensureBackfillStateTable(db) {
156285
+ db.exec(`
156286
+ CREATE TABLE IF NOT EXISTS tool_owner_backfill_state (
156287
+ session_id TEXT PRIMARY KEY,
156288
+ status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'completed', 'skipped')),
156289
+ started_at INTEGER,
156290
+ lease_expires_at INTEGER,
156291
+ completed_at INTEGER,
156292
+ last_error TEXT
156293
+ );
156294
+ CREATE INDEX IF NOT EXISTS idx_tool_owner_backfill_state_status
156295
+ ON tool_owner_backfill_state(status);
156296
+ `);
156297
+ }
156298
+ function runToolOwnerBackfill(db) {
156299
+ const startedAt = performance.now();
156300
+ ensureBackfillStateTable(db);
156301
+ const result = {
156302
+ sessionsProcessed: 0,
156303
+ sessionsSkippedNoOcDb: 0,
156304
+ sessionsSkippedNoMatches: 0,
156305
+ sessionsCompleted: 0,
156306
+ sessionsBlockedByLease: 0,
156307
+ sessionsErrored: 0,
156308
+ rowsUpdated: 0,
156309
+ rowsLeftNull: 0,
156310
+ durationMs: 0
156311
+ };
156312
+ if (!isToolOwnerBackfillNeeded(db)) {
156313
+ result.durationMs = performance.now() - startedAt;
156314
+ return result;
156315
+ }
156316
+ const opencodeDbPath = resolveOpencodeDbPath();
156317
+ if (!existsSync2(opencodeDbPath)) {
156318
+ log(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
156319
+ markAllUnbackfilledSessionsSkipped(db);
156320
+ result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
156321
+ result.durationMs = performance.now() - startedAt;
156322
+ return result;
156323
+ }
156324
+ db.exec(`ATTACH '${opencodeDbPath}' AS oc_backfill`);
156325
+ try {
156326
+ backfillToolOwnersInChunks(db, result);
156327
+ } finally {
156328
+ try {
156329
+ db.exec("DETACH DATABASE oc_backfill");
156330
+ } catch (error48) {
156331
+ log(`[backfill] failed to detach oc_backfill database: ${error48 instanceof Error ? error48.message : String(error48)}`);
156332
+ }
156333
+ }
156334
+ result.durationMs = performance.now() - startedAt;
156335
+ log(`[backfill] sessions=${result.sessionsProcessed} completed=${result.sessionsCompleted} skipped_no_oc=${result.sessionsSkippedNoOcDb} skipped_no_matches=${result.sessionsSkippedNoMatches} blocked_by_lease=${result.sessionsBlockedByLease} errored=${result.sessionsErrored} rows_updated=${result.rowsUpdated} rows_left_null=${result.rowsLeftNull} duration_ms=${Math.round(result.durationMs)}`);
156336
+ return result;
156337
+ }
156338
+ function isToolOwnerBackfillNeeded(db) {
156339
+ ensureBackfillStateTable(db);
156340
+ const row = db.prepare(`SELECT 1 AS hit
156341
+ FROM tags
156342
+ WHERE type = 'tool' AND tool_owner_message_id IS NULL
156343
+ AND NOT EXISTS (
156344
+ SELECT 1 FROM tool_owner_backfill_state s
156345
+ WHERE s.session_id = tags.session_id
156346
+ AND s.status IN ('completed', 'skipped')
156347
+ )
156348
+ LIMIT 1`).get();
156349
+ return row !== null && row !== undefined;
156350
+ }
156351
+ function markAllUnbackfilledSessionsSkipped(db) {
156352
+ const now = Date.now();
156353
+ db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, started_at, completed_at, last_error)
156354
+ SELECT DISTINCT session_id, 'skipped', NULL, ?, NULL
156355
+ FROM tags
156356
+ WHERE type = 'tool' AND tool_owner_message_id IS NULL
156357
+ ON CONFLICT(session_id) DO UPDATE SET
156358
+ status = 'skipped',
156359
+ completed_at = excluded.completed_at,
156360
+ last_error = NULL
156361
+ WHERE tool_owner_backfill_state.status NOT IN ('completed', 'running')`).run(now);
156362
+ }
156363
+ function countSessionsByStatus(db, status) {
156364
+ const row = db.prepare("SELECT COUNT(*) AS c FROM tool_owner_backfill_state WHERE status = ?").get(status);
156365
+ return row.c;
156366
+ }
156367
+ function acquireSessionLease(db, sessionId, now) {
156368
+ const expiresAt = now + LEASE_DURATION_MS;
156369
+ const result = db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, started_at, lease_expires_at)
156370
+ VALUES (?, 'running', ?, ?)
156371
+ ON CONFLICT(session_id) DO UPDATE SET
156372
+ status = 'running',
156373
+ started_at = excluded.started_at,
156374
+ lease_expires_at = excluded.lease_expires_at,
156375
+ last_error = NULL
156376
+ WHERE tool_owner_backfill_state.status IN ('pending', 'skipped')
156377
+ OR (tool_owner_backfill_state.status = 'running'
156378
+ AND tool_owner_backfill_state.lease_expires_at < ?)`).run(sessionId, now, expiresAt, now);
156379
+ return (result.changes ?? 0) === 1;
156380
+ }
156381
+ function renewSessionLease(db, sessionId, now) {
156382
+ const expiresAt = now + LEASE_DURATION_MS;
156383
+ db.prepare(`UPDATE tool_owner_backfill_state
156384
+ SET lease_expires_at = ?
156385
+ WHERE session_id = ? AND status = 'running'`).run(expiresAt, sessionId);
156386
+ }
156387
+ function markSessionCompleted(db, sessionId, now) {
156388
+ db.prepare(`UPDATE tool_owner_backfill_state
156389
+ SET status = 'completed', completed_at = ?, lease_expires_at = NULL, last_error = NULL
156390
+ WHERE session_id = ?`).run(now, sessionId);
156391
+ }
156392
+ function markSessionSkipped(db, sessionId, now, reason) {
156393
+ db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, completed_at, last_error)
156394
+ VALUES (?, 'skipped', ?, ?)
156395
+ ON CONFLICT(session_id) DO UPDATE SET
156396
+ status = 'skipped',
156397
+ completed_at = excluded.completed_at,
156398
+ last_error = excluded.last_error,
156399
+ lease_expires_at = NULL`).run(sessionId, now, reason);
156400
+ }
156401
+ function markSessionErrored(db, sessionId, error48) {
156402
+ const message = error48 instanceof Error ? error48.message : String(error48);
156403
+ db.prepare(`UPDATE tool_owner_backfill_state
156404
+ SET last_error = ?, lease_expires_at = NULL
156405
+ WHERE session_id = ?`).run(message, sessionId);
156406
+ }
156407
+ function getSessionsNeedingBackfill(db) {
156408
+ const rows = db.prepare(`SELECT DISTINCT t.session_id
156409
+ FROM tags t
156410
+ LEFT JOIN tool_owner_backfill_state s ON s.session_id = t.session_id
156411
+ WHERE t.type = 'tool' AND t.tool_owner_message_id IS NULL
156412
+ AND (s.status IS NULL OR s.status NOT IN ('completed', 'skipped'))
156413
+ ORDER BY t.session_id ASC`).all();
156414
+ return rows.map((r) => r.session_id);
156415
+ }
156416
+ function buildSessionOwnerMap(db, sessionId) {
156417
+ const rows = db.prepare(`SELECT
156418
+ COALESCE(
156419
+ CASE WHEN json_extract(p.data, '$.type') = 'tool_use'
156420
+ THEN json_extract(p.data, '$.id')
156421
+ END,
156422
+ json_extract(p.data, '$.callID')
156423
+ ) AS callid,
156424
+ m.id AS owner_id,
156425
+ m.time_created AS owner_t_created,
156426
+ p.id AS part_id,
156427
+ p.time_created AS part_t_created
156428
+ FROM oc_backfill.message m
156429
+ INNER JOIN oc_backfill.part p ON p.message_id = m.id
156430
+ WHERE m.session_id = ?
156431
+ AND json_extract(m.data, '$.role') = 'assistant'
156432
+ AND (
156433
+ (json_extract(p.data, '$.type') IN ('tool', 'tool-invocation')
156434
+ AND json_extract(p.data, '$.callID') IS NOT NULL)
156435
+ OR (json_extract(p.data, '$.type') = 'tool_use'
156436
+ AND json_extract(p.data, '$.id') IS NOT NULL)
156437
+ )
156438
+ ORDER BY
156439
+ m.time_created ASC,
156440
+ m.id ASC,
156441
+ p.time_created ASC,
156442
+ p.id ASC`).all(sessionId);
156443
+ const oldestByCallId = new Map;
156444
+ for (const r of rows) {
156445
+ if (typeof r.callid !== "string" || r.callid.length === 0)
156446
+ continue;
156447
+ if (!oldestByCallId.has(r.callid)) {
156448
+ oldestByCallId.set(r.callid, r.owner_id);
156449
+ }
156450
+ }
156451
+ return oldestByCallId;
156452
+ }
156453
+ function applyOwnersForSession(db, sessionId, ownersByCallId) {
156454
+ if (ownersByCallId.size === 0) {
156455
+ const leftNull = db.prepare(`SELECT COUNT(*) AS c FROM tags
156456
+ WHERE session_id = ? AND type = 'tool'
156457
+ AND tool_owner_message_id IS NULL`).get(sessionId).c;
156458
+ return { rowsUpdated: 0, rowsLeftNull: leftNull };
156459
+ }
156460
+ const findOrphanStmt = db.prepare(`SELECT id FROM tags
156461
+ WHERE session_id = ? AND message_id = ? AND type = 'tool'
156462
+ AND tool_owner_message_id IS NULL
156463
+ ORDER BY tag_number ASC
156464
+ LIMIT 1`);
156465
+ const updateRowStmt = db.prepare(`UPDATE tags
156466
+ SET tool_owner_message_id = ?
156467
+ WHERE id = ? AND tool_owner_message_id IS NULL`);
156468
+ let rowsUpdated = 0;
156469
+ db.transaction(() => {
156470
+ for (const [callId, ownerId] of ownersByCallId) {
156471
+ const orphan = findOrphanStmt.get(sessionId, callId);
156472
+ if (!orphan)
156473
+ continue;
156474
+ const result = updateRowStmt.run(ownerId, orphan.id);
156475
+ rowsUpdated += result.changes ?? 0;
156476
+ }
156477
+ })();
156478
+ const rowsLeftNull = db.prepare(`SELECT COUNT(*) AS c FROM tags
156479
+ WHERE session_id = ? AND type = 'tool'
156480
+ AND tool_owner_message_id IS NULL`).get(sessionId).c;
156481
+ return { rowsUpdated, rowsLeftNull };
156482
+ }
156483
+ function backfillToolOwnersInChunks(db, result) {
156484
+ const sessionIds = getSessionsNeedingBackfill(db);
156485
+ let lastRenewedAt = Date.now();
156486
+ for (const sessionId of sessionIds) {
156487
+ const now = Date.now();
156488
+ result.sessionsProcessed += 1;
156489
+ const acquired = acquireSessionLease(db, sessionId, now);
156490
+ if (!acquired) {
156491
+ result.sessionsBlockedByLease += 1;
156492
+ continue;
156493
+ }
156494
+ try {
156495
+ const owners = buildSessionOwnerMap(db, sessionId);
156496
+ const { rowsUpdated, rowsLeftNull } = applyOwnersForSession(db, sessionId, owners);
156497
+ result.rowsUpdated += rowsUpdated;
156498
+ result.rowsLeftNull += rowsLeftNull;
156499
+ if (owners.size === 0) {
156500
+ markSessionSkipped(db, sessionId, Date.now(), "no_oc_matches");
156501
+ result.sessionsSkippedNoMatches += 1;
156502
+ } else {
156503
+ markSessionCompleted(db, sessionId, Date.now());
156504
+ result.sessionsCompleted += 1;
156505
+ }
156506
+ } catch (error48) {
156507
+ log(`[backfill] session=${sessionId} errored: ${error48 instanceof Error ? error48.message : String(error48)}`);
156508
+ markSessionErrored(db, sessionId, error48);
156509
+ result.sessionsErrored += 1;
156510
+ }
156511
+ const sinceRenew = Date.now() - lastRenewedAt;
156512
+ if (sinceRenew > LEASE_RENEWAL_MS) {
156513
+ renewSessionLease(db, sessionId, Date.now());
156514
+ lastRenewedAt = Date.now();
156515
+ }
156516
+ }
156517
+ }
156518
+
156519
+ // ../plugin/src/features/magic-context/storage-db.ts
156520
+ var databases = new Map;
156521
+ var persistenceByDatabase = new WeakMap;
156522
+ var persistenceErrorByDatabase = new WeakMap;
156523
+ function resolveDatabasePath() {
156524
+ const dbDir = getMagicContextStorageDir();
156525
+ return { dbDir, dbPath: join6(dbDir, "context.db") };
156526
+ }
156527
+ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
156528
+ if (existsSync3(targetDbPath))
156529
+ return;
156530
+ const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
156531
+ const legacyDbPath = join6(legacyDir, "context.db");
156532
+ if (!existsSync3(legacyDbPath))
156533
+ return;
156534
+ log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
156535
+ mkdirSync3(targetDbDir, { recursive: true });
156536
+ for (const suffix of ["", "-wal", "-shm"]) {
156537
+ const src = `${legacyDbPath}${suffix}`;
156538
+ const dst = join6(targetDbDir, `context.db${suffix}`);
156539
+ if (existsSync3(src)) {
156540
+ try {
156541
+ copyFileSync(src, dst);
156542
+ } catch (error48) {
156543
+ log(`[magic-context] failed to copy ${src}:`, getErrorMessage(error48));
156544
+ }
156545
+ }
156546
+ }
156547
+ const legacyModelsDir = join6(legacyDir, "models");
156548
+ const targetModelsDir = join6(targetDbDir, "models");
156549
+ if (existsSync3(legacyModelsDir) && !existsSync3(targetModelsDir)) {
156550
+ try {
156551
+ cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
156552
+ } catch (error48) {
156553
+ log("[magic-context] failed to copy embedding model cache:", getErrorMessage(error48));
156554
+ }
156555
+ }
156556
+ }
156557
+ function initializeDatabase(db) {
156558
+ db.exec("PRAGMA journal_mode=WAL");
156559
+ db.exec("PRAGMA busy_timeout=5000");
156560
+ db.exec("PRAGMA foreign_keys=ON");
156561
+ db.exec(`
156562
+ CREATE TABLE IF NOT EXISTS tags (
156563
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156564
+ session_id TEXT,
156565
+ message_id TEXT,
156566
+ type TEXT,
156567
+ status TEXT DEFAULT 'active',
156568
+ byte_size INTEGER,
156569
+ tag_number INTEGER,
156570
+ harness TEXT NOT NULL DEFAULT 'opencode',
156571
+ UNIQUE(session_id, tag_number)
156572
+ );
156573
+
156574
+ CREATE TABLE IF NOT EXISTS pending_ops (
156575
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156576
+ session_id TEXT,
156577
+ tag_id INTEGER,
156578
+ operation TEXT,
156579
+ queued_at INTEGER,
156580
+ harness TEXT NOT NULL DEFAULT 'opencode'
156581
+ );
156582
+
156583
+ CREATE TABLE IF NOT EXISTS source_contents (
156584
+ tag_id INTEGER,
156585
+ session_id TEXT,
156586
+ content TEXT,
156587
+ created_at INTEGER,
156588
+ harness TEXT NOT NULL DEFAULT 'opencode',
156589
+ PRIMARY KEY(session_id, tag_id)
156590
+ );
156591
+
156592
+ CREATE TABLE IF NOT EXISTS compartments (
156593
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156594
+ session_id TEXT NOT NULL,
156595
+ sequence INTEGER NOT NULL,
156596
+ start_message INTEGER NOT NULL,
156597
+ end_message INTEGER NOT NULL,
156598
+ start_message_id TEXT DEFAULT '',
156599
+ end_message_id TEXT DEFAULT '',
156600
+ title TEXT NOT NULL,
156601
+ content TEXT NOT NULL,
156602
+ created_at INTEGER NOT NULL,
156603
+ harness TEXT NOT NULL DEFAULT 'opencode',
156604
+ UNIQUE(session_id, sequence)
156605
+ );
156606
+ CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
156607
+
156608
+ CREATE TABLE IF NOT EXISTS compression_depth (
156609
+ session_id TEXT NOT NULL,
156610
+ message_ordinal INTEGER NOT NULL,
156611
+ depth INTEGER NOT NULL DEFAULT 0,
156612
+ harness TEXT NOT NULL DEFAULT 'opencode',
156613
+ PRIMARY KEY(session_id, message_ordinal)
156614
+ );
156615
+ CREATE INDEX IF NOT EXISTS idx_compression_depth_session ON compression_depth(session_id);
156616
+
156617
+ CREATE TABLE IF NOT EXISTS session_facts (
156618
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156619
+ session_id TEXT NOT NULL,
156620
+ category TEXT NOT NULL,
156621
+ content TEXT NOT NULL,
156622
+ created_at INTEGER NOT NULL,
156623
+ updated_at INTEGER NOT NULL,
156624
+ harness TEXT NOT NULL DEFAULT 'opencode'
156625
+ );
156626
+
156627
+ -- session_notes and smart_notes were merged into the unified notes table
156628
+ -- by migration v1 (see features/magic-context/migrations.ts). The old tables
156629
+ -- are never recreated; fresh DBs create only notes, upgraded DBs have
156630
+ -- their old tables migrated and dropped by the migration runner.
156631
+
156632
+ CREATE TABLE IF NOT EXISTS memories (
156633
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156634
+ project_path TEXT NOT NULL,
156635
+ category TEXT NOT NULL,
156636
+ content TEXT NOT NULL,
156637
+ normalized_hash TEXT NOT NULL,
156638
+ source_session_id TEXT,
156639
+ source_type TEXT DEFAULT 'historian',
156640
+ seen_count INTEGER DEFAULT 1,
156641
+ retrieval_count INTEGER DEFAULT 0,
156642
+ first_seen_at INTEGER NOT NULL,
156643
+ created_at INTEGER NOT NULL,
156644
+ updated_at INTEGER NOT NULL,
156645
+ last_seen_at INTEGER NOT NULL,
156646
+ last_retrieved_at INTEGER,
156647
+ status TEXT DEFAULT 'active',
156648
+ expires_at INTEGER,
156649
+ verification_status TEXT DEFAULT 'unverified',
156650
+ verified_at INTEGER,
156651
+ superseded_by_memory_id INTEGER,
156652
+ merged_from TEXT,
156653
+ metadata_json TEXT,
156654
+ UNIQUE(project_path, category, normalized_hash)
156655
+ );
156656
+
156657
+ CREATE TABLE IF NOT EXISTS memory_embeddings (
156658
+ memory_id INTEGER PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
156659
+ embedding BLOB NOT NULL,
156660
+ model_id TEXT
156661
+ );
156662
+
156663
+ CREATE TABLE IF NOT EXISTS dream_state (
156664
+ key TEXT PRIMARY KEY,
156665
+ value TEXT NOT NULL
156666
+ );
156667
+
156668
+ CREATE TABLE IF NOT EXISTS dream_queue (
156669
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156670
+ project_path TEXT NOT NULL,
156671
+ reason TEXT NOT NULL,
156672
+ enqueued_at INTEGER NOT NULL,
156673
+ started_at INTEGER,
156674
+ retry_count INTEGER DEFAULT 0
156675
+ );
156676
+ CREATE INDEX IF NOT EXISTS idx_dream_queue_project ON dream_queue(project_path);
156677
+ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, enqueued_at);
156678
+
156679
+ CREATE TABLE IF NOT EXISTS dream_runs (
156680
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156681
+ project_path TEXT NOT NULL,
156682
+ started_at INTEGER NOT NULL,
156683
+ finished_at INTEGER NOT NULL,
156684
+ holder_id TEXT NOT NULL,
156685
+ tasks_json TEXT NOT NULL,
156686
+ tasks_succeeded INTEGER NOT NULL DEFAULT 0,
156687
+ tasks_failed INTEGER NOT NULL DEFAULT 0,
156688
+ smart_notes_surfaced INTEGER NOT NULL DEFAULT 0,
156689
+ smart_notes_pending INTEGER NOT NULL DEFAULT 0,
156690
+ memory_changes_json TEXT
156691
+ );
156692
+ CREATE INDEX IF NOT EXISTS idx_dream_runs_project ON dream_runs(project_path, finished_at DESC);
156693
+
156694
+ -- (smart_notes: see note above; merged into unified notes table by migration v1)
156695
+
156696
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
156697
+ content,
156698
+ category,
156699
+ content='memories',
156700
+ content_rowid='id',
156701
+ tokenize='porter unicode61'
156702
+ );
156703
+
156704
+ CREATE VIRTUAL TABLE IF NOT EXISTS message_history_fts USING fts5(
156705
+ session_id UNINDEXED,
156706
+ message_ordinal UNINDEXED,
156707
+ message_id UNINDEXED,
156708
+ role,
156709
+ content,
156710
+ tokenize='porter unicode61'
156711
+ );
156712
+
156713
+ CREATE TABLE IF NOT EXISTS message_history_index (
156714
+ session_id TEXT PRIMARY KEY,
156715
+ last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
156716
+ updated_at INTEGER NOT NULL,
156717
+ harness TEXT NOT NULL DEFAULT 'opencode'
156718
+ );
156719
+
156720
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
156721
+ INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
156722
+ END;
156723
+
156724
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
156725
+ INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
156726
+ END;
156727
+
156728
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
156729
+ INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
156730
+ INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
156731
+ END;
156732
+
156733
+ CREATE TABLE IF NOT EXISTS session_meta (
156734
+ session_id TEXT PRIMARY KEY,
156735
+ harness TEXT NOT NULL DEFAULT 'opencode',
156736
+ last_response_time INTEGER,
156737
+ cache_ttl TEXT,
156738
+ counter INTEGER DEFAULT 0,
156739
+ last_nudge_tokens INTEGER DEFAULT 0,
156740
+ last_nudge_band TEXT DEFAULT '',
156741
+ last_transform_error TEXT DEFAULT '',
156742
+ nudge_anchor_message_id TEXT DEFAULT '',
156743
+ nudge_anchor_text TEXT DEFAULT '',
156744
+ sticky_turn_reminder_text TEXT DEFAULT '',
156745
+ sticky_turn_reminder_message_id TEXT DEFAULT '',
156746
+ note_nudge_trigger_pending INTEGER DEFAULT 0,
156747
+ note_nudge_trigger_message_id TEXT DEFAULT '',
156748
+ note_nudge_sticky_text TEXT DEFAULT '',
156749
+ note_nudge_sticky_message_id TEXT DEFAULT '',
156750
+ is_subagent INTEGER DEFAULT 0,
156751
+ last_context_percentage REAL DEFAULT 0,
156752
+ last_input_tokens INTEGER DEFAULT 0,
156753
+ times_execute_threshold_reached INTEGER DEFAULT 0,
156754
+ compartment_in_progress INTEGER DEFAULT 0,
156755
+ historian_failure_count INTEGER DEFAULT 0,
156756
+ historian_last_error TEXT DEFAULT NULL,
156757
+ historian_last_failure_at INTEGER DEFAULT NULL,
156758
+ system_prompt_hash TEXT DEFAULT '',
156759
+ memory_block_cache TEXT DEFAULT '',
156760
+ memory_block_count INTEGER DEFAULT 0,
156761
+ memory_block_ids TEXT DEFAULT ''
156762
+ );
156763
+
156764
+ CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
156765
+ CREATE INDEX IF NOT EXISTS idx_tags_session_message_id ON tags(session_id, message_id);
156766
+ CREATE INDEX IF NOT EXISTS idx_pending_ops_session ON pending_ops(session_id);
156767
+ CREATE INDEX IF NOT EXISTS idx_pending_ops_session_tag_id ON pending_ops(session_id, tag_id);
156768
+ CREATE INDEX IF NOT EXISTS idx_source_contents_session ON source_contents(session_id);
156769
+
156770
+ CREATE TABLE IF NOT EXISTS recomp_compartments (
156771
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156772
+ session_id TEXT NOT NULL,
156773
+ sequence INTEGER NOT NULL,
156774
+ start_message INTEGER NOT NULL,
156775
+ end_message INTEGER NOT NULL,
156776
+ start_message_id TEXT DEFAULT '',
156777
+ end_message_id TEXT DEFAULT '',
156778
+ title TEXT NOT NULL,
156779
+ content TEXT NOT NULL,
156780
+ pass_number INTEGER NOT NULL,
156781
+ created_at INTEGER NOT NULL,
156782
+ harness TEXT NOT NULL DEFAULT 'opencode',
156783
+ UNIQUE(session_id, sequence)
156784
+ );
156785
+
156786
+ CREATE TABLE IF NOT EXISTS recomp_facts (
156787
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156788
+ session_id TEXT NOT NULL,
156789
+ category TEXT NOT NULL,
156790
+ content TEXT NOT NULL,
156791
+ pass_number INTEGER NOT NULL,
156792
+ created_at INTEGER NOT NULL,
156793
+ harness TEXT NOT NULL DEFAULT 'opencode'
156794
+ );
156795
+
156796
+ CREATE INDEX IF NOT EXISTS idx_session_facts_session ON session_facts(session_id);
156797
+ CREATE INDEX IF NOT EXISTS idx_recomp_compartments_session ON recomp_compartments(session_id);
156798
+ CREATE INDEX IF NOT EXISTS idx_recomp_facts_session ON recomp_facts(session_id);
156799
+ CREATE INDEX IF NOT EXISTS idx_memories_project_status_category ON memories(project_path, status, category);
156800
+ CREATE INDEX IF NOT EXISTS idx_memories_project_status_expires ON memories(project_path, status, expires_at);
156801
+ CREATE INDEX IF NOT EXISTS idx_memories_project_category_hash ON memories(project_path, category, normalized_hash);
156802
+ CREATE INDEX IF NOT EXISTS idx_message_history_index_updated_at ON message_history_index(updated_at);
156803
+ `);
156804
+ ensureColumn(db, "session_meta", "last_nudge_band", "TEXT DEFAULT ''");
156805
+ ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
156806
+ ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
156807
+ ensureColumn(db, "session_meta", "nudge_anchor_text", "TEXT DEFAULT ''");
156808
+ ensureColumn(db, "session_meta", "sticky_turn_reminder_text", "TEXT DEFAULT ''");
156809
+ ensureColumn(db, "session_meta", "sticky_turn_reminder_message_id", "TEXT DEFAULT ''");
156810
+ ensureColumn(db, "session_meta", "note_nudge_trigger_pending", "INTEGER DEFAULT 0");
156811
+ ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
156812
+ ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
156813
+ ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
156814
+ ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
156815
+ ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
156816
+ ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
156817
+ ensureColumn(db, "session_meta", "historian_failure_count", "INTEGER DEFAULT 0");
156818
+ ensureColumn(db, "session_meta", "historian_last_error", "TEXT DEFAULT NULL");
156819
+ ensureColumn(db, "session_meta", "historian_last_failure_at", "INTEGER DEFAULT NULL");
156820
+ ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
156821
+ ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
156822
+ ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
156823
+ ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
156824
+ ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
156825
+ ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
156826
+ ensureColumn(db, "session_meta", "memory_block_cache", "TEXT DEFAULT ''");
156827
+ ensureColumn(db, "session_meta", "memory_block_count", "INTEGER DEFAULT 0");
156828
+ ensureColumn(db, "session_meta", "memory_block_ids", "TEXT DEFAULT ''");
156829
+ ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
156830
+ ensureColumn(db, "tags", "reasoning_byte_size", "INTEGER DEFAULT 0");
156831
+ ensureColumn(db, "tags", "drop_mode", "TEXT DEFAULT 'full'");
156832
+ ensureColumn(db, "tags", "tool_name", "TEXT");
156833
+ ensureColumn(db, "tags", "input_byte_size", "INTEGER DEFAULT 0");
156834
+ ensureColumn(db, "tags", "caveman_depth", "INTEGER DEFAULT 0");
156835
+ ensureColumn(db, "tags", "tool_owner_message_id", "TEXT DEFAULT NULL");
156836
+ ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
156837
+ ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
156838
+ ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
156839
+ ensureColumn(db, "session_meta", "conversation_tokens", "INTEGER DEFAULT 0");
156840
+ ensureColumn(db, "session_meta", "tool_call_tokens", "INTEGER DEFAULT 0");
156841
+ ensureColumn(db, "session_meta", "recomp_partial_range_start", "INTEGER DEFAULT 0");
156842
+ ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
156843
+ ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
156844
+ ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
156845
+ ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
156846
+ ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
156847
+ ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
156848
+ ensureColumn(db, "compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
156849
+ ensureColumn(db, "compression_depth", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
156850
+ ensureColumn(db, "session_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
156851
+ ensureColumn(db, "session_meta", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
156852
+ ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
156853
+ ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
156854
+ ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
156855
+ }
156856
+ function healAllNullColumns(db) {
156857
+ healNullTextColumns(db);
156858
+ healNullIntegerColumns(db);
156859
+ healMissingMemoryBlockIds(db);
156860
+ }
156861
+ function healMissingMemoryBlockIds(db) {
156862
+ try {
156863
+ 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();
156864
+ } catch {}
156865
+ }
156866
+ function healNullTextColumns(db) {
156867
+ const columns = [
156868
+ ["cache_ttl", ""],
156869
+ ["last_nudge_band", ""],
156870
+ ["last_transform_error", ""],
156871
+ ["nudge_anchor_message_id", ""],
156872
+ ["nudge_anchor_text", ""],
156873
+ ["sticky_turn_reminder_text", ""],
156874
+ ["sticky_turn_reminder_message_id", ""],
156875
+ ["note_nudge_trigger_message_id", ""],
156876
+ ["note_nudge_sticky_text", ""],
156877
+ ["note_nudge_sticky_message_id", ""],
156878
+ ["system_prompt_hash", ""],
156879
+ ["stripped_placeholder_ids", ""],
156880
+ ["memory_block_cache", ""],
156881
+ ["memory_block_ids", ""],
156882
+ ["compaction_marker_state", ""],
156883
+ ["key_files", ""]
156884
+ ];
156885
+ for (const [column, fallback] of columns) {
156886
+ try {
156887
+ db.prepare(`UPDATE session_meta SET ${column} = ? WHERE ${column} IS NULL`).run(fallback);
156888
+ } catch (_error) {}
156889
+ }
156890
+ }
156891
+ function healNullIntegerColumns(db) {
156892
+ const columns = [
156893
+ ["times_execute_threshold_reached", 0],
156894
+ ["compartment_in_progress", 0],
156895
+ ["historian_failure_count", 0],
156896
+ ["cleared_reasoning_through_tag", 0],
156897
+ ["memory_block_count", 0],
156898
+ ["system_prompt_tokens", 0],
156899
+ ["conversation_tokens", 0],
156900
+ ["tool_call_tokens", 0],
156901
+ ["note_nudge_trigger_pending", 0]
156902
+ ];
156903
+ for (const [column, fallback] of columns) {
156904
+ try {
156905
+ db.prepare(`UPDATE session_meta SET ${column} = ? WHERE ${column} IS NULL`).run(fallback);
156906
+ } catch (_error) {}
156907
+ }
156908
+ }
156909
+ function ensureColumn(db, table, column, definition) {
156910
+ if (!/^[a-z_]+$/.test(table) || !/^[a-z_]+$/.test(column) || !/^[A-Z0-9_'(),\s]+$/i.test(definition)) {
156911
+ throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
156912
+ }
156913
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
156914
+ if (rows.some((row) => row.name === column)) {
156915
+ return;
156916
+ }
156917
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
156918
+ }
156919
+ function openDatabase() {
156920
+ const { dbDir, dbPath } = resolveDatabasePath();
156921
+ const existing = databases.get(dbPath);
156922
+ if (existing) {
156923
+ if (!persistenceByDatabase.has(existing)) {
156924
+ persistenceByDatabase.set(existing, true);
156925
+ }
156926
+ return existing;
156927
+ }
156928
+ try {
156929
+ migrateLegacyStorageIfNeeded(dbPath, dbDir);
156930
+ mkdirSync3(dbDir, { recursive: true });
156931
+ const db = new Database(dbPath);
156932
+ initializeDatabase(db);
156933
+ runMigrations(db);
156934
+ try {
156935
+ runToolOwnerBackfill(db);
156936
+ } catch (error48) {
156937
+ log(`[magic-context] tool-owner backfill failed (continuing with lazy adoption fallback): ${getErrorMessage(error48)}`);
156938
+ }
156939
+ setDatabase(db);
156940
+ loadToolDefinitionMeasurements(db);
156941
+ databases.set(dbPath, db);
156942
+ persistenceByDatabase.set(db, true);
156943
+ persistenceErrorByDatabase.delete(db);
156944
+ return db;
156945
+ } catch (error48) {
156946
+ const detail = getErrorMessage(error48);
156947
+ log(`[magic-context] storage fatal: failed to open ${dbPath}: ${detail}`);
156948
+ throw new Error(`[magic-context] storage unavailable: ${detail}. Magic Context is disabled for this run; check log for details.`);
156949
+ }
156950
+ }
156951
+
156952
+ // ../plugin/src/shared/harness.ts
156953
+ var currentHarness = "opencode";
156954
+ var harnessLocked = false;
156955
+ function setHarness(value) {
156956
+ if (harnessLocked && currentHarness !== value) {
156957
+ throw new Error(`Magic Context: harness already locked to "${currentHarness}"; cannot change to "${value}"`);
156958
+ }
156959
+ currentHarness = value;
156960
+ harnessLocked = true;
156961
+ }
156962
+ function getHarness() {
156963
+ return currentHarness;
156964
+ }
156965
+
156966
+ // src/subagent-entry.ts
156967
+ init_logger();
156968
+
156969
+ // src/config/index.ts
156970
+ init_magic_context();
156971
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
156972
+ import { homedir as homedir3 } from "node:os";
156973
+ import { join as join7 } from "node:path";
156974
+
156975
+ // ../plugin/src/config/variable.ts
156976
+ import { existsSync as existsSync4, readFileSync } from "node:fs";
156977
+ import { homedir as homedir2 } from "node:os";
156978
+ import { dirname as dirname2, isAbsolute, resolve } from "node:path";
156979
+ var ENV_PATTERN = /\{env:([^}]+)\}/g;
156980
+ var FILE_PATTERN = /\{file:([^}]+)\}/g;
156981
+ function substituteConfigVariables(input) {
156982
+ const warnings = [];
156983
+ let text = input.text;
156984
+ text = text.replace(ENV_PATTERN, (_, rawName) => {
156985
+ const varName = rawName.trim();
156986
+ const value = varName ? process.env[varName] : undefined;
156987
+ if (value === undefined || value === "") {
156988
+ warnings.push(`Environment variable ${varName} is not set (referenced via {env:${varName}}); using empty string`);
156989
+ return "";
156990
+ }
156991
+ return value;
156992
+ });
156993
+ const fileMatches = Array.from(text.matchAll(FILE_PATTERN));
156994
+ if (fileMatches.length === 0) {
156995
+ return { text, warnings };
156996
+ }
156997
+ const configDir = input.configPath ? dirname2(input.configPath) : process.cwd();
156998
+ let output = "";
156999
+ let cursor = 0;
157000
+ for (const match of fileMatches) {
157001
+ const token = match[0];
157002
+ const rawPath = match[1] ?? "";
157003
+ const index = match.index ?? 0;
157004
+ output += text.slice(cursor, index);
157005
+ cursor = index + token.length;
157006
+ const lineStart = text.lastIndexOf(`
157007
+ `, index - 1) + 1;
157008
+ const prefix = text.slice(lineStart, index).trimStart();
157009
+ if (prefix.startsWith("//")) {
157010
+ output += token;
157011
+ continue;
157012
+ }
157013
+ let filePath = rawPath.trim();
157014
+ if (filePath.startsWith("~/")) {
157015
+ filePath = resolve(homedir2(), filePath.slice(2));
157016
+ } else if (!isAbsolute(filePath)) {
157017
+ filePath = resolve(configDir, filePath);
157018
+ }
157019
+ if (!existsSync4(filePath)) {
157020
+ warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
157021
+ continue;
157022
+ }
157023
+ let contents;
157024
+ try {
157025
+ contents = readFileSync(filePath, "utf-8").trim();
157026
+ } catch (error48) {
157027
+ const message = error48 instanceof Error ? error48.message : String(error48);
157028
+ warnings.push(`Failed to read file for ${token} (${filePath}): ${message}; using empty string`);
157029
+ continue;
157030
+ }
157031
+ output += JSON.stringify(contents).slice(1, -1);
157032
+ }
157033
+ output += text.slice(cursor);
157034
+ return { text: output, warnings };
157035
+ }
157036
+
157037
+ // src/config/index.ts
157038
+ var import_comment_json = __toESM(require_src2(), 1);
157039
+ var CONFIG_FILE_NAME = "magic-context";
157040
+ function getProjectConfigPaths(cwd) {
157041
+ const basePath = join7(cwd, ".pi", CONFIG_FILE_NAME);
157042
+ return [`${basePath}.jsonc`, `${basePath}.json`];
157043
+ }
157044
+ function getUserConfigPaths() {
157045
+ const home = process.env.HOME ?? homedir3();
157046
+ const basePath = join7(home, ".pi", "agent", CONFIG_FILE_NAME);
157047
+ return [`${basePath}.jsonc`, `${basePath}.json`];
157048
+ }
157049
+ function resolveFirstExisting(paths) {
157050
+ return paths.find((path4) => existsSync5(path4));
157051
+ }
157052
+ function loadConfigFile(path4, scope) {
157053
+ try {
157054
+ const rawText = readFileSync2(path4, "utf-8");
157055
+ const substituted = substituteConfigVariables({
157056
+ text: rawText,
157057
+ configPath: path4
157058
+ });
157059
+ return {
157060
+ path: path4,
157061
+ scope,
157062
+ config: import_comment_json.parse(substituted.text),
157063
+ warnings: substituted.warnings.map((warning) => `${path4}: ${warning}`)
157064
+ };
157065
+ } catch (error48) {
157066
+ const message = error48 instanceof Error ? error48.message : String(error48);
157067
+ return {
157068
+ path: path4,
157069
+ scope,
157070
+ config: {},
157071
+ warnings: [
157072
+ `${path4}: failed to load config: ${message}; using defaults for this file.`
157073
+ ]
157074
+ };
157075
+ }
157076
+ }
157077
+ function redactConfigValue(value) {
157078
+ if (value === undefined)
157079
+ return "<missing>";
157080
+ if (value === null)
157081
+ return "null";
157082
+ if (typeof value === "string") {
157083
+ return `string, ${value.length} char${value.length === 1 ? "" : "s"}`;
157084
+ }
157085
+ if (typeof value === "number")
157086
+ return `number ${value}`;
157087
+ if (typeof value === "boolean")
157088
+ return `boolean ${value}`;
157089
+ if (Array.isArray(value))
157090
+ return `array, ${value.length} item${value.length === 1 ? "" : "s"}`;
157091
+ if (typeof value === "object") {
157092
+ const keys = Object.keys(value);
157093
+ return `object with keys [${keys.join(", ")}]`;
157094
+ }
157095
+ return typeof value;
157096
+ }
157097
+ function isPlainObject2(value) {
157098
+ return typeof value === "object" && value !== null && !Array.isArray(value);
157099
+ }
157100
+ function mergeRawConfigs(base, override) {
157101
+ const merged = { ...base };
157102
+ for (const [key, overrideValue] of Object.entries(override)) {
157103
+ const baseValue = merged[key];
157104
+ merged[key] = isPlainObject2(baseValue) && isPlainObject2(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
157105
+ }
157106
+ return merged;
157107
+ }
157108
+ function parsePiConfig(rawConfig) {
157109
+ const parsed = MagicContextConfigSchema.safeParse(rawConfig);
157110
+ if (parsed.success) {
157111
+ return { config: parsed.data, warnings: [] };
157112
+ }
157113
+ const defaults = MagicContextConfigSchema.parse({});
157114
+ const errorPaths = new Set;
157115
+ for (const issue2 of parsed.error.issues) {
157116
+ const topKey = issue2.path[0];
157117
+ if (topKey !== undefined) {
157118
+ errorPaths.add(String(topKey));
157119
+ }
157120
+ }
157121
+ const patched = { ...rawConfig };
157122
+ const warnings = [];
157123
+ for (const key of errorPaths) {
157124
+ const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
157125
+ delete patched[key];
157126
+ if (isAgentConfig) {
157127
+ warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
157128
+ continue;
157129
+ }
157130
+ const defaultValue = defaults[key];
157131
+ warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultValue)}.`);
157132
+ }
157133
+ const retryParsed = MagicContextConfigSchema.safeParse(patched);
157134
+ if (retryParsed.success) {
157135
+ return { config: retryParsed.data, warnings };
157136
+ }
157137
+ warnings.push("Config recovery failed, using all defaults.");
157138
+ return { config: defaults, warnings };
157139
+ }
157140
+ function loadPiConfig(opts = {}) {
157141
+ const cwd = opts.cwd ?? process.cwd();
157142
+ const loadedFiles = [];
157143
+ const warnings = [];
157144
+ const projectPath = resolveFirstExisting(getProjectConfigPaths(cwd));
157145
+ if (projectPath) {
157146
+ const loaded = loadConfigFile(projectPath, "project");
157147
+ if (loaded)
157148
+ loadedFiles.push(loaded);
157149
+ }
157150
+ const userPath = resolveFirstExisting(getUserConfigPaths());
157151
+ if (userPath) {
157152
+ const loaded = loadConfigFile(userPath, "user");
157153
+ if (loaded)
157154
+ loadedFiles.push(loaded);
157155
+ }
157156
+ let rawConfig = {};
157157
+ const mergeFiles = [...loadedFiles].sort((a, b) => {
157158
+ if (a.scope === b.scope)
157159
+ return 0;
157160
+ return a.scope === "user" ? -1 : 1;
157161
+ });
157162
+ for (const loaded of mergeFiles) {
157163
+ const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
157164
+ warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
157165
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157166
+ }
157167
+ const parsed = parsePiConfig(rawConfig);
157168
+ warnings.push(...parsed.warnings.map((warning) => `[merged config] ${warning}`));
157169
+ return {
157170
+ config: parsed.config,
157171
+ warnings,
157172
+ loadedFromPaths: loadedFiles.map((loaded) => loaded.path)
157173
+ };
157174
+ }
157175
+
157176
+ // ../plugin/src/hooks/magic-context/read-session-chunk.ts
157177
+ await init_read_session_db();
157178
+
156811
157179
  // ../plugin/src/hooks/magic-context/read-session-raw.ts
156812
157180
  function isRawMessageRow(row) {
156813
157181
  if (row === null || typeof row !== "object")
@@ -156890,21 +157258,85 @@ function isTextPart(part) {
156890
157258
  const p = part;
156891
157259
  return p.type === "text" && typeof p.text === "string";
156892
157260
  }
156893
- function isToolPartWithOutput(part) {
157261
+ function isFilePart(part) {
156894
157262
  if (part === null || typeof part !== "object")
156895
157263
  return false;
156896
157264
  const p = part;
156897
- if (p.type !== "tool" || typeof p.callID !== "string")
157265
+ return p.type === "file" && typeof p.url === "string";
157266
+ }
157267
+
157268
+ // ../plugin/src/shared/record-type-guard.ts
157269
+ function isRecord(value) {
157270
+ return typeof value === "object" && value !== null && !Array.isArray(value);
157271
+ }
157272
+
157273
+ // ../plugin/src/hooks/magic-context/tool-drop-target.ts
157274
+ var IGNORE_PART_TYPES = new Set([
157275
+ "thinking",
157276
+ "reasoning",
157277
+ "redacted_thinking",
157278
+ "meta",
157279
+ "step-start",
157280
+ "step-finish"
157281
+ ]);
157282
+ function isToolCallId(value) {
157283
+ return typeof value === "string" && value.length > 0;
157284
+ }
157285
+ function hasMeaningfulPart(part) {
157286
+ if (!isRecord(part))
156898
157287
  return false;
156899
- if (p.state === null || typeof p.state !== "object")
157288
+ const type = part.type;
157289
+ if (type === "text") {
157290
+ return typeof part.text === "string" && part.text.trim().length > 0;
157291
+ }
157292
+ if (typeof type !== "string")
156900
157293
  return false;
156901
- return typeof p.state.output === "string";
156902
- }
156903
- function isFilePart(part) {
156904
- if (part === null || typeof part !== "object")
157294
+ if (IGNORE_PART_TYPES.has(type))
156905
157295
  return false;
156906
- const p = part;
156907
- return p.type === "file" && typeof p.url === "string";
157296
+ return true;
157297
+ }
157298
+ function extractToolCallObservation(part) {
157299
+ if (!isRecord(part))
157300
+ return null;
157301
+ if (part.type === "tool" && isToolCallId(part.callID)) {
157302
+ return { callId: part.callID, kind: "result" };
157303
+ }
157304
+ if (part.type === "tool-invocation" && isToolCallId(part.callID)) {
157305
+ return { callId: part.callID, kind: "invocation" };
157306
+ }
157307
+ if (part.type === "tool_use" && isToolCallId(part.id)) {
157308
+ return { callId: part.id, kind: "invocation" };
157309
+ }
157310
+ if (part.type === "tool_result" && isToolCallId(part.tool_use_id)) {
157311
+ return { callId: part.tool_use_id, kind: "result" };
157312
+ }
157313
+ return null;
157314
+ }
157315
+ class ToolMutationBatch {
157316
+ partsToRemove = new Set;
157317
+ affectedMessages = new Set;
157318
+ messages;
157319
+ constructor(messages) {
157320
+ this.messages = messages;
157321
+ }
157322
+ markForRemoval(occurrence) {
157323
+ this.partsToRemove.add(occurrence.part);
157324
+ this.affectedMessages.add(occurrence.message);
157325
+ }
157326
+ finalize() {
157327
+ if (this.partsToRemove.size === 0)
157328
+ return;
157329
+ for (const message of this.affectedMessages) {
157330
+ message.parts = message.parts.filter((p) => !this.partsToRemove.has(p));
157331
+ }
157332
+ for (let i = this.messages.length - 1;i >= 0; i -= 1) {
157333
+ if (!this.messages[i].parts.some(hasMeaningfulPart)) {
157334
+ this.messages.splice(i, 1);
157335
+ }
157336
+ }
157337
+ this.partsToRemove.clear();
157338
+ this.affectedMessages.clear();
157339
+ }
156908
157340
  }
156909
157341
 
156910
157342
  // ../plugin/src/hooks/magic-context/read-session-chunk.ts
@@ -156971,23 +157403,47 @@ function getRawSessionMessageCount(sessionId) {
156971
157403
  }
156972
157404
  function getRawSessionTagKeysThrough(sessionId, upToMessageIndex) {
156973
157405
  const messages = readRawSessionMessages(sessionId);
156974
- const keys = [];
157406
+ const messageFileKeys = new Set;
157407
+ const toolObservations = new Map;
157408
+ const unpairedInvocations = new Map;
156975
157409
  for (const message of messages) {
156976
157410
  if (message.ordinal > upToMessageIndex)
156977
157411
  break;
156978
157412
  for (const [partIndex, part] of message.parts.entries()) {
156979
157413
  if (isTextPart(part)) {
156980
- keys.push(`${message.id}:p${partIndex}`);
157414
+ messageFileKeys.add(`${message.id}:p${partIndex}`);
157415
+ continue;
156981
157416
  }
156982
157417
  if (isFilePart(part)) {
156983
- keys.push(`${message.id}:file${partIndex}`);
157418
+ messageFileKeys.add(`${message.id}:file${partIndex}`);
157419
+ continue;
156984
157420
  }
156985
- if (isToolPartWithOutput(part)) {
156986
- keys.push(part.callID);
157421
+ const obs = extractToolCallObservation(part);
157422
+ if (!obs)
157423
+ continue;
157424
+ let ownerMsgId;
157425
+ if (obs.kind === "invocation") {
157426
+ ownerMsgId = message.id;
157427
+ const queue = unpairedInvocations.get(obs.callId) ?? [];
157428
+ queue.push(message.id);
157429
+ unpairedInvocations.set(obs.callId, queue);
157430
+ } else {
157431
+ const queue = unpairedInvocations.get(obs.callId);
157432
+ if (queue && queue.length > 0) {
157433
+ const popped = queue.shift();
157434
+ if (queue.length === 0)
157435
+ unpairedInvocations.delete(obs.callId);
157436
+ ownerMsgId = popped ?? message.id;
157437
+ } else {
157438
+ ownerMsgId = message.id;
157439
+ }
156987
157440
  }
157441
+ const owners = toolObservations.get(obs.callId) ?? new Set;
157442
+ owners.add(ownerMsgId);
157443
+ toolObservations.set(obs.callId, owners);
156988
157444
  }
156989
157445
  }
156990
- return keys;
157446
+ return { messageFileKeys, toolObservations };
156991
157447
  }
156992
157448
  var PROTECTED_TAIL_USER_TURNS = 5;
156993
157449
  function getProtectedTailStartOrdinal(sessionId) {
@@ -162624,6 +163080,7 @@ var upsertIndexStatements = new WeakMap;
162624
163080
  var deleteFtsStatements = new WeakMap;
162625
163081
  var deleteIndexStatements = new WeakMap;
162626
163082
  var countIndexedMessageStatements = new WeakMap;
163083
+ var indexedMessageIdStatements = new WeakMap;
162627
163084
  function normalizeIndexText(text) {
162628
163085
  return text.replace(/\s+/g, " ").trim();
162629
163086
  }
@@ -162667,10 +163124,34 @@ function getDeleteIndexStatement(db) {
162667
163124
  }
162668
163125
  return stmt;
162669
163126
  }
163127
+ function getCountIndexedMessageStatement(db) {
163128
+ let stmt = countIndexedMessageStatements.get(db);
163129
+ if (!stmt) {
163130
+ stmt = db.prepare("SELECT COUNT(*) AS count FROM message_history_fts WHERE session_id = ? AND message_id = ?");
163131
+ countIndexedMessageStatements.set(db, stmt);
163132
+ }
163133
+ return stmt;
163134
+ }
163135
+ function getIndexedMessageIdStatement(db) {
163136
+ let stmt = indexedMessageIdStatements.get(db);
163137
+ if (!stmt) {
163138
+ stmt = db.prepare("SELECT message_id AS messageId FROM message_history_fts WHERE session_id = ?");
163139
+ indexedMessageIdStatements.set(db, stmt);
163140
+ }
163141
+ return stmt;
163142
+ }
162670
163143
  function getLastIndexedOrdinal(db, sessionId) {
162671
163144
  const row = getLastIndexedStatement(db).get(sessionId);
162672
163145
  return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
162673
163146
  }
163147
+ function isMessageAlreadyIndexed(db, sessionId, messageId) {
163148
+ const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
163149
+ return (typeof row?.count === "number" ? row.count : 0) > 0;
163150
+ }
163151
+ function advanceIndexWatermark(db, sessionId, ordinal, now) {
163152
+ const current = getLastIndexedOrdinal(db, sessionId);
163153
+ getUpsertIndexStatement(db).run(sessionId, Math.max(current, ordinal), now, getHarness());
163154
+ }
162674
163155
  function clearIndexedMessages(db, sessionId) {
162675
163156
  db.transaction(() => {
162676
163157
  getDeleteFtsStatement(db).run(sessionId);
@@ -162690,34 +163171,51 @@ function getIndexableContent(role, parts) {
162690
163171
  }
162691
163172
  return "";
162692
163173
  }
162693
- function ensureMessagesIndexed(db, sessionId, readMessages) {
162694
- const messages = readMessages(sessionId);
162695
- if (messages.length === 0) {
162696
- db.transaction(() => clearIndexedMessages(db, sessionId))();
162697
- return;
163174
+ function indexSingleMessageInTransaction(db, sessionId, message, now) {
163175
+ if (message.role !== "user" && message.role !== "assistant") {
163176
+ advanceIndexWatermark(db, sessionId, message.ordinal, now);
163177
+ return false;
162698
163178
  }
162699
- let lastIndexedOrdinal = getLastIndexedOrdinal(db, sessionId);
162700
- if (lastIndexedOrdinal > messages.length) {
162701
- db.transaction(() => clearIndexedMessages(db, sessionId))();
162702
- lastIndexedOrdinal = 0;
163179
+ const content = getIndexableContent(message.role, message.parts);
163180
+ if (content.length === 0) {
163181
+ advanceIndexWatermark(db, sessionId, message.ordinal, now);
163182
+ return false;
162703
163183
  }
162704
- if (lastIndexedOrdinal >= messages.length) {
162705
- return;
163184
+ if (isMessageAlreadyIndexed(db, sessionId, message.id)) {
163185
+ advanceIndexWatermark(db, sessionId, message.ordinal, now);
163186
+ return false;
162706
163187
  }
162707
- const messagesToInsert = messages.filter((message) => message.ordinal > lastIndexedOrdinal).filter((message) => message.role === "user" || message.role === "assistant").map((message) => ({
162708
- ordinal: message.ordinal,
162709
- id: message.id,
162710
- role: message.role,
162711
- content: getIndexableContent(message.role, message.parts)
162712
- })).filter((message) => message.content.length > 0);
163188
+ getInsertMessageStatement(db).run(sessionId, message.ordinal, message.id, message.role, content);
163189
+ advanceIndexWatermark(db, sessionId, message.ordinal, now);
163190
+ return true;
163191
+ }
163192
+ function indexSingleMessage(db, sessionId, message) {
163193
+ return db.transaction(() => indexSingleMessageInTransaction(db, sessionId, message, Date.now()))();
163194
+ }
163195
+ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal, finalWatermark = messages.length) {
162713
163196
  const now = Date.now();
163197
+ let inserted = 0;
162714
163198
  db.transaction(() => {
163199
+ const existingMessageIds = new Set(getIndexedMessageIdStatement(db).all(sessionId).map((row) => row.messageId).filter((messageId) => typeof messageId === "string"));
162715
163200
  const insertMessage = getInsertMessageStatement(db);
162716
- for (const message of messagesToInsert) {
162717
- insertMessage.run(sessionId, message.ordinal, message.id, message.role, message.content);
163201
+ for (const message of messages) {
163202
+ if (message.ordinal <= lastIndexedOrdinal) {
163203
+ continue;
163204
+ }
163205
+ if (message.role !== "user" && message.role !== "assistant") {
163206
+ continue;
163207
+ }
163208
+ const content = getIndexableContent(message.role, message.parts);
163209
+ if (content.length === 0 || existingMessageIds.has(message.id)) {
163210
+ continue;
163211
+ }
163212
+ insertMessage.run(sessionId, message.ordinal, message.id, message.role, content);
163213
+ existingMessageIds.add(message.id);
163214
+ inserted++;
162718
163215
  }
162719
- getUpsertIndexStatement(db).run(sessionId, messages.length, now, getHarness());
163216
+ getUpsertIndexStatement(db).run(sessionId, finalWatermark, now, getHarness());
162720
163217
  })();
163218
+ return inserted;
162721
163219
  }
162722
163220
  // ../plugin/src/features/magic-context/storage-meta-persisted.ts
162723
163221
  init_logger();
@@ -163316,7 +163814,7 @@ var getTagNumberByMessageIdStatements = new WeakMap;
163316
163814
  function getInsertTagStatement(db) {
163317
163815
  let stmt = insertTagStatements.get(db);
163318
163816
  if (!stmt) {
163319
- stmt = db.prepare("INSERT INTO tags (session_id, message_id, type, byte_size, reasoning_byte_size, tag_number, tool_name, input_byte_size, harness) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
163817
+ 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) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
163320
163818
  insertTagStatements.set(db, stmt);
163321
163819
  }
163322
163820
  return stmt;
@@ -163397,7 +163895,8 @@ function toTagEntry(row) {
163397
163895
  byteSize: row.byte_size,
163398
163896
  reasoningByteSize: row.reasoning_byte_size ?? 0,
163399
163897
  sessionId: row.session_id,
163400
- cavemanDepth: typeof row.caveman_depth === "number" && Number.isFinite(row.caveman_depth) ? row.caveman_depth : 0
163898
+ cavemanDepth: typeof row.caveman_depth === "number" && Number.isFinite(row.caveman_depth) ? row.caveman_depth : 0,
163899
+ toolOwnerMessageId: typeof row.tool_owner_message_id === "string" ? row.tool_owner_message_id : null
163401
163900
  };
163402
163901
  }
163403
163902
  function isTagNumberRow(row) {
@@ -163412,8 +163911,8 @@ function isMaxTagNumberRow(row) {
163412
163911
  const r = row;
163413
163912
  return typeof r.max_tag_number === "number";
163414
163913
  }
163415
- function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber, reasoningByteSize = 0, toolName = null, inputByteSize = 0) {
163416
- getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, reasoningByteSize, tagNumber, toolName, inputByteSize, getHarness());
163914
+ function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber, reasoningByteSize = 0, toolName = null, inputByteSize = 0, toolOwnerMessageId = null) {
163915
+ getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, reasoningByteSize, tagNumber, toolName, inputByteSize, getHarness(), toolOwnerMessageId);
163417
163916
  return tagNumber;
163418
163917
  }
163419
163918
  function updateTagStatus(db, sessionId, tagId, status) {
@@ -163425,6 +163924,7 @@ function updateTagDropMode(db, sessionId, tagNumber, dropMode) {
163425
163924
  function updateCavemanDepth(db, sessionId, tagNumber, depth) {
163426
163925
  db.prepare("UPDATE tags SET caveman_depth = ? WHERE session_id = ? AND tag_number = ?").run(depth, sessionId, tagNumber);
163427
163926
  }
163927
+ var getOwnerScopedToolTagNumbersStatements = new WeakMap;
163428
163928
  function getMaxTagNumberBySession(db, sessionId) {
163429
163929
  const row = getMaxTagNumberBySessionStatement(db).get(sessionId);
163430
163930
  return isMaxTagNumberRow(row) ? row.max_tag_number : 0;
@@ -163433,17 +163933,103 @@ function getTagNumberByMessageId(db, sessionId, messageId) {
163433
163933
  const row = getTagNumberByMessageIdStatement(db).get(sessionId, messageId);
163434
163934
  return isTagNumberRow(row) ? row.tag_number : null;
163435
163935
  }
163936
+ var TAG_SELECT_COLUMNS = "id, message_id, type, status, drop_mode, tool_name, input_byte_size, byte_size, reasoning_byte_size, session_id, tag_number, caveman_depth, tool_owner_message_id";
163436
163937
  function getTagsBySession(db, sessionId) {
163437
- const rows = db.prepare("SELECT id, message_id, type, status, drop_mode, tool_name, input_byte_size, byte_size, reasoning_byte_size, session_id, tag_number, caveman_depth FROM tags WHERE session_id = ? ORDER BY tag_number ASC, id ASC").all(sessionId).filter(isTagRow);
163938
+ const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? ORDER BY tag_number ASC, id ASC`).all(sessionId).filter(isTagRow);
163939
+ return rows.map(toTagEntry);
163940
+ }
163941
+ var getActiveTagsBySessionStatements = new WeakMap;
163942
+ var getMaxDroppedTagNumberStatements = new WeakMap;
163943
+ function getActiveTagsBySessionStatement(db) {
163944
+ let stmt = getActiveTagsBySessionStatements.get(db);
163945
+ if (!stmt) {
163946
+ stmt = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND status = 'active' ORDER BY tag_number ASC, id ASC`);
163947
+ getActiveTagsBySessionStatements.set(db, stmt);
163948
+ }
163949
+ return stmt;
163950
+ }
163951
+ function getActiveTagsBySession(db, sessionId) {
163952
+ const rows = getActiveTagsBySessionStatement(db).all(sessionId).filter(isTagRow);
163953
+ return rows.map(toTagEntry);
163954
+ }
163955
+ function getTagsByNumbers(db, sessionId, tagNumbers) {
163956
+ if (tagNumbers.length === 0)
163957
+ return [];
163958
+ if (tagNumbers.length > 900) {
163959
+ const all = [];
163960
+ for (let i = 0;i < tagNumbers.length; i += 900) {
163961
+ all.push(...getTagsByNumbers(db, sessionId, tagNumbers.slice(i, i + 900)));
163962
+ }
163963
+ return all;
163964
+ }
163965
+ const placeholders = tagNumbers.map(() => "?").join(",");
163966
+ 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);
163438
163967
  return rows.map(toTagEntry);
163439
163968
  }
163440
163969
  function getTopNBySize(db, sessionId, n) {
163441
163970
  if (n <= 0) {
163442
163971
  return [];
163443
163972
  }
163444
- const rows = db.prepare("SELECT id, message_id, type, status, drop_mode, tool_name, input_byte_size, byte_size, reasoning_byte_size, session_id, tag_number, caveman_depth FROM tags WHERE session_id = ? AND status = 'active' ORDER BY byte_size DESC, tag_number ASC LIMIT ?").all(sessionId, n).filter(isTagRow);
163973
+ 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);
163445
163974
  return rows.map(toTagEntry);
163446
163975
  }
163976
+ var getToolTagNumberByOwnerStatements = new WeakMap;
163977
+ var getNullOwnerToolTagStatements = new WeakMap;
163978
+ var adoptNullOwnerToolTagStatements = new WeakMap;
163979
+ var deleteToolTagsByOwnerStatements = new WeakMap;
163980
+ function getGetToolTagNumberByOwnerStatement(db) {
163981
+ let stmt = getToolTagNumberByOwnerStatements.get(db);
163982
+ if (!stmt) {
163983
+ stmt = db.prepare(`SELECT tag_number FROM tags
163984
+ WHERE session_id = ? AND message_id = ?
163985
+ AND type = 'tool' AND tool_owner_message_id = ?
163986
+ LIMIT 1`);
163987
+ getToolTagNumberByOwnerStatements.set(db, stmt);
163988
+ }
163989
+ return stmt;
163990
+ }
163991
+ function getToolTagNumberByOwner(db, sessionId, callId, ownerMsgId) {
163992
+ const row = getGetToolTagNumberByOwnerStatement(db).get(sessionId, callId, ownerMsgId);
163993
+ return isTagNumberRow(row) ? row.tag_number : null;
163994
+ }
163995
+ function isNullOwnerToolTagRow(row) {
163996
+ if (row === null || typeof row !== "object")
163997
+ return false;
163998
+ const r = row;
163999
+ return typeof r.id === "number" && typeof r.tag_number === "number";
164000
+ }
164001
+ function getGetNullOwnerToolTagStatement(db) {
164002
+ let stmt = getNullOwnerToolTagStatements.get(db);
164003
+ if (!stmt) {
164004
+ stmt = db.prepare(`SELECT id, tag_number FROM tags
164005
+ WHERE session_id = ? AND message_id = ?
164006
+ AND type = 'tool' AND tool_owner_message_id IS NULL
164007
+ ORDER BY tag_number ASC
164008
+ LIMIT 1`);
164009
+ getNullOwnerToolTagStatements.set(db, stmt);
164010
+ }
164011
+ return stmt;
164012
+ }
164013
+ function getNullOwnerToolTag(db, sessionId, callId) {
164014
+ const row = getGetNullOwnerToolTagStatement(db).get(sessionId, callId);
164015
+ if (!isNullOwnerToolTagRow(row))
164016
+ return null;
164017
+ return { id: row.id, tagNumber: row.tag_number };
164018
+ }
164019
+ function getAdoptNullOwnerToolTagStatement(db) {
164020
+ let stmt = adoptNullOwnerToolTagStatements.get(db);
164021
+ if (!stmt) {
164022
+ stmt = db.prepare(`UPDATE tags
164023
+ SET tool_owner_message_id = ?
164024
+ WHERE id = ? AND tool_owner_message_id IS NULL`);
164025
+ adoptNullOwnerToolTagStatements.set(db, stmt);
164026
+ }
164027
+ return stmt;
164028
+ }
164029
+ function adoptNullOwnerToolTag(db, rowId, ownerMsgId) {
164030
+ const result = getAdoptNullOwnerToolTagStatement(db).run(ownerMsgId, rowId);
164031
+ return (result.changes ?? 0) === 1;
164032
+ }
163447
164033
  // src/tools/ctx-note.ts
163448
164034
  var FILTER_VALUES = [
163449
164035
  "active",
@@ -164360,11 +164946,7 @@ function getMessageOrdinal(value) {
164360
164946
  }
164361
164947
  async function getSemanticScores(args) {
164362
164948
  const semanticScores = new Map;
164363
- if (!args.embeddingEnabled || !args.isEmbeddingRuntimeEnabled() || args.memories.length === 0) {
164364
- return semanticScores;
164365
- }
164366
- const queryEmbedding = await args.embedQuery(args.query, args.signal);
164367
- if (!queryEmbedding) {
164949
+ if (!args.queryEmbedding || args.memories.length === 0) {
164368
164950
  return semanticScores;
164369
164951
  }
164370
164952
  const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
@@ -164378,7 +164960,7 @@ async function getSemanticScores(args) {
164378
164960
  if (!memoryEmbedding) {
164379
164961
  continue;
164380
164962
  }
164381
- semanticScores.set(memory2.id, normalizeCosineScore(cosineSimilarity(queryEmbedding, memoryEmbedding)));
164963
+ semanticScores.set(memory2.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding)));
164382
164964
  }
164383
164965
  return semanticScores;
164384
164966
  }
@@ -164474,12 +165056,8 @@ async function searchMemories(args) {
164474
165056
  const semanticScores = await getSemanticScores({
164475
165057
  db: args.db,
164476
165058
  projectPath: args.projectPath,
164477
- query: args.query,
164478
165059
  memories: semanticCandidates,
164479
- embeddingEnabled: args.embeddingEnabled,
164480
- embedQuery: args.embedQuery,
164481
- isEmbeddingRuntimeEnabled: args.isEmbeddingRuntimeEnabled,
164482
- signal: args.signal
165060
+ queryEmbedding: args.queryEmbedding
164483
165061
  });
164484
165062
  return mergeMemoryResults({
164485
165063
  memories,
@@ -164495,7 +165073,6 @@ function linearDecayScore(rank, total) {
164495
165073
  return Math.max(0, 1 - rank / total);
164496
165074
  }
164497
165075
  function searchMessages(args) {
164498
- ensureMessagesIndexed(args.db, args.sessionId, args.readMessages);
164499
165076
  const sanitizedQuery = sanitizeFtsQuery(args.query.trim());
164500
165077
  if (sanitizedQuery.length === 0) {
164501
165078
  return [];
@@ -164566,20 +165143,12 @@ function toGitCommitResult(hit) {
164566
165143
  matchType: hit.matchType
164567
165144
  };
164568
165145
  }
164569
- async function searchGitCommitsAsync(args) {
165146
+ function searchGitCommits(args) {
164570
165147
  if (args.limit <= 0)
164571
165148
  return [];
164572
- let queryEmbedding = null;
164573
- if (args.embeddingEnabled && args.isEmbeddingRuntimeEnabled()) {
164574
- try {
164575
- queryEmbedding = await args.embedQuery(args.query, args.signal);
164576
- } catch (error48) {
164577
- log(`[search] git commit query embedding failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
164578
- }
164579
- }
164580
165149
  const hits = searchGitCommitsSync(args.db, args.projectPath, args.query, {
164581
165150
  limit: args.limit,
164582
- queryEmbedding
165151
+ queryEmbedding: args.queryEmbedding
164583
165152
  });
164584
165153
  return hits.map(toGitCommitResult);
164585
165154
  }
@@ -164610,37 +165179,37 @@ async function unifiedSearch(db, sessionId, projectPath, query, options3 = {}) {
164610
165179
  const runMemory = activeSources.has("memory") && (options3.memoryEnabled ?? true);
164611
165180
  const runMessages = activeSources.has("message");
164612
165181
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
164613
- const [memoryResults, messageResults, gitCommitResults] = await Promise.all([
165182
+ const needsEmbedding = (runMemory || runGitCommits) && embeddingEnabled && isEmbeddingRuntimeEnabled();
165183
+ const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options3.signal).catch((error48) => {
165184
+ log(`[search] query embedding failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
165185
+ return null;
165186
+ }) : Promise.resolve(null);
165187
+ await Promise.resolve();
165188
+ const messageResults = runMessages ? searchMessages({
165189
+ db,
165190
+ sessionId,
165191
+ query: trimmedQuery,
165192
+ limit: tierLimit,
165193
+ maxOrdinal: options3.maxMessageOrdinal
165194
+ }) : [];
165195
+ const queryEmbedding = await queryEmbeddingPromise;
165196
+ const [memoryResults, gitCommitResults] = await Promise.all([
164614
165197
  runMemory ? searchMemories({
164615
165198
  db,
164616
165199
  projectPath,
164617
165200
  query: trimmedQuery,
164618
165201
  limit: tierLimit,
164619
165202
  memoryEnabled: true,
164620
- embeddingEnabled,
164621
- embedQuery,
164622
- isEmbeddingRuntimeEnabled,
164623
- visibleMemoryIds: options3.visibleMemoryIds,
164624
- signal: options3.signal
165203
+ queryEmbedding,
165204
+ visibleMemoryIds: options3.visibleMemoryIds
164625
165205
  }) : Promise.resolve([]),
164626
- runMessages ? Promise.resolve(searchMessages({
164627
- db,
164628
- sessionId,
164629
- query: trimmedQuery,
164630
- limit: tierLimit,
164631
- readMessages: options3.readMessages ?? readRawSessionMessages,
164632
- maxOrdinal: options3.maxMessageOrdinal
164633
- })) : Promise.resolve([]),
164634
- runGitCommits ? searchGitCommitsAsync({
165206
+ runGitCommits ? Promise.resolve(searchGitCommits({
164635
165207
  db,
164636
165208
  projectPath,
164637
165209
  query: trimmedQuery,
164638
165210
  limit: tierLimit,
164639
- embeddingEnabled,
164640
- embedQuery,
164641
- isEmbeddingRuntimeEnabled,
164642
- signal: options3.signal
164643
- }) : Promise.resolve([])
165211
+ queryEmbedding
165212
+ })) : Promise.resolve([])
164644
165213
  ]);
164645
165214
  const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
164646
165215
  const countRetrievals = options3.countRetrievals ?? true;