@cortexkit/pi-magic-context 0.19.0 → 0.20.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.
@@ -23808,10 +23808,10 @@ var require_stringify = __commonJS((exports, module) => {
23808
23808
  replacer = null;
23809
23809
  indent = EMPTY;
23810
23810
  };
23811
- 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;
23811
+ 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;
23812
23812
  var join_content = (inside, value, gap) => {
23813
23813
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
23814
- return join6(comment, inside, gap);
23814
+ return join7(comment, inside, gap);
23815
23815
  };
23816
23816
  var stringify_string = (holder, key, value) => {
23817
23817
  const raw = get_raw_string_literal(holder, key);
@@ -23833,13 +23833,13 @@ var require_stringify = __commonJS((exports, module) => {
23833
23833
  if (i !== 0) {
23834
23834
  inside += COMMA;
23835
23835
  }
23836
- const before = join6(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
23836
+ const before = join7(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
23837
23837
  inside += before || LF + deeper_gap;
23838
23838
  inside += stringify(i, value, deeper_gap) || STR_NULL;
23839
23839
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
23840
23840
  after_comma = process_comments(value, AFTER(i), deeper_gap);
23841
23841
  }
23842
- inside += join6(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23842
+ inside += join7(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23843
23843
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
23844
23844
  };
23845
23845
  var object_stringify = (value, gap) => {
@@ -23860,13 +23860,13 @@ var require_stringify = __commonJS((exports, module) => {
23860
23860
  inside += COMMA;
23861
23861
  }
23862
23862
  first = false;
23863
- const before = join6(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
23863
+ const before = join7(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
23864
23864
  inside += before || LF + deeper_gap;
23865
23865
  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);
23866
23866
  after_comma = process_comments(value, AFTER(key), deeper_gap);
23867
23867
  };
23868
23868
  keys.forEach(iteratee);
23869
- inside += join6(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23869
+ inside += join7(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23870
23870
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
23871
23871
  };
23872
23872
  function stringify(key, holder, gap) {
@@ -23959,17 +23959,9 @@ var require_src2 = __commonJS((exports, module) => {
23959
23959
  });
23960
23960
 
23961
23961
  // ../plugin/src/hooks/magic-context/read-session-db.ts
23962
- var exports_read_session_db = {};
23963
- __export(exports_read_session_db, {
23964
- withReadOnlySessionDb: () => withReadOnlySessionDb,
23965
- getRawSessionMessageCountFromDb: () => getRawSessionMessageCountFromDb,
23966
- getMessageTimesFromOpenCodeDb: () => getMessageTimesFromOpenCodeDb,
23967
- findLastAssistantModelFromOpenCodeDb: () => findLastAssistantModelFromOpenCodeDb,
23968
- closeReadOnlySessionDb: () => closeReadOnlySessionDb
23969
- });
23970
- import { join as join7 } from "node:path";
23962
+ import { join as join8 } from "node:path";
23971
23963
  function getOpenCodeDbPath() {
23972
- return join7(getDataDir(), "opencode", "opencode.db");
23964
+ return join8(getDataDir(), "opencode", "opencode.db");
23973
23965
  }
23974
23966
  function closeCachedReadOnlyDb() {
23975
23967
  if (!cachedReadOnlyDb) {
@@ -23996,9 +23988,6 @@ function getReadOnlySessionDb() {
23996
23988
  function withReadOnlySessionDb(fn) {
23997
23989
  return fn(getReadOnlySessionDb());
23998
23990
  }
23999
- function closeReadOnlySessionDb() {
24000
- closeCachedReadOnlyDb();
24001
- }
24002
23991
  function getRawSessionMessageCountFromDb(db, sessionId) {
24003
23992
  const row = db.prepare(`SELECT COUNT(*) as count FROM message WHERE session_id = ?
24004
23993
  AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
@@ -24024,34 +24013,6 @@ function getMessageTimesFromOpenCodeDb(sessionId, messageIds) {
24024
24013
  }
24025
24014
  return result;
24026
24015
  }
24027
- function findLastAssistantModelFromOpenCodeDb(sessionId) {
24028
- try {
24029
- return withReadOnlySessionDb((db) => {
24030
- const row = db.prepare(`SELECT json_extract(data, '$.providerID') as providerID,
24031
- json_extract(data, '$.modelID') as modelID,
24032
- json_extract(data, '$.agent') as agent
24033
- FROM message
24034
- WHERE session_id = ?
24035
- AND json_extract(data, '$.role') = 'assistant'
24036
- AND json_extract(data, '$.providerID') IS NOT NULL
24037
- AND json_extract(data, '$.modelID') IS NOT NULL
24038
- ORDER BY time_created DESC
24039
- LIMIT 1`).get(sessionId);
24040
- if (!row || typeof row.providerID !== "string" || typeof row.modelID !== "string") {
24041
- return null;
24042
- }
24043
- const agent = typeof row.agent === "string" && row.agent.length > 0 ? row.agent : undefined;
24044
- return {
24045
- providerID: row.providerID,
24046
- modelID: row.modelID,
24047
- ...agent ? { agent } : {}
24048
- };
24049
- });
24050
- } catch (error51) {
24051
- log("[magic-context] failed to recover live model from OpenCode DB:", error51);
24052
- return null;
24053
- }
24054
- }
24055
24016
  var cachedReadOnlyDb = null;
24056
24017
  var init_read_session_db = __esm(async () => {
24057
24018
  init_data_path();
@@ -24064,8 +24025,8 @@ init_embedding();
24064
24025
 
24065
24026
  // ../plugin/src/features/magic-context/storage-db.ts
24066
24027
  init_data_path();
24067
- import { copyFileSync, cpSync, existsSync as existsSync3, mkdirSync as mkdirSync4 } from "node:fs";
24068
- import { join as join5 } from "node:path";
24028
+ import { copyFileSync, cpSync, existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
24029
+ import { join as join6 } from "node:path";
24069
24030
 
24070
24031
  // ../plugin/src/shared/error-message.ts
24071
24032
  function getErrorMessage(error51) {
@@ -24147,6 +24108,119 @@ function safeString(value) {
24147
24108
  init_logger();
24148
24109
  await init_sqlite();
24149
24110
 
24111
+ // ../plugin/src/features/magic-context/key-files/project-key-files.ts
24112
+ init_logger();
24113
+ import { createHash as createHash2 } from "node:crypto";
24114
+ import { existsSync as existsSync2, readFileSync, realpathSync } from "node:fs";
24115
+ import { join as join4, resolve, sep } from "node:path";
24116
+ var MISSING_CONTENT_HASH = "<missing>";
24117
+ var ORPHAN_GRACE_MS = 7 * 24 * 60 * 60 * 1000;
24118
+ function sha256(input) {
24119
+ return createHash2("sha256").update(input).digest("hex");
24120
+ }
24121
+ function resolveProjectPath(directory) {
24122
+ if (!directory?.trim())
24123
+ return null;
24124
+ try {
24125
+ return realpathSync(directory);
24126
+ } catch {
24127
+ return resolve(directory);
24128
+ }
24129
+ }
24130
+ function rowToProjectKeyFile(row) {
24131
+ return {
24132
+ projectPath: String(row.project_path),
24133
+ path: String(row.path),
24134
+ content: String(row.content),
24135
+ contentHash: String(row.content_hash),
24136
+ localTokenEstimate: Number(row.local_token_estimate),
24137
+ generatedAt: Number(row.generated_at),
24138
+ generatedByModel: row.generated_by_model == null ? null : String(row.generated_by_model),
24139
+ generationConfigHash: String(row.generation_config_hash),
24140
+ staleReason: row.stale_reason == null ? null : String(row.stale_reason)
24141
+ };
24142
+ }
24143
+ function readCurrentKeyFiles(db, projectPath) {
24144
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
24145
+ const rows = db.prepare(`SELECT project_path, path, content, content_hash, local_token_estimate,
24146
+ generated_at, generated_by_model, generation_config_hash, stale_reason
24147
+ FROM project_key_files
24148
+ WHERE project_path = ?
24149
+ ORDER BY generated_at DESC, path ASC`).all(resolvedProjectPath);
24150
+ return rows.map(rowToProjectKeyFile);
24151
+ }
24152
+ function getKeyFilesVersion(db, projectPath) {
24153
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
24154
+ const row = db.prepare("SELECT version FROM project_key_files_version WHERE project_path = ?").get(resolvedProjectPath);
24155
+ return Number(row?.version ?? 0);
24156
+ }
24157
+ function bumpKeyFilesVersion(db, projectPath) {
24158
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
24159
+ db.prepare(`INSERT INTO project_key_files_version (project_path, version)
24160
+ VALUES (?, 1)
24161
+ ON CONFLICT(project_path) DO UPDATE SET version = version + 1`).run(resolvedProjectPath);
24162
+ return getKeyFilesVersion(db, resolvedProjectPath);
24163
+ }
24164
+ function resolveCommitFiles(projectPath, files) {
24165
+ return files.map((file2) => {
24166
+ try {
24167
+ const disk = readFileSync(join4(projectPath, file2.path));
24168
+ return {
24169
+ ...file2,
24170
+ contentHash: sha256(disk),
24171
+ staleReason: null
24172
+ };
24173
+ } catch {
24174
+ return {
24175
+ ...file2,
24176
+ contentHash: MISSING_CONTENT_HASH,
24177
+ staleReason: "missing"
24178
+ };
24179
+ }
24180
+ });
24181
+ }
24182
+ function insertResolvedKeyFiles(db, projectPath, files, generatedAt, generatedByModel, generationConfigHash) {
24183
+ const insert = db.prepare(`INSERT INTO project_key_files
24184
+ (project_path, path, content, content_hash, local_token_estimate,
24185
+ generated_at, generated_by_model, generation_config_hash, stale_reason)
24186
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
24187
+ for (const file2 of files) {
24188
+ insert.run(projectPath, file2.path, file2.content, file2.contentHash, file2.localTokenEstimate, generatedAt, generatedByModel, generationConfigHash, file2.staleReason);
24189
+ }
24190
+ }
24191
+ function deleteOrphanProjectKeyFiles(db, now = Date.now()) {
24192
+ const cutoff = now - ORPHAN_GRACE_MS;
24193
+ const rows = db.prepare(`SELECT project_path AS projectPath, MAX(generated_at) AS lastGen
24194
+ FROM project_key_files
24195
+ GROUP BY project_path
24196
+ HAVING lastGen < ?`).all(cutoff);
24197
+ let deletedProjects = 0;
24198
+ for (const row of rows) {
24199
+ if (existsSync2(row.projectPath))
24200
+ continue;
24201
+ try {
24202
+ db.prepare("DELETE FROM project_key_files WHERE project_path = ?").run(row.projectPath);
24203
+ db.prepare("DELETE FROM project_key_files_version WHERE project_path = ?").run(row.projectPath);
24204
+ deletedProjects++;
24205
+ } catch (error51) {
24206
+ log(`[key-files] orphan GC failed for ${row.projectPath}:`, error51);
24207
+ }
24208
+ }
24209
+ return deletedProjects;
24210
+ }
24211
+ function isRelativeProjectFile(projectPath, relativePath) {
24212
+ if (!relativePath || relativePath.startsWith("/") || relativePath.includes(".."))
24213
+ return false;
24214
+ try {
24215
+ const root = realpathSync(projectPath);
24216
+ const absPath = resolve(projectPath, relativePath);
24217
+ const real = realpathSync(absPath);
24218
+ return real.startsWith(root + sep) || real === root;
24219
+ } catch {
24220
+ return false;
24221
+ }
24222
+ }
24223
+
24150
24224
  // ../plugin/src/features/magic-context/migrations.ts
24151
24225
  init_logger();
24152
24226
  var MIGRATIONS = [
@@ -24447,6 +24521,46 @@ var MIGRATIONS = [
24447
24521
  db.exec("ALTER TABLE session_meta ADD COLUMN pending_compaction_marker_state TEXT");
24448
24522
  }
24449
24523
  }
24524
+ },
24525
+ {
24526
+ version: 14,
24527
+ description: "Add project-scoped key files and version counter",
24528
+ up: (db) => {
24529
+ db.exec(`
24530
+ CREATE TABLE IF NOT EXISTS project_key_files (
24531
+ project_path TEXT NOT NULL,
24532
+ path TEXT NOT NULL,
24533
+ content TEXT NOT NULL,
24534
+ content_hash TEXT NOT NULL,
24535
+ local_token_estimate INTEGER NOT NULL,
24536
+ generated_at INTEGER NOT NULL,
24537
+ generated_by_model TEXT,
24538
+ generation_config_hash TEXT NOT NULL,
24539
+ stale_reason TEXT,
24540
+ PRIMARY KEY (project_path, path)
24541
+ );
24542
+
24543
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_project
24544
+ ON project_key_files(project_path);
24545
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at
24546
+ ON project_key_files(project_path, generated_at);
24547
+
24548
+ CREATE TABLE IF NOT EXISTS project_key_files_version (
24549
+ project_path TEXT PRIMARY KEY,
24550
+ version INTEGER NOT NULL DEFAULT 0
24551
+ );
24552
+ `);
24553
+ }
24554
+ },
24555
+ {
24556
+ version: 15,
24557
+ description: "Add deferred_execute_state column for boundary execution drain",
24558
+ up: (db) => {
24559
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
24560
+ if (!cols.some((c) => c.name === "deferred_execute_state")) {
24561
+ db.exec("ALTER TABLE session_meta ADD COLUMN deferred_execute_state TEXT");
24562
+ }
24563
+ }
24450
24564
  }
24451
24565
  ];
24452
24566
  function ensureMigrationsTable(db) {
@@ -157162,12 +157276,12 @@ function loadToolDefinitionMeasurements(db) {
157162
157276
  // ../plugin/src/features/magic-context/tool-owner-backfill.ts
157163
157277
  init_data_path();
157164
157278
  init_logger();
157165
- import { existsSync as existsSync2 } from "node:fs";
157166
- import { join as join4 } from "node:path";
157279
+ import { existsSync as existsSync3 } from "node:fs";
157280
+ import { join as join5 } from "node:path";
157167
157281
  var LEASE_DURATION_MS = 5 * 60 * 1000;
157168
157282
  var LEASE_RENEWAL_MS = 60 * 1000;
157169
157283
  function resolveOpencodeDbPath() {
157170
- return join4(getDataDir(), "opencode", "opencode.db");
157284
+ return join5(getDataDir(), "opencode", "opencode.db");
157171
157285
  }
157172
157286
  function ensureBackfillStateTable(db) {
157173
157287
  db.exec(`
@@ -157202,7 +157316,7 @@ function runToolOwnerBackfill(db) {
157202
157316
  return result;
157203
157317
  }
157204
157318
  const opencodeDbPath = resolveOpencodeDbPath();
157205
- if (!existsSync2(opencodeDbPath)) {
157319
+ if (!existsSync3(opencodeDbPath)) {
157206
157320
  log(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
157207
157321
  markAllUnbackfilledSessionsSkipped(db);
157208
157322
  result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
@@ -157410,21 +157524,21 @@ var persistenceByDatabase = new WeakMap;
157410
157524
  var persistenceErrorByDatabase = new WeakMap;
157411
157525
  function resolveDatabasePath() {
157412
157526
  const dbDir = getMagicContextStorageDir();
157413
- return { dbDir, dbPath: join5(dbDir, "context.db") };
157527
+ return { dbDir, dbPath: join6(dbDir, "context.db") };
157414
157528
  }
157415
157529
  function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
157416
- if (existsSync3(targetDbPath))
157530
+ if (existsSync4(targetDbPath))
157417
157531
  return;
157418
157532
  const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
157419
- const legacyDbPath = join5(legacyDir, "context.db");
157420
- if (!existsSync3(legacyDbPath))
157533
+ const legacyDbPath = join6(legacyDir, "context.db");
157534
+ if (!existsSync4(legacyDbPath))
157421
157535
  return;
157422
157536
  log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
157423
157537
  mkdirSync4(targetDbDir, { recursive: true });
157424
157538
  for (const suffix of ["", "-wal", "-shm"]) {
157425
157539
  const src = `${legacyDbPath}${suffix}`;
157426
- const dst = join5(targetDbDir, `context.db${suffix}`);
157427
- if (existsSync3(src)) {
157540
+ const dst = join6(targetDbDir, `context.db${suffix}`);
157541
+ if (existsSync4(src)) {
157428
157542
  try {
157429
157543
  copyFileSync(src, dst);
157430
157544
  } catch (error51) {
@@ -157432,9 +157546,9 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
157432
157546
  }
157433
157547
  }
157434
157548
  }
157435
- const legacyModelsDir = join5(legacyDir, "models");
157436
- const targetModelsDir = join5(targetDbDir, "models");
157437
- if (existsSync3(legacyModelsDir) && !existsSync3(targetModelsDir)) {
157549
+ const legacyModelsDir = join6(legacyDir, "models");
157550
+ const targetModelsDir = join6(targetDbDir, "models");
157551
+ if (existsSync4(legacyModelsDir) && !existsSync4(targetModelsDir)) {
157438
157552
  try {
157439
157553
  cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
157440
157554
  } catch (error51) {
@@ -157582,6 +157696,26 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
157582
157696
  );
157583
157697
  CREATE INDEX IF NOT EXISTS idx_dream_runs_project ON dream_runs(project_path, finished_at DESC);
157584
157698
 
157699
+ CREATE TABLE IF NOT EXISTS project_key_files (
157700
+ project_path TEXT NOT NULL,
157701
+ path TEXT NOT NULL,
157702
+ content TEXT NOT NULL,
157703
+ content_hash TEXT NOT NULL,
157704
+ local_token_estimate INTEGER NOT NULL,
157705
+ generated_at INTEGER NOT NULL,
157706
+ generated_by_model TEXT,
157707
+ generation_config_hash TEXT NOT NULL,
157708
+ stale_reason TEXT,
157709
+ PRIMARY KEY (project_path, path)
157710
+ );
157711
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_project ON project_key_files(project_path);
157712
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at ON project_key_files(project_path, generated_at);
157713
+
157714
+ CREATE TABLE IF NOT EXISTS project_key_files_version (
157715
+ project_path TEXT PRIMARY KEY,
157716
+ version INTEGER NOT NULL DEFAULT 0
157717
+ );
157718
+
157585
157719
  -- (smart_notes: see note above; merged into unified notes table by migration v1)
157586
157720
 
157587
157721
  CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
@@ -157659,7 +157793,11 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
157659
157793
  -- valid JSON blob written via setPendingCompactionMarkerState.
157660
157794
  -- Excluded from healNullTextColumns. Readers filter IS NOT NULL AND
157661
157795
  -- != empty-string defensively. Plan v6 section 3.
157662
- pending_compaction_marker_state TEXT
157796
+ pending_compaction_marker_state TEXT,
157797
+ -- deferred_execute_state: intentionally NULLABLE without a default.
157798
+ -- Absence is SQL NULL; presence is a JSON blob written via
157799
+ -- setDeferredExecutePendingIfAbsent. Excluded from healNullTextColumns.
157800
+ deferred_execute_state TEXT
157663
157801
  );
157664
157802
 
157665
157803
  CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
@@ -157748,6 +157886,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
157748
157886
  ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
157749
157887
  ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
157750
157888
  ensureColumn(db, "session_meta", "pending_compaction_marker_state", "TEXT");
157889
+ ensureColumn(db, "session_meta", "deferred_execute_state", "TEXT");
157751
157890
  ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
157752
157891
  ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
157753
157892
  ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
@@ -157824,7 +157963,15 @@ function ensureColumn(db, table, column, definition) {
157824
157963
  if (rows.some((row) => row.name === column)) {
157825
157964
  return;
157826
157965
  }
157827
- db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
157966
+ try {
157967
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
157968
+ } catch (err) {
157969
+ const recheck = db.prepare(`PRAGMA table_info(${table})`).all();
157970
+ if (recheck.some((row) => row.name === column)) {
157971
+ return;
157972
+ }
157973
+ throw err;
157974
+ }
157828
157975
  }
157829
157976
  function openDatabase() {
157830
157977
  const { dbDir, dbPath } = resolveDatabasePath();
@@ -157841,6 +157988,11 @@ function openDatabase() {
157841
157988
  const db = new Database(dbPath);
157842
157989
  initializeDatabase(db);
157843
157990
  runMigrations(db);
157991
+ try {
157992
+ deleteOrphanProjectKeyFiles(db);
157993
+ } catch (error51) {
157994
+ log(`[magic-context] key-files orphan GC failed: ${getErrorMessage(error51)}`);
157995
+ }
157844
157996
  try {
157845
157997
  runToolOwnerBackfill(db);
157846
157998
  } catch (error51) {
@@ -157864,14 +158016,14 @@ init_logger();
157864
158016
 
157865
158017
  // src/config/index.ts
157866
158018
  init_magic_context();
157867
- import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
158019
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "node:fs";
157868
158020
  import { homedir as homedir3 } from "node:os";
157869
- import { join as join6 } from "node:path";
158021
+ import { join as join7 } from "node:path";
157870
158022
 
157871
158023
  // ../plugin/src/config/variable.ts
157872
- import { existsSync as existsSync4, readFileSync } from "node:fs";
158024
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
157873
158025
  import { homedir as homedir2 } from "node:os";
157874
- import { dirname as dirname3, isAbsolute, resolve } from "node:path";
158026
+ import { dirname as dirname3, isAbsolute, resolve as resolve2 } from "node:path";
157875
158027
  var ENV_PATTERN = /\{env:([^}]+)\}/g;
157876
158028
  var FILE_PATTERN = /\{file:([^}]+)\}/g;
157877
158029
  function substituteConfigVariables(input) {
@@ -157908,17 +158060,17 @@ function substituteConfigVariables(input) {
157908
158060
  }
157909
158061
  let filePath = rawPath.trim();
157910
158062
  if (filePath.startsWith("~/")) {
157911
- filePath = resolve(homedir2(), filePath.slice(2));
158063
+ filePath = resolve2(homedir2(), filePath.slice(2));
157912
158064
  } else if (!isAbsolute(filePath)) {
157913
- filePath = resolve(configDir, filePath);
158065
+ filePath = resolve2(configDir, filePath);
157914
158066
  }
157915
- if (!existsSync4(filePath)) {
158067
+ if (!existsSync5(filePath)) {
157916
158068
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
157917
158069
  continue;
157918
158070
  }
157919
158071
  let contents;
157920
158072
  try {
157921
- contents = readFileSync(filePath, "utf-8").trim();
158073
+ contents = readFileSync2(filePath, "utf-8").trim();
157922
158074
  } catch (error51) {
157923
158075
  const message = error51 instanceof Error ? error51.message : String(error51);
157924
158076
  warnings.push(`Failed to read file for ${token} (${filePath}): ${message}; using empty string`);
@@ -157934,20 +158086,20 @@ function substituteConfigVariables(input) {
157934
158086
  var import_comment_json = __toESM(require_src2(), 1);
157935
158087
  var CONFIG_FILE_NAME = "magic-context";
157936
158088
  function getProjectConfigPaths(cwd) {
157937
- const basePath = join6(cwd, ".pi", CONFIG_FILE_NAME);
158089
+ const basePath = join7(cwd, ".pi", CONFIG_FILE_NAME);
157938
158090
  return [`${basePath}.jsonc`, `${basePath}.json`];
157939
158091
  }
157940
158092
  function getUserConfigPaths() {
157941
158093
  const home = process.env.HOME ?? homedir3();
157942
- const basePath = join6(home, ".pi", "agent", CONFIG_FILE_NAME);
158094
+ const basePath = join7(home, ".pi", "agent", CONFIG_FILE_NAME);
157943
158095
  return [`${basePath}.jsonc`, `${basePath}.json`];
157944
158096
  }
157945
158097
  function resolveFirstExisting(paths) {
157946
- return paths.find((path4) => existsSync5(path4));
158098
+ return paths.find((path4) => existsSync6(path4));
157947
158099
  }
157948
158100
  function loadConfigFile(path4, scope) {
157949
158101
  try {
157950
- const rawText = readFileSync2(path4, "utf-8");
158102
+ const rawText = readFileSync3(path4, "utf-8");
157951
158103
  const substituted = substituteConfigVariables({
157952
158104
  text: rawText,
157953
158105
  configPath: path4
@@ -162556,6 +162708,61 @@ __export(exports_typebox, {
162556
162708
  Any: () => Any
162557
162709
  });
162558
162710
  // src/read-session-pi.ts
162711
+ function isMidTurnPi(event, _sessionId) {
162712
+ const messages = event?.messages;
162713
+ if (!Array.isArray(messages))
162714
+ return false;
162715
+ let latestAssistantIndex = -1;
162716
+ let latestAssistant = null;
162717
+ for (let i = messages.length - 1;i >= 0; i--) {
162718
+ const msg = messages[i];
162719
+ if (msg !== null && typeof msg === "object") {
162720
+ const record4 = msg;
162721
+ if (record4.role === "assistant") {
162722
+ latestAssistantIndex = i;
162723
+ latestAssistant = record4;
162724
+ break;
162725
+ }
162726
+ }
162727
+ }
162728
+ if (latestAssistant === null)
162729
+ return false;
162730
+ if (latestAssistant.stopReason === "toolUse")
162731
+ return true;
162732
+ const toolCallIds = getToolCallIds(latestAssistant.content);
162733
+ if (toolCallIds.size === 0)
162734
+ return false;
162735
+ const pairedToolResultIds = new Set;
162736
+ for (const msg of messages.slice(latestAssistantIndex + 1)) {
162737
+ if (msg === null || typeof msg !== "object")
162738
+ continue;
162739
+ const record4 = msg;
162740
+ if (record4.role !== "toolResult")
162741
+ continue;
162742
+ if (typeof record4.toolCallId === "string") {
162743
+ pairedToolResultIds.add(record4.toolCallId);
162744
+ }
162745
+ }
162746
+ for (const id of toolCallIds) {
162747
+ if (!pairedToolResultIds.has(id))
162748
+ return true;
162749
+ }
162750
+ return false;
162751
+ }
162752
+ function getToolCallIds(content) {
162753
+ const ids = new Set;
162754
+ if (!Array.isArray(content))
162755
+ return ids;
162756
+ for (const item of content) {
162757
+ if (item === null || typeof item !== "object")
162758
+ continue;
162759
+ const record4 = item;
162760
+ if (record4.type === "toolCall" && typeof record4.id === "string") {
162761
+ ids.add(record4.id);
162762
+ }
162763
+ }
162764
+ return ids;
162765
+ }
162559
162766
  function readPiSessionMessages(ctx) {
162560
162767
  const sm = ctx.sessionManager;
162561
162768
  if (sm === undefined)
@@ -162577,6 +162784,7 @@ function convertEntriesToRawMessages(entries) {
162577
162784
  const result = [];
162578
162785
  let nextOrdinal = 1;
162579
162786
  let pendingToolParts = [];
162787
+ let pendingFirstRealId = "";
162580
162788
  for (const entry of entries) {
162581
162789
  if (!isMessageEntry(entry)) {
162582
162790
  continue;
@@ -162585,6 +162793,9 @@ function convertEntriesToRawMessages(entries) {
162585
162793
  const role = msg.role;
162586
162794
  if (role === "toolResult") {
162587
162795
  pendingToolParts.push(...synthesizeToolResultParts(msg));
162796
+ if (pendingFirstRealId === "") {
162797
+ pendingFirstRealId = entry.id;
162798
+ }
162588
162799
  continue;
162589
162800
  }
162590
162801
  if (role === "user") {
@@ -162593,6 +162804,7 @@ function convertEntriesToRawMessages(entries) {
162593
162804
  ...synthesizeUserParts(msg)
162594
162805
  ];
162595
162806
  pendingToolParts = [];
162807
+ pendingFirstRealId = "";
162596
162808
  result.push({
162597
162809
  ordinal: nextOrdinal++,
162598
162810
  id: entry.id,
@@ -162605,11 +162817,12 @@ function convertEntriesToRawMessages(entries) {
162605
162817
  if (pendingToolParts.length > 0) {
162606
162818
  result.push({
162607
162819
  ordinal: nextOrdinal++,
162608
- id: "",
162820
+ id: pendingFirstRealId,
162609
162821
  role: "user",
162610
162822
  parts: pendingToolParts
162611
162823
  });
162612
162824
  pendingToolParts = [];
162825
+ pendingFirstRealId = "";
162613
162826
  }
162614
162827
  result.push({
162615
162828
  ordinal: nextOrdinal++,
@@ -162629,7 +162842,7 @@ function convertEntriesToRawMessages(entries) {
162629
162842
  if (pendingToolParts.length > 0) {
162630
162843
  result.push({
162631
162844
  ordinal: nextOrdinal,
162632
- id: "",
162845
+ id: pendingFirstRealId,
162633
162846
  role: "user",
162634
162847
  parts: pendingToolParts
162635
162848
  });
@@ -163130,7 +163343,7 @@ init_normalize_hash();
163130
163343
 
163131
163344
  // ../plugin/src/features/magic-context/memory/project-identity.ts
163132
163345
  import { execSync } from "node:child_process";
163133
- import { createHash as createHash2 } from "node:crypto";
163346
+ import { createHash as createHash3 } from "node:crypto";
163134
163347
  import path4 from "node:path";
163135
163348
  var GIT_TIMEOUT_MS = 5000;
163136
163349
  var resolvedCache = new Map;
@@ -163151,7 +163364,7 @@ function getRootCommitHash(directory) {
163151
163364
  }
163152
163365
  function directoryFallback(directory) {
163153
163366
  const canonical = path4.resolve(directory);
163154
- const hash2 = createHash2("md5").update(canonical).digest("hex").slice(0, 12);
163367
+ const hash2 = createHash3("md5").update(canonical).digest("hex").slice(0, 12);
163155
163368
  return `dir:${hash2}`;
163156
163369
  }
163157
163370
  function resolveProjectIdentity(directory) {
@@ -164216,6 +164429,12 @@ function isPersistedNoteNudgeRow(row) {
164216
164429
  const r = row;
164217
164430
  return typeof r.note_nudge_trigger_pending === "number" && typeof r.note_nudge_trigger_message_id === "string" && typeof r.note_nudge_sticky_text === "string" && typeof r.note_nudge_sticky_message_id === "string";
164218
164431
  }
164432
+ function isPersistedTodoSyntheticAnchorRow(row) {
164433
+ if (row === null || typeof row !== "object")
164434
+ return false;
164435
+ const r = row;
164436
+ return typeof r.todo_synthetic_call_id === "string" && typeof r.todo_synthetic_anchor_message_id === "string" && typeof r.todo_synthetic_state_json === "string";
164437
+ }
164219
164438
  function isPersistedHistorianFailureRow(row) {
164220
164439
  if (row === null || typeof row !== "object")
164221
164440
  return false;
@@ -164292,6 +164511,29 @@ function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
164292
164511
  function clearPersistedNoteNudge(db, sessionId) {
164293
164512
  db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '', note_nudge_sticky_text = '', note_nudge_sticky_message_id = '' WHERE session_id = ?").run(sessionId);
164294
164513
  }
164514
+ function getPersistedTodoSyntheticAnchor(db, sessionId) {
164515
+ const result = db.prepare("SELECT todo_synthetic_call_id, todo_synthetic_anchor_message_id, todo_synthetic_state_json FROM session_meta WHERE session_id = ?").get(sessionId);
164516
+ if (!isPersistedTodoSyntheticAnchorRow(result)) {
164517
+ return null;
164518
+ }
164519
+ if (result.todo_synthetic_call_id.length === 0 || result.todo_synthetic_anchor_message_id.length === 0) {
164520
+ return null;
164521
+ }
164522
+ return {
164523
+ callId: result.todo_synthetic_call_id,
164524
+ messageId: result.todo_synthetic_anchor_message_id,
164525
+ stateJson: result.todo_synthetic_state_json
164526
+ };
164527
+ }
164528
+ function setPersistedTodoSyntheticAnchor(db, sessionId, callId, messageId, stateJson) {
164529
+ db.transaction(() => {
164530
+ ensureSessionMetaRow(db, sessionId);
164531
+ db.prepare("UPDATE session_meta SET todo_synthetic_call_id = ?, todo_synthetic_anchor_message_id = ?, todo_synthetic_state_json = ? WHERE session_id = ?").run(callId, messageId, stateJson, sessionId);
164532
+ })();
164533
+ }
164534
+ function clearPersistedTodoSyntheticAnchor(db, sessionId) {
164535
+ db.prepare("UPDATE session_meta SET todo_synthetic_call_id = '', todo_synthetic_anchor_message_id = '', todo_synthetic_state_json = '' WHERE session_id = ?").run(sessionId);
164536
+ }
164295
164537
  function getNoteLastReadAt(db, sessionId) {
164296
164538
  try {
164297
164539
  const result = db.prepare("SELECT note_last_read_at FROM session_meta WHERE session_id = ?").get(sessionId);
@@ -164418,6 +164660,30 @@ function clearPendingCompactionMarkerStateIf(db, sessionId, expected) {
164418
164660
  WHERE session_id = ? AND pending_compaction_marker_state = ?`).run(sessionId, expectedBlob);
164419
164661
  return result.changes > 0;
164420
164662
  }
164663
+ function peekDeferredExecutePending(db, sessionId) {
164664
+ const row = db.prepare("SELECT deferred_execute_state FROM session_meta WHERE session_id = ?").get(sessionId);
164665
+ const raw = row?.deferred_execute_state;
164666
+ if (raw === null || raw === undefined || raw === "")
164667
+ return null;
164668
+ try {
164669
+ return JSON.parse(raw);
164670
+ } catch {
164671
+ return null;
164672
+ }
164673
+ }
164674
+ function setDeferredExecutePendingIfAbsent(db, sessionId, payload) {
164675
+ ensureSessionMetaRow(db, sessionId);
164676
+ const payloadBlob = stableStringify(payload);
164677
+ const result = db.prepare(`UPDATE session_meta SET deferred_execute_state = ?
164678
+ WHERE session_id = ? AND deferred_execute_state IS NULL`).run(payloadBlob, sessionId);
164679
+ return result.changes > 0;
164680
+ }
164681
+ function clearDeferredExecutePendingIfMatches(db, sessionId, expected) {
164682
+ const expectedBlob = stableStringify(expected);
164683
+ const result = db.prepare(`UPDATE session_meta SET deferred_execute_state = NULL
164684
+ WHERE session_id = ? AND deferred_execute_state = ?`).run(sessionId, expectedBlob);
164685
+ return result.changes > 0;
164686
+ }
164421
164687
  // ../plugin/src/features/magic-context/resolve-subagent-fallback.ts
164422
164688
  init_logger();
164423
164689
  await init_read_session_db();
@@ -166590,6 +166856,70 @@ function createCtxSearchTool(deps) {
166590
166856
  };
166591
166857
  }
166592
166858
 
166859
+ // src/tools/todowrite.ts
166860
+ var STATUS_VALUES = [
166861
+ "pending",
166862
+ "in_progress",
166863
+ "completed",
166864
+ "cancelled"
166865
+ ];
166866
+ var PRIORITY_VALUES = ["high", "medium", "low"];
166867
+ var TodoItem = exports_typebox.Object({
166868
+ content: exports_typebox.String({ description: "Brief description of the task" }),
166869
+ status: exports_typebox.Union(STATUS_VALUES.map((v) => exports_typebox.Literal(v))),
166870
+ priority: exports_typebox.Optional(exports_typebox.Union(PRIORITY_VALUES.map((v) => exports_typebox.Literal(v)))),
166871
+ id: exports_typebox.Optional(exports_typebox.String({ description: "Optional stable id for the todo" }))
166872
+ });
166873
+ var TodowriteParams = exports_typebox.Object({
166874
+ todos: exports_typebox.Array(TodoItem, {
166875
+ description: "Replace the current task list with this complete set of todos. Include every task you intend to track this turn — pending, in_progress, completed, or cancelled — because the list overwrites previous state."
166876
+ })
166877
+ });
166878
+ var TOOL_DESCRIPTION = [
166879
+ "Manage your task list for this session.",
166880
+ "",
166881
+ "Use this tool to plan multi-step work, track in-flight tasks, and",
166882
+ "mark progress as you complete steps. Pass the COMPLETE updated list",
166883
+ "of todos every time — this tool replaces the prior list rather than",
166884
+ "appending to it.",
166885
+ "",
166886
+ "Task states:",
166887
+ " - pending: not started yet",
166888
+ " - in_progress: currently working on (limit to ONE task at a time)",
166889
+ " - completed: finished successfully",
166890
+ " - cancelled: no longer needed",
166891
+ "",
166892
+ "Use this tool proactively for non-trivial work spanning 3+ steps.",
166893
+ "Skip it for single-shot answers or trivial 1-2 step tasks."
166894
+ ].join(`
166895
+ `);
166896
+ function createTodowriteTool() {
166897
+ return {
166898
+ name: "todowrite",
166899
+ label: "Todos",
166900
+ description: TOOL_DESCRIPTION,
166901
+ parameters: TodowriteParams,
166902
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
166903
+ const todos = params.todos ?? [];
166904
+ const completed = todos.filter((t) => t.status === "completed").length;
166905
+ const active = todos.length - completed;
166906
+ return {
166907
+ content: [
166908
+ {
166909
+ type: "text",
166910
+ text: JSON.stringify(todos, null, 2)
166911
+ }
166912
+ ],
166913
+ details: {
166914
+ todos,
166915
+ title: `${active} todos`,
166916
+ truncated: false
166917
+ }
166918
+ };
166919
+ }
166920
+ };
166921
+ }
166922
+
166593
166923
  // src/tools/index.ts
166594
166924
  function registerMagicContextTools(pi, opts) {
166595
166925
  pi.registerTool(createCtxSearchTool({
@@ -166609,6 +166939,7 @@ function registerMagicContextTools(pi, opts) {
166609
166939
  dreamerEnabled: opts.dreamerEnabled ?? false
166610
166940
  }));
166611
166941
  pi.registerTool(createCtxExpandTool({ db: opts.db }));
166942
+ pi.registerTool(createTodowriteTool());
166612
166943
  if (opts.ctxReduceEnabled === true) {
166613
166944
  pi.registerTool(createCtxReduceTool({
166614
166945
  db: opts.db,