@cortexkit/pi-magic-context 0.18.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.
package/dist/index.js CHANGED
@@ -11277,7 +11277,7 @@ function finalize(ctx, schema) {
11277
11277
  result.$schema = "http://json-schema.org/draft-07/schema#";
11278
11278
  } else if (ctx.target === "draft-04") {
11279
11279
  result.$schema = "http://json-schema.org/draft-04/schema#";
11280
- } else if (ctx.target === "openapi-3.0") {} else {}
11280
+ } else if (ctx.target === "openapi-3.0") {}
11281
11281
  if (ctx.external?.uri) {
11282
11282
  const id = ctx.external.registry.get(schema)?.id;
11283
11283
  if (!id)
@@ -11538,7 +11538,7 @@ var formatMap, stringProcessor = (schema, ctx, _json, _params) => {
11538
11538
  if (val === undefined) {
11539
11539
  if (ctx.unrepresentable === "throw") {
11540
11540
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
11541
- } else {}
11541
+ }
11542
11542
  } else if (typeof val === "bigint") {
11543
11543
  if (ctx.unrepresentable === "throw") {
11544
11544
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -14891,10 +14891,63 @@ var init_magic_context = __esm(() => {
14891
14891
  });
14892
14892
  });
14893
14893
 
14894
- // ../plugin/src/shared/logger.ts
14895
- import * as fs from "node:fs";
14894
+ // ../plugin/src/shared/harness.ts
14895
+ function setHarness(value) {
14896
+ if (harnessLocked && currentHarness !== value) {
14897
+ throw new Error(`Magic Context: harness already locked to "${currentHarness}"; cannot change to "${value}"`);
14898
+ }
14899
+ currentHarness = value;
14900
+ harnessLocked = true;
14901
+ }
14902
+ function getHarness() {
14903
+ return currentHarness;
14904
+ }
14905
+ var currentHarness = "opencode", harnessLocked = false;
14906
+
14907
+ // ../plugin/src/shared/data-path.ts
14896
14908
  import * as os from "node:os";
14897
14909
  import * as path from "node:path";
14910
+ function getDataDir() {
14911
+ return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
14912
+ }
14913
+ function getMagicContextTempDir(harness = getHarness()) {
14914
+ return path.join(os.tmpdir(), harness, "magic-context");
14915
+ }
14916
+ function getMagicContextLogPath(harness = getHarness()) {
14917
+ return path.join(getMagicContextTempDir(harness), "magic-context.log");
14918
+ }
14919
+ function getProjectMagicContextDir(directory) {
14920
+ return path.join(directory, ".opencode", "magic-context");
14921
+ }
14922
+ function getProjectMagicContextHistorianDir(directory) {
14923
+ return path.join(getProjectMagicContextDir(directory), "historian");
14924
+ }
14925
+ function getOpenCodeStorageDir() {
14926
+ return path.join(getDataDir(), "opencode", "storage");
14927
+ }
14928
+ function getMagicContextStorageDir() {
14929
+ return path.join(getDataDir(), "cortexkit", "magic-context");
14930
+ }
14931
+ function getLegacyOpenCodeMagicContextStorageDir() {
14932
+ return path.join(getOpenCodeStorageDir(), "plugin", "magic-context");
14933
+ }
14934
+ function getCacheDir() {
14935
+ return process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache");
14936
+ }
14937
+ var init_data_path = () => {};
14938
+
14939
+ // ../plugin/src/shared/logger.ts
14940
+ import * as fs from "node:fs";
14941
+ import * as path2 from "node:path";
14942
+ function ensureDir(filePath) {
14943
+ const dir = path2.dirname(filePath);
14944
+ if (dir === lastEnsuredDir)
14945
+ return;
14946
+ try {
14947
+ fs.mkdirSync(dir, { recursive: true });
14948
+ lastEnsuredDir = dir;
14949
+ } catch {}
14950
+ }
14898
14951
  function flush() {
14899
14952
  if (flushTimer) {
14900
14953
  clearTimeout(flushTimer);
@@ -14905,6 +14958,8 @@ function flush() {
14905
14958
  const data = buffer.join("");
14906
14959
  buffer = [];
14907
14960
  try {
14961
+ const logFile = getMagicContextLogPath();
14962
+ ensureDir(logFile);
14908
14963
  fs.appendFileSync(logFile, data);
14909
14964
  } catch {}
14910
14965
  }
@@ -14935,9 +14990,9 @@ ${data.stack}` : ""}` : ` ${JSON.stringify(data)}`;
14935
14990
  function sessionLog(sessionId, message, data) {
14936
14991
  log(`[magic-context][${sessionId}] ${message}`, data);
14937
14992
  }
14938
- var logFile, isTestEnv = false, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50;
14993
+ var isTestEnv = false, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50, lastEnsuredDir = null;
14939
14994
  var init_logger = __esm(() => {
14940
- logFile = path.join(os.tmpdir(), "magic-context.log");
14995
+ init_data_path();
14941
14996
  buffer = [];
14942
14997
  if (!isTestEnv) {
14943
14998
  process.on("exit", flush);
@@ -14961,30 +15016,47 @@ function cosineSimilarity(a, b) {
14961
15016
  return denominator === 0 ? 0 : dotProduct / denominator;
14962
15017
  }
14963
15018
 
14964
- // ../plugin/src/shared/data-path.ts
14965
- import * as os2 from "node:os";
14966
- import * as path2 from "node:path";
14967
- function getDataDir() {
14968
- return process.env.XDG_DATA_HOME ?? path2.join(os2.homedir(), ".local", "share");
14969
- }
14970
- function getOpenCodeStorageDir() {
14971
- return path2.join(getDataDir(), "opencode", "storage");
15019
+ // ../plugin/src/features/magic-context/memory/normalize-hash.ts
15020
+ import { createHash } from "node:crypto";
15021
+ function normalizeMemoryContent(content) {
15022
+ return content.toLowerCase().replace(/\s+/g, " ").trim();
14972
15023
  }
14973
- function getMagicContextStorageDir() {
14974
- return path2.join(getDataDir(), "cortexkit", "magic-context");
15024
+ function computeNormalizedHash(content) {
15025
+ const normalized = normalizeMemoryContent(content);
15026
+ return createHash("md5").update(normalized).digest("hex");
14975
15027
  }
14976
- function getLegacyOpenCodeMagicContextStorageDir() {
14977
- return path2.join(getOpenCodeStorageDir(), "plugin", "magic-context");
15028
+ var init_normalize_hash = () => {};
15029
+
15030
+ // ../plugin/src/features/magic-context/memory/embedding-identity.ts
15031
+ function normalizeEndpoint(endpoint) {
15032
+ return endpoint?.trim().replace(/\/+$/, "") ?? "";
14978
15033
  }
14979
- function getCacheDir() {
14980
- return process.env.XDG_CACHE_HOME ?? path2.join(os2.homedir(), ".cache");
15034
+ function getEmbeddingProviderIdentity(config2) {
15035
+ if (config2.provider === "off") {
15036
+ return "embedding-provider:off";
15037
+ }
15038
+ const identityInput = config2.provider === "openai-compatible" ? {
15039
+ provider: "openai-compatible",
15040
+ model: config2.model.trim(),
15041
+ endpoint: normalizeEndpoint(config2.endpoint),
15042
+ apiKeyPresent: Boolean(config2.api_key?.trim())
15043
+ } : {
15044
+ provider: "local",
15045
+ model: config2.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
15046
+ endpoint: "",
15047
+ apiKeyPresent: false
15048
+ };
15049
+ return `embedding-provider:${computeNormalizedHash(JSON.stringify(identityInput))}`;
14981
15050
  }
14982
- var init_data_path = () => {};
15051
+ var init_embedding_identity = __esm(() => {
15052
+ init_magic_context();
15053
+ init_normalize_hash();
15054
+ });
14983
15055
 
14984
15056
  // ../plugin/src/features/magic-context/memory/embedding-local.ts
14985
- import { mkdirSync } from "node:fs";
15057
+ import { mkdirSync as mkdirSync2 } from "node:fs";
14986
15058
  import { open, stat, unlink, writeFile } from "node:fs/promises";
14987
- import { join as join3 } from "node:path";
15059
+ import { join as join2 } from "node:path";
14988
15060
  async function acquireModelLoadLock(lockPath) {
14989
15061
  const waitStart = Date.now();
14990
15062
  while (true) {
@@ -15099,7 +15171,7 @@ class LocalEmbeddingProvider {
15099
15171
  initPromise = null;
15100
15172
  constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
15101
15173
  this.model = model;
15102
- this.modelId = `local:${model}`;
15174
+ this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
15103
15175
  }
15104
15176
  async initialize() {
15105
15177
  if (this.pipeline) {
@@ -15118,15 +15190,15 @@ class LocalEmbeddingProvider {
15118
15190
  if (LogLevel && "ERROR" in LogLevel) {
15119
15191
  env.logLevel = LogLevel.ERROR;
15120
15192
  }
15121
- const modelCacheDir = join3(getMagicContextStorageDir(), "models");
15193
+ const modelCacheDir = join2(getMagicContextStorageDir(), "models");
15122
15194
  try {
15123
- mkdirSync(modelCacheDir, { recursive: true });
15195
+ mkdirSync2(modelCacheDir, { recursive: true });
15124
15196
  env.cacheDir = modelCacheDir;
15125
15197
  } catch {
15126
15198
  log("[magic-context] could not create model cache dir, using library default");
15127
15199
  }
15128
15200
  const createPipeline = transformersModule.pipeline;
15129
- const lockPath = join3(modelCacheDir, ".load.lock");
15201
+ const lockPath = join2(modelCacheDir, ".load.lock");
15130
15202
  const releaseLock = await acquireModelLoadLock(lockPath);
15131
15203
  const stopHeartbeat = startLockHeartbeat(lockPath);
15132
15204
  try {
@@ -15241,12 +15313,13 @@ var init_embedding_local = __esm(() => {
15241
15313
  init_magic_context();
15242
15314
  init_data_path();
15243
15315
  init_logger();
15316
+ init_embedding_identity();
15244
15317
  STALE_LOCK_MS = 3 * 60000;
15245
15318
  MAX_LOCK_WAIT_MS = 5 * 60000;
15246
15319
  });
15247
15320
 
15248
15321
  // ../plugin/src/features/magic-context/memory/embedding-openai.ts
15249
- function normalizeEndpoint(endpoint) {
15322
+ function normalizeEndpoint2(endpoint) {
15250
15323
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
15251
15324
  }
15252
15325
 
@@ -15261,10 +15334,15 @@ class OpenAICompatibleEmbeddingProvider {
15261
15334
  openLogged = false;
15262
15335
  halfOpenProbeInFlight = false;
15263
15336
  constructor(options) {
15264
- this.endpoint = normalizeEndpoint(options.endpoint);
15337
+ this.endpoint = normalizeEndpoint2(options.endpoint);
15265
15338
  this.model = options.model?.trim() ?? "";
15266
15339
  this.apiKey = options.apiKey?.trim() ?? "";
15267
- this.modelId = `openai-compat:${this.endpoint}:${this.model}`;
15340
+ this.modelId = getEmbeddingProviderIdentity({
15341
+ provider: "openai-compatible",
15342
+ endpoint: this.endpoint,
15343
+ model: this.model,
15344
+ ...this.apiKey ? { api_key: this.apiKey } : {}
15345
+ });
15268
15346
  }
15269
15347
  async initialize() {
15270
15348
  if (this.initialized)
@@ -15446,20 +15524,10 @@ class OpenAICompatibleEmbeddingProvider {
15446
15524
  var FAILURE_THRESHOLD = 3, FAILURE_WINDOW_MS = 60000, OPEN_DURATION_MS, FETCH_TIMEOUT_MS = 30000;
15447
15525
  var init_embedding_openai = __esm(() => {
15448
15526
  init_logger();
15527
+ init_embedding_identity();
15449
15528
  OPEN_DURATION_MS = 5 * 60000;
15450
15529
  });
15451
15530
 
15452
- // ../plugin/src/features/magic-context/memory/normalize-hash.ts
15453
- import { createHash } from "node:crypto";
15454
- function normalizeMemoryContent(content) {
15455
- return content.toLowerCase().replace(/\s+/g, " ").trim();
15456
- }
15457
- function computeNormalizedHash(content) {
15458
- const normalized = normalizeMemoryContent(content);
15459
- return createHash("md5").update(normalized).digest("hex");
15460
- }
15461
- var init_normalize_hash = () => {};
15462
-
15463
15531
  // ../plugin/src/features/magic-context/memory/storage-memory-embeddings.ts
15464
15532
  var exports_storage_memory_embeddings = {};
15465
15533
  __export(exports_storage_memory_embeddings, {
@@ -15604,17 +15672,8 @@ function resolveEmbeddingConfig(config2) {
15604
15672
  }
15605
15673
  return { provider: "off" };
15606
15674
  }
15607
- function resolveModelId(config2) {
15608
- if (config2.provider === "off") {
15609
- return "off";
15610
- }
15611
- if (config2.provider === "openai-compatible") {
15612
- const endpoint = config2.endpoint.trim();
15613
- const model = config2.model.trim();
15614
- const keyHash = config2.api_key ? computeNormalizedHash(config2.api_key) : "nokey";
15615
- return `openai-compat:${endpoint}:${model}:${keyHash}`;
15616
- }
15617
- return config2.model.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL;
15675
+ function resolveProviderIdentity(config2) {
15676
+ return getEmbeddingProviderIdentity(config2);
15618
15677
  }
15619
15678
  function createProvider(config2) {
15620
15679
  if (config2.provider === "off") {
@@ -15638,10 +15697,10 @@ function getOrCreateProvider() {
15638
15697
  }
15639
15698
  function initializeEmbedding(config2) {
15640
15699
  const nextConfig = resolveEmbeddingConfig(config2);
15641
- const nextModelId = resolveModelId(nextConfig);
15700
+ const nextProviderIdentity = resolveProviderIdentity(nextConfig);
15642
15701
  const previousProvider = provider;
15643
- const previousModelId = previousProvider?.modelId ?? resolveModelId(embeddingConfig);
15644
- if (previousModelId === nextModelId) {
15702
+ const previousProviderIdentity = previousProvider?.modelId ?? resolveProviderIdentity(embeddingConfig);
15703
+ if (previousProviderIdentity === nextProviderIdentity) {
15645
15704
  embeddingConfig = nextConfig;
15646
15705
  return;
15647
15706
  }
@@ -15789,9 +15848,9 @@ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMe
15789
15848
  var init_embedding = __esm(() => {
15790
15849
  init_magic_context();
15791
15850
  init_logger();
15851
+ init_embedding_identity();
15792
15852
  init_embedding_local();
15793
15853
  init_embedding_openai();
15794
- init_normalize_hash();
15795
15854
  init_storage_memory_embeddings();
15796
15855
  DEFAULT_EMBEDDING_CONFIG = {
15797
15856
  provider: "local",
@@ -15983,7 +16042,7 @@ var exports_native_binding = {};
15983
16042
  __export(exports_native_binding, {
15984
16043
  resolveBetterSqliteNativeBinding: () => resolveBetterSqliteNativeBinding
15985
16044
  });
15986
- import { existsSync, mkdirSync as mkdirSync2, writeFileSync } from "node:fs";
16045
+ import { existsSync, mkdirSync as mkdirSync3, writeFileSync } from "node:fs";
15987
16046
  import { createRequire } from "node:module";
15988
16047
  import * as path4 from "node:path";
15989
16048
  function logInfo(message) {
@@ -16083,7 +16142,7 @@ async function resolveBetterSqliteNativeBinding() {
16083
16142
  }
16084
16143
  logWarn(`cached binary at ${cachedPath} has wrong ABI (${cachedProbe.actual ?? "unknown"} != ${expected}); refetching`);
16085
16144
  }
16086
- mkdirSync2(path4.dirname(cachedPath), { recursive: true });
16145
+ mkdirSync3(path4.dirname(cachedPath), { recursive: true });
16087
16146
  const nodeFileBytes = await downloadElectronPrebuild(pkgVersion, expected);
16088
16147
  writeFileSync(cachedPath, nodeFileBytes);
16089
16148
  logInfo(`cached Electron prebuild at ${cachedPath} (${(nodeFileBytes.length / 1024).toFixed(1)} KB)`);
@@ -16143,17 +16202,9 @@ function closeQuietly(db) {
16143
16202
  }
16144
16203
 
16145
16204
  // ../plugin/src/hooks/magic-context/read-session-db.ts
16146
- var exports_read_session_db = {};
16147
- __export(exports_read_session_db, {
16148
- withReadOnlySessionDb: () => withReadOnlySessionDb,
16149
- getRawSessionMessageCountFromDb: () => getRawSessionMessageCountFromDb,
16150
- getMessageTimesFromOpenCodeDb: () => getMessageTimesFromOpenCodeDb,
16151
- findLastAssistantModelFromOpenCodeDb: () => findLastAssistantModelFromOpenCodeDb,
16152
- closeReadOnlySessionDb: () => closeReadOnlySessionDb
16153
- });
16154
- import { join as join5 } from "node:path";
16205
+ import { join as join4 } from "node:path";
16155
16206
  function getOpenCodeDbPath() {
16156
- return join5(getDataDir(), "opencode", "opencode.db");
16207
+ return join4(getDataDir(), "opencode", "opencode.db");
16157
16208
  }
16158
16209
  function closeCachedReadOnlyDb() {
16159
16210
  if (!cachedReadOnlyDb) {
@@ -16180,9 +16231,6 @@ function getReadOnlySessionDb() {
16180
16231
  function withReadOnlySessionDb(fn) {
16181
16232
  return fn(getReadOnlySessionDb());
16182
16233
  }
16183
- function closeReadOnlySessionDb() {
16184
- closeCachedReadOnlyDb();
16185
- }
16186
16234
  function getRawSessionMessageCountFromDb(db, sessionId) {
16187
16235
  const row = db.prepare(`SELECT COUNT(*) as count FROM message WHERE session_id = ?
16188
16236
  AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
@@ -16208,34 +16256,6 @@ function getMessageTimesFromOpenCodeDb(sessionId, messageIds) {
16208
16256
  }
16209
16257
  return result;
16210
16258
  }
16211
- function findLastAssistantModelFromOpenCodeDb(sessionId) {
16212
- try {
16213
- return withReadOnlySessionDb((db) => {
16214
- const row = db.prepare(`SELECT json_extract(data, '$.providerID') as providerID,
16215
- json_extract(data, '$.modelID') as modelID,
16216
- json_extract(data, '$.agent') as agent
16217
- FROM message
16218
- WHERE session_id = ?
16219
- AND json_extract(data, '$.role') = 'assistant'
16220
- AND json_extract(data, '$.providerID') IS NOT NULL
16221
- AND json_extract(data, '$.modelID') IS NOT NULL
16222
- ORDER BY time_created DESC
16223
- LIMIT 1`).get(sessionId);
16224
- if (!row || typeof row.providerID !== "string" || typeof row.modelID !== "string") {
16225
- return null;
16226
- }
16227
- const agent = typeof row.agent === "string" && row.agent.length > 0 ? row.agent : undefined;
16228
- return {
16229
- providerID: row.providerID,
16230
- modelID: row.modelID,
16231
- ...agent ? { agent } : {}
16232
- };
16233
- });
16234
- } catch (error51) {
16235
- log("[magic-context] failed to recover live model from OpenCode DB:", error51);
16236
- return null;
16237
- }
16238
- }
16239
16259
  var cachedReadOnlyDb = null;
16240
16260
  var init_read_session_db = __esm(async () => {
16241
16261
  init_data_path();
@@ -16243,33 +16263,6 @@ var init_read_session_db = __esm(async () => {
16243
16263
  await init_sqlite();
16244
16264
  });
16245
16265
 
16246
- // ../plugin/src/shared/rpc-notifications.ts
16247
- var exports_rpc_notifications = {};
16248
- __export(exports_rpc_notifications, {
16249
- pushNotification: () => pushNotification,
16250
- isTuiConnected: () => isTuiConnected,
16251
- drainNotifications: () => drainNotifications
16252
- });
16253
- function pushNotification(type, payload, sessionId) {
16254
- queue2.push({ type, payload, sessionId });
16255
- if (queue2.length > 100) {
16256
- queue2 = queue2.slice(-50);
16257
- }
16258
- }
16259
- function drainNotifications() {
16260
- lastDrainAt = Date.now();
16261
- const result = queue2;
16262
- queue2 = [];
16263
- return result;
16264
- }
16265
- function isTuiConnected() {
16266
- return lastDrainAt > 0 && Date.now() - lastDrainAt < TUI_CONNECTED_WINDOW_MS;
16267
- }
16268
- var queue2, lastDrainAt = 0, TUI_CONNECTED_WINDOW_MS = 3000;
16269
- var init_rpc_notifications = __esm(() => {
16270
- queue2 = [];
16271
- });
16272
-
16273
16266
  // ../../node_modules/.bun/esprima@4.0.1/node_modules/esprima/dist/esprima.js
16274
16267
  var require_esprima = __commonJS((exports, module) => {
16275
16268
  (function webpackUniversalModuleDefinition(root, factory) {
@@ -23877,10 +23870,10 @@ var require_stringify = __commonJS((exports, module) => {
23877
23870
  replacer = null;
23878
23871
  indent = EMPTY;
23879
23872
  };
23880
- var join13 = (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;
23873
+ var join9 = (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;
23881
23874
  var join_content = (inside, value, gap) => {
23882
23875
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
23883
- return join13(comment, inside, gap);
23876
+ return join9(comment, inside, gap);
23884
23877
  };
23885
23878
  var stringify_string = (holder, key, value) => {
23886
23879
  const raw = get_raw_string_literal(holder, key);
@@ -23902,13 +23895,13 @@ var require_stringify = __commonJS((exports, module) => {
23902
23895
  if (i !== 0) {
23903
23896
  inside += COMMA;
23904
23897
  }
23905
- const before = join13(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
23898
+ const before = join9(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
23906
23899
  inside += before || LF + deeper_gap;
23907
23900
  inside += stringify(i, value, deeper_gap) || STR_NULL;
23908
23901
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
23909
23902
  after_comma = process_comments(value, AFTER(i), deeper_gap);
23910
23903
  }
23911
- inside += join13(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23904
+ inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23912
23905
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
23913
23906
  };
23914
23907
  var object_stringify = (value, gap) => {
@@ -23929,13 +23922,13 @@ var require_stringify = __commonJS((exports, module) => {
23929
23922
  inside += COMMA;
23930
23923
  }
23931
23924
  first = false;
23932
- const before = join13(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
23925
+ const before = join9(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
23933
23926
  inside += before || LF + deeper_gap;
23934
23927
  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);
23935
23928
  after_comma = process_comments(value, AFTER(key), deeper_gap);
23936
23929
  };
23937
23930
  keys.forEach(iteratee);
23938
- inside += join13(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23931
+ inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
23939
23932
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
23940
23933
  };
23941
23934
  function stringify(key, holder, gap) {
@@ -24027,10 +24020,38 @@ var require_src2 = __commonJS((exports, module) => {
24027
24020
  };
24028
24021
  });
24029
24022
 
24023
+ // ../plugin/src/shared/rpc-notifications.ts
24024
+ var exports_rpc_notifications = {};
24025
+ __export(exports_rpc_notifications, {
24026
+ pushNotification: () => pushNotification,
24027
+ isTuiConnected: () => isTuiConnected,
24028
+ drainNotifications: () => drainNotifications
24029
+ });
24030
+ function pushNotification(type, payload, sessionId) {
24031
+ queue2.push({ id: nextNotificationId++, type, payload, sessionId });
24032
+ if (queue2.length > 100) {
24033
+ queue2 = queue2.slice(-50);
24034
+ }
24035
+ }
24036
+ function drainNotifications(lastReceivedId = 0) {
24037
+ lastDrainAt = Date.now();
24038
+ if (lastReceivedId > 0) {
24039
+ queue2 = queue2.filter((notification) => notification.id > lastReceivedId);
24040
+ }
24041
+ return [...queue2];
24042
+ }
24043
+ function isTuiConnected() {
24044
+ return lastDrainAt > 0 && Date.now() - lastDrainAt < TUI_CONNECTED_WINDOW_MS;
24045
+ }
24046
+ var queue2, nextNotificationId = 1, lastDrainAt = 0, TUI_CONNECTED_WINDOW_MS = 3000;
24047
+ var init_rpc_notifications = __esm(() => {
24048
+ queue2 = [];
24049
+ });
24050
+
24030
24051
  // src/index.ts
24031
24052
  init_embedding();
24032
24053
  import { createRequire as createRequire2 } from "node:module";
24033
- import { join as join15 } from "node:path";
24054
+ import { join as join18 } from "node:path";
24034
24055
 
24035
24056
  // ../plugin/src/features/magic-context/memory/project-identity.ts
24036
24057
  import { execSync } from "node:child_process";
@@ -157083,21 +157104,6 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
157083
157104
  toolOnlyRanges
157084
157105
  };
157085
157106
  }
157086
-
157087
- // ../plugin/src/shared/harness.ts
157088
- var currentHarness = "opencode";
157089
- var harnessLocked = false;
157090
- function setHarness(value) {
157091
- if (harnessLocked && currentHarness !== value) {
157092
- throw new Error(`Magic Context: harness already locked to "${currentHarness}"; cannot change to "${value}"`);
157093
- }
157094
- currentHarness = value;
157095
- harnessLocked = true;
157096
- }
157097
- function getHarness() {
157098
- return currentHarness;
157099
- }
157100
-
157101
157107
  // ../plugin/src/features/magic-context/compression-depth-storage.ts
157102
157108
  var incrementDepthStatements = new WeakMap;
157103
157109
  var totalDepthStatements = new WeakMap;
@@ -157773,7 +157779,7 @@ function escapeXmlContent(s) {
157773
157779
  }
157774
157780
  // ../plugin/src/features/magic-context/storage-db.ts
157775
157781
  init_data_path();
157776
- import { copyFileSync, cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
157782
+ import { copyFileSync, cpSync, existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
157777
157783
  import { join as join7 } from "node:path";
157778
157784
 
157779
157785
  // ../plugin/src/shared/error-message.ts
@@ -157856,6 +157862,119 @@ function safeString(value) {
157856
157862
  init_logger();
157857
157863
  await init_sqlite();
157858
157864
 
157865
+ // ../plugin/src/features/magic-context/key-files/project-key-files.ts
157866
+ init_logger();
157867
+ import { createHash as createHash3 } from "node:crypto";
157868
+ import { existsSync as existsSync2, readFileSync, realpathSync } from "node:fs";
157869
+ import { join as join5, resolve, sep } from "node:path";
157870
+ var MISSING_CONTENT_HASH = "<missing>";
157871
+ var ORPHAN_GRACE_MS = 7 * 24 * 60 * 60 * 1000;
157872
+ function sha256(input) {
157873
+ return createHash3("sha256").update(input).digest("hex");
157874
+ }
157875
+ function resolveProjectPath(directory) {
157876
+ if (!directory?.trim())
157877
+ return null;
157878
+ try {
157879
+ return realpathSync(directory);
157880
+ } catch {
157881
+ return resolve(directory);
157882
+ }
157883
+ }
157884
+ function rowToProjectKeyFile(row) {
157885
+ return {
157886
+ projectPath: String(row.project_path),
157887
+ path: String(row.path),
157888
+ content: String(row.content),
157889
+ contentHash: String(row.content_hash),
157890
+ localTokenEstimate: Number(row.local_token_estimate),
157891
+ generatedAt: Number(row.generated_at),
157892
+ generatedByModel: row.generated_by_model == null ? null : String(row.generated_by_model),
157893
+ generationConfigHash: String(row.generation_config_hash),
157894
+ staleReason: row.stale_reason == null ? null : String(row.stale_reason)
157895
+ };
157896
+ }
157897
+ function readCurrentKeyFiles(db, projectPath) {
157898
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
157899
+ const rows = db.prepare(`SELECT project_path, path, content, content_hash, local_token_estimate,
157900
+ generated_at, generated_by_model, generation_config_hash, stale_reason
157901
+ FROM project_key_files
157902
+ WHERE project_path = ?
157903
+ ORDER BY generated_at DESC, path ASC`).all(resolvedProjectPath);
157904
+ return rows.map(rowToProjectKeyFile);
157905
+ }
157906
+ function getKeyFilesVersion(db, projectPath) {
157907
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
157908
+ const row = db.prepare("SELECT version FROM project_key_files_version WHERE project_path = ?").get(resolvedProjectPath);
157909
+ return Number(row?.version ?? 0);
157910
+ }
157911
+ function bumpKeyFilesVersion(db, projectPath) {
157912
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
157913
+ db.prepare(`INSERT INTO project_key_files_version (project_path, version)
157914
+ VALUES (?, 1)
157915
+ ON CONFLICT(project_path) DO UPDATE SET version = version + 1`).run(resolvedProjectPath);
157916
+ return getKeyFilesVersion(db, resolvedProjectPath);
157917
+ }
157918
+ function resolveCommitFiles(projectPath, files) {
157919
+ return files.map((file2) => {
157920
+ try {
157921
+ const disk = readFileSync(join5(projectPath, file2.path));
157922
+ return {
157923
+ ...file2,
157924
+ contentHash: sha256(disk),
157925
+ staleReason: null
157926
+ };
157927
+ } catch {
157928
+ return {
157929
+ ...file2,
157930
+ contentHash: MISSING_CONTENT_HASH,
157931
+ staleReason: "missing"
157932
+ };
157933
+ }
157934
+ });
157935
+ }
157936
+ function insertResolvedKeyFiles(db, projectPath, files, generatedAt, generatedByModel, generationConfigHash) {
157937
+ const insert = db.prepare(`INSERT INTO project_key_files
157938
+ (project_path, path, content, content_hash, local_token_estimate,
157939
+ generated_at, generated_by_model, generation_config_hash, stale_reason)
157940
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
157941
+ for (const file2 of files) {
157942
+ insert.run(projectPath, file2.path, file2.content, file2.contentHash, file2.localTokenEstimate, generatedAt, generatedByModel, generationConfigHash, file2.staleReason);
157943
+ }
157944
+ }
157945
+ function deleteOrphanProjectKeyFiles(db, now = Date.now()) {
157946
+ const cutoff = now - ORPHAN_GRACE_MS;
157947
+ const rows = db.prepare(`SELECT project_path AS projectPath, MAX(generated_at) AS lastGen
157948
+ FROM project_key_files
157949
+ GROUP BY project_path
157950
+ HAVING lastGen < ?`).all(cutoff);
157951
+ let deletedProjects = 0;
157952
+ for (const row of rows) {
157953
+ if (existsSync2(row.projectPath))
157954
+ continue;
157955
+ try {
157956
+ db.prepare("DELETE FROM project_key_files WHERE project_path = ?").run(row.projectPath);
157957
+ db.prepare("DELETE FROM project_key_files_version WHERE project_path = ?").run(row.projectPath);
157958
+ deletedProjects++;
157959
+ } catch (error51) {
157960
+ log(`[key-files] orphan GC failed for ${row.projectPath}:`, error51);
157961
+ }
157962
+ }
157963
+ return deletedProjects;
157964
+ }
157965
+ function isRelativeProjectFile(projectPath, relativePath) {
157966
+ if (!relativePath || relativePath.startsWith("/") || relativePath.includes(".."))
157967
+ return false;
157968
+ try {
157969
+ const root = realpathSync(projectPath);
157970
+ const absPath = resolve(projectPath, relativePath);
157971
+ const real = realpathSync(absPath);
157972
+ return real.startsWith(root + sep) || real === root;
157973
+ } catch {
157974
+ return false;
157975
+ }
157976
+ }
157977
+
157859
157978
  // ../plugin/src/features/magic-context/migrations.ts
157860
157979
  init_logger();
157861
157980
  var MIGRATIONS = [
@@ -157991,6 +158110,9 @@ var MIGRATIONS = [
157991
158110
  embedding BLOB NOT NULL,
157992
158111
  model_id TEXT NOT NULL,
157993
158112
  created_at INTEGER NOT NULL,
158113
+ -- FK-cascade audit (v12): git_commit_embeddings.sha -> git_commits.sha
158114
+ -- uses ON DELETE CASCADE, so SQLite PRAGMA foreign_keys must be ON on
158115
+ -- every connection and v12 cleans historical orphan rows.
157994
158116
  FOREIGN KEY(sha) REFERENCES git_commits(sha) ON DELETE CASCADE
157995
158117
  );
157996
158118
 
@@ -158130,6 +158252,69 @@ var MIGRATIONS = [
158130
158252
  db.exec("ALTER TABLE session_meta ADD COLUMN todo_synthetic_state_json TEXT DEFAULT ''");
158131
158253
  }
158132
158254
  }
158255
+ },
158256
+ {
158257
+ version: 12,
158258
+ description: "Clean orphan rows from FK-cascade embedding tables",
158259
+ up: (db) => {
158260
+ const hasTable = (name2) => Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name2));
158261
+ const memoryEmbeddings = hasTable("memory_embeddings") ? db.prepare(`DELETE FROM memory_embeddings
158262
+ WHERE memory_id NOT IN (SELECT id FROM memories)`).run().changes : 0;
158263
+ log(`[migrations] v12 cleaned ${memoryEmbeddings} orphan memory_embeddings row(s)`);
158264
+ const gitCommitEmbeddings = hasTable("git_commit_embeddings") ? db.prepare(`DELETE FROM git_commit_embeddings
158265
+ WHERE sha NOT IN (SELECT sha FROM git_commits)`).run().changes : 0;
158266
+ log(`[migrations] v12 cleaned ${gitCommitEmbeddings} orphan git_commit_embeddings row(s)`);
158267
+ }
158268
+ },
158269
+ {
158270
+ version: 13,
158271
+ description: "Add pending_compaction_marker_state column for deferred marker drain",
158272
+ up: (db) => {
158273
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
158274
+ if (!cols.some((c) => c.name === "pending_compaction_marker_state")) {
158275
+ db.exec("ALTER TABLE session_meta ADD COLUMN pending_compaction_marker_state TEXT");
158276
+ }
158277
+ }
158278
+ },
158279
+ {
158280
+ version: 14,
158281
+ description: "Add project-scoped key files and version counter",
158282
+ up: (db) => {
158283
+ db.exec(`
158284
+ CREATE TABLE IF NOT EXISTS project_key_files (
158285
+ project_path TEXT NOT NULL,
158286
+ path TEXT NOT NULL,
158287
+ content TEXT NOT NULL,
158288
+ content_hash TEXT NOT NULL,
158289
+ local_token_estimate INTEGER NOT NULL,
158290
+ generated_at INTEGER NOT NULL,
158291
+ generated_by_model TEXT,
158292
+ generation_config_hash TEXT NOT NULL,
158293
+ stale_reason TEXT,
158294
+ PRIMARY KEY (project_path, path)
158295
+ );
158296
+
158297
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_project
158298
+ ON project_key_files(project_path);
158299
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at
158300
+ ON project_key_files(project_path, generated_at);
158301
+
158302
+ CREATE TABLE IF NOT EXISTS project_key_files_version (
158303
+ project_path TEXT PRIMARY KEY,
158304
+ version INTEGER NOT NULL DEFAULT 0
158305
+ );
158306
+ `);
158307
+ }
158308
+ },
158309
+ {
158310
+ version: 15,
158311
+ description: "Add deferred_execute_state column for boundary execution drain",
158312
+ up: (db) => {
158313
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
158314
+ if (!cols.some((c) => c.name === "deferred_execute_state")) {
158315
+ db.exec("ALTER TABLE session_meta ADD COLUMN deferred_execute_state TEXT");
158316
+ }
158317
+ }
158133
158318
  }
158134
158319
  ];
158135
158320
  function ensureMigrationsTable(db) {
@@ -158197,6 +158382,28 @@ function runMigrations(db) {
158197
158382
  log(`[migrations] schema version now: ${MIGRATIONS[MIGRATIONS.length - 1].version}`);
158198
158383
  }
158199
158384
 
158385
+ // ../plugin/src/shared/stable-json.ts
158386
+ function stableStringify(value, seen = new WeakSet) {
158387
+ if (value === undefined)
158388
+ return "undefined";
158389
+ if (value === null || typeof value !== "object")
158390
+ return JSON.stringify(value) ?? String(value);
158391
+ if (seen.has(value))
158392
+ return '"[Circular]"';
158393
+ seen.add(value);
158394
+ if (Array.isArray(value)) {
158395
+ return `[${value.map((item) => stableStringify(item, seen)).join(",")}]`;
158396
+ }
158397
+ const entries = Object.entries(value).sort(([a], [b]) => {
158398
+ if (a < b)
158399
+ return -1;
158400
+ if (a > b)
158401
+ return 1;
158402
+ return 0;
158403
+ });
158404
+ return `{${entries.map(([key, child]) => `${JSON.stringify(key)}:${stableStringify(child, seen)}`).join(",")}}`;
158405
+ }
158406
+
158200
158407
  // ../plugin/src/features/magic-context/tool-definition-tokens.ts
158201
158408
  var measurements = new Map;
158202
158409
  var fingerprints = new Map;
@@ -158231,7 +158438,7 @@ function loadToolDefinitionMeasurements(db) {
158231
158438
  // ../plugin/src/features/magic-context/tool-owner-backfill.ts
158232
158439
  init_data_path();
158233
158440
  init_logger();
158234
- import { existsSync as existsSync2 } from "node:fs";
158441
+ import { existsSync as existsSync3 } from "node:fs";
158235
158442
  import { join as join6 } from "node:path";
158236
158443
  var LEASE_DURATION_MS = 5 * 60 * 1000;
158237
158444
  var LEASE_RENEWAL_MS = 60 * 1000;
@@ -158271,7 +158478,7 @@ function runToolOwnerBackfill(db) {
158271
158478
  return result;
158272
158479
  }
158273
158480
  const opencodeDbPath = resolveOpencodeDbPath();
158274
- if (!existsSync2(opencodeDbPath)) {
158481
+ if (!existsSync3(opencodeDbPath)) {
158275
158482
  log(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
158276
158483
  markAllUnbackfilledSessionsSkipped(db);
158277
158484
  result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
@@ -158482,18 +158689,18 @@ function resolveDatabasePath() {
158482
158689
  return { dbDir, dbPath: join7(dbDir, "context.db") };
158483
158690
  }
158484
158691
  function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
158485
- if (existsSync3(targetDbPath))
158692
+ if (existsSync4(targetDbPath))
158486
158693
  return;
158487
158694
  const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
158488
158695
  const legacyDbPath = join7(legacyDir, "context.db");
158489
- if (!existsSync3(legacyDbPath))
158696
+ if (!existsSync4(legacyDbPath))
158490
158697
  return;
158491
158698
  log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
158492
- mkdirSync3(targetDbDir, { recursive: true });
158699
+ mkdirSync4(targetDbDir, { recursive: true });
158493
158700
  for (const suffix of ["", "-wal", "-shm"]) {
158494
158701
  const src = `${legacyDbPath}${suffix}`;
158495
158702
  const dst = join7(targetDbDir, `context.db${suffix}`);
158496
- if (existsSync3(src)) {
158703
+ if (existsSync4(src)) {
158497
158704
  try {
158498
158705
  copyFileSync(src, dst);
158499
158706
  } catch (error51) {
@@ -158503,7 +158710,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
158503
158710
  }
158504
158711
  const legacyModelsDir = join7(legacyDir, "models");
158505
158712
  const targetModelsDir = join7(targetDbDir, "models");
158506
- if (existsSync3(legacyModelsDir) && !existsSync3(targetModelsDir)) {
158713
+ if (existsSync4(legacyModelsDir) && !existsSync4(targetModelsDir)) {
158507
158714
  try {
158508
158715
  cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
158509
158716
  } catch (error51) {
@@ -158512,9 +158719,9 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
158512
158719
  }
158513
158720
  }
158514
158721
  function initializeDatabase(db) {
158722
+ db.exec("PRAGMA foreign_keys=ON");
158515
158723
  db.exec("PRAGMA journal_mode=WAL");
158516
158724
  db.exec("PRAGMA busy_timeout=5000");
158517
- db.exec("PRAGMA foreign_keys=ON");
158518
158725
  db.exec(`
158519
158726
  CREATE TABLE IF NOT EXISTS tags (
158520
158727
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -158612,6 +158819,9 @@ function initializeDatabase(db) {
158612
158819
  );
158613
158820
 
158614
158821
  CREATE TABLE IF NOT EXISTS memory_embeddings (
158822
+ -- FK-cascade audit (v12): memory_embeddings.memory_id -> memories.id
158823
+ -- uses ON DELETE CASCADE, so SQLite PRAGMA foreign_keys must be ON on
158824
+ -- every connection and v12 cleans historical orphan rows.
158615
158825
  memory_id INTEGER PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
158616
158826
  embedding BLOB NOT NULL,
158617
158827
  model_id TEXT
@@ -158648,6 +158858,26 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
158648
158858
  );
158649
158859
  CREATE INDEX IF NOT EXISTS idx_dream_runs_project ON dream_runs(project_path, finished_at DESC);
158650
158860
 
158861
+ CREATE TABLE IF NOT EXISTS project_key_files (
158862
+ project_path TEXT NOT NULL,
158863
+ path TEXT NOT NULL,
158864
+ content TEXT NOT NULL,
158865
+ content_hash TEXT NOT NULL,
158866
+ local_token_estimate INTEGER NOT NULL,
158867
+ generated_at INTEGER NOT NULL,
158868
+ generated_by_model TEXT,
158869
+ generation_config_hash TEXT NOT NULL,
158870
+ stale_reason TEXT,
158871
+ PRIMARY KEY (project_path, path)
158872
+ );
158873
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_project ON project_key_files(project_path);
158874
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at ON project_key_files(project_path, generated_at);
158875
+
158876
+ CREATE TABLE IF NOT EXISTS project_key_files_version (
158877
+ project_path TEXT PRIMARY KEY,
158878
+ version INTEGER NOT NULL DEFAULT 0
158879
+ );
158880
+
158651
158881
  -- (smart_notes: see note above; merged into unified notes table by migration v1)
158652
158882
 
158653
158883
  CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
@@ -158719,7 +158949,17 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
158719
158949
  system_prompt_hash TEXT DEFAULT '',
158720
158950
  memory_block_cache TEXT DEFAULT '',
158721
158951
  memory_block_count INTEGER DEFAULT 0,
158722
- memory_block_ids TEXT DEFAULT ''
158952
+ memory_block_ids TEXT DEFAULT '',
158953
+ -- pending_compaction_marker_state: intentionally NULLABLE without a
158954
+ -- default. Absence of a deferred marker is SQL NULL; presence is a
158955
+ -- valid JSON blob written via setPendingCompactionMarkerState.
158956
+ -- Excluded from healNullTextColumns. Readers filter IS NOT NULL AND
158957
+ -- != empty-string defensively. Plan v6 section 3.
158958
+ pending_compaction_marker_state TEXT,
158959
+ -- deferred_execute_state: intentionally NULLABLE without a default.
158960
+ -- Absence is SQL NULL; presence is a JSON blob written via
158961
+ -- setDeferredExecutePendingIfAbsent. Excluded from healNullTextColumns.
158962
+ deferred_execute_state TEXT
158723
158963
  );
158724
158964
 
158725
158965
  CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
@@ -158807,6 +159047,8 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
158807
159047
  ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
158808
159048
  ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
158809
159049
  ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
159050
+ ensureColumn(db, "session_meta", "pending_compaction_marker_state", "TEXT");
159051
+ ensureColumn(db, "session_meta", "deferred_execute_state", "TEXT");
158810
159052
  ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
158811
159053
  ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
158812
159054
  ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
@@ -158883,7 +159125,15 @@ function ensureColumn(db, table, column, definition) {
158883
159125
  if (rows.some((row) => row.name === column)) {
158884
159126
  return;
158885
159127
  }
158886
- db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
159128
+ try {
159129
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
159130
+ } catch (err) {
159131
+ const recheck = db.prepare(`PRAGMA table_info(${table})`).all();
159132
+ if (recheck.some((row) => row.name === column)) {
159133
+ return;
159134
+ }
159135
+ throw err;
159136
+ }
158887
159137
  }
158888
159138
  function openDatabase() {
158889
159139
  const { dbDir, dbPath } = resolveDatabasePath();
@@ -158896,10 +159146,15 @@ function openDatabase() {
158896
159146
  }
158897
159147
  try {
158898
159148
  migrateLegacyStorageIfNeeded(dbPath, dbDir);
158899
- mkdirSync3(dbDir, { recursive: true });
159149
+ mkdirSync4(dbDir, { recursive: true });
158900
159150
  const db = new Database(dbPath);
158901
159151
  initializeDatabase(db);
158902
159152
  runMigrations(db);
159153
+ try {
159154
+ deleteOrphanProjectKeyFiles(db);
159155
+ } catch (error51) {
159156
+ log(`[magic-context] key-files orphan GC failed: ${getErrorMessage(error51)}`);
159157
+ }
158903
159158
  try {
158904
159159
  runToolOwnerBackfill(db);
158905
159160
  } catch (error51) {
@@ -159021,6 +159276,12 @@ function isPersistedNoteNudgeRow(row) {
159021
159276
  const r = row;
159022
159277
  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";
159023
159278
  }
159279
+ function isPersistedTodoSyntheticAnchorRow(row) {
159280
+ if (row === null || typeof row !== "object")
159281
+ return false;
159282
+ const r = row;
159283
+ return typeof r.todo_synthetic_call_id === "string" && typeof r.todo_synthetic_anchor_message_id === "string" && typeof r.todo_synthetic_state_json === "string";
159284
+ }
159024
159285
  function isPersistedHistorianFailureRow(row) {
159025
159286
  if (row === null || typeof row !== "object")
159026
159287
  return false;
@@ -159097,6 +159358,29 @@ function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
159097
159358
  function clearPersistedNoteNudge(db, sessionId) {
159098
159359
  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);
159099
159360
  }
159361
+ function getPersistedTodoSyntheticAnchor(db, sessionId) {
159362
+ 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);
159363
+ if (!isPersistedTodoSyntheticAnchorRow(result)) {
159364
+ return null;
159365
+ }
159366
+ if (result.todo_synthetic_call_id.length === 0 || result.todo_synthetic_anchor_message_id.length === 0) {
159367
+ return null;
159368
+ }
159369
+ return {
159370
+ callId: result.todo_synthetic_call_id,
159371
+ messageId: result.todo_synthetic_anchor_message_id,
159372
+ stateJson: result.todo_synthetic_state_json
159373
+ };
159374
+ }
159375
+ function setPersistedTodoSyntheticAnchor(db, sessionId, callId, messageId, stateJson) {
159376
+ db.transaction(() => {
159377
+ ensureSessionMetaRow(db, sessionId);
159378
+ 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);
159379
+ })();
159380
+ }
159381
+ function clearPersistedTodoSyntheticAnchor(db, sessionId) {
159382
+ db.prepare("UPDATE session_meta SET todo_synthetic_call_id = '', todo_synthetic_anchor_message_id = '', todo_synthetic_state_json = '' WHERE session_id = ?").run(sessionId);
159383
+ }
159100
159384
  function getNoteLastReadAt(db, sessionId) {
159101
159385
  try {
159102
159386
  const result = db.prepare("SELECT note_last_read_at FROM session_meta WHERE session_id = ?").get(sessionId);
@@ -159201,6 +159485,52 @@ function setStrippedPlaceholderIds(db, sessionId, ids) {
159201
159485
  const json2 = ids.size > 0 ? JSON.stringify([...ids]) : "";
159202
159486
  db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ?").run(json2, sessionId);
159203
159487
  }
159488
+ function isPendingCompactionMarker(value) {
159489
+ return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
159490
+ }
159491
+ function getPendingCompactionMarkerState(db, sessionId) {
159492
+ const row = db.prepare("SELECT pending_compaction_marker_state FROM session_meta WHERE session_id = ?").get(sessionId);
159493
+ const raw = row?.pending_compaction_marker_state;
159494
+ if (raw === null || raw === undefined || raw === "")
159495
+ return null;
159496
+ try {
159497
+ const parsed = JSON.parse(raw);
159498
+ if (isPendingCompactionMarker(parsed)) {
159499
+ return parsed;
159500
+ }
159501
+ } catch {}
159502
+ return null;
159503
+ }
159504
+ function clearPendingCompactionMarkerStateIf(db, sessionId, expected) {
159505
+ const expectedBlob = stableStringify(expected);
159506
+ const result = db.prepare(`UPDATE session_meta SET pending_compaction_marker_state = NULL
159507
+ WHERE session_id = ? AND pending_compaction_marker_state = ?`).run(sessionId, expectedBlob);
159508
+ return result.changes > 0;
159509
+ }
159510
+ function peekDeferredExecutePending(db, sessionId) {
159511
+ const row = db.prepare("SELECT deferred_execute_state FROM session_meta WHERE session_id = ?").get(sessionId);
159512
+ const raw = row?.deferred_execute_state;
159513
+ if (raw === null || raw === undefined || raw === "")
159514
+ return null;
159515
+ try {
159516
+ return JSON.parse(raw);
159517
+ } catch {
159518
+ return null;
159519
+ }
159520
+ }
159521
+ function setDeferredExecutePendingIfAbsent(db, sessionId, payload) {
159522
+ ensureSessionMetaRow(db, sessionId);
159523
+ const payloadBlob = stableStringify(payload);
159524
+ const result = db.prepare(`UPDATE session_meta SET deferred_execute_state = ?
159525
+ WHERE session_id = ? AND deferred_execute_state IS NULL`).run(payloadBlob, sessionId);
159526
+ return result.changes > 0;
159527
+ }
159528
+ function clearDeferredExecutePendingIfMatches(db, sessionId, expected) {
159529
+ const expectedBlob = stableStringify(expected);
159530
+ const result = db.prepare(`UPDATE session_meta SET deferred_execute_state = NULL
159531
+ WHERE session_id = ? AND deferred_execute_state = ?`).run(sessionId, expectedBlob);
159532
+ return result.changes > 0;
159533
+ }
159204
159534
  // ../plugin/src/features/magic-context/resolve-subagent-fallback.ts
159205
159535
  init_logger();
159206
159536
  await init_read_session_db();
@@ -159795,8 +160125,8 @@ function getAgentFallbackModels(agent) {
159795
160125
 
159796
160126
  // ../plugin/src/shared/models-dev-cache.ts
159797
160127
  init_data_path();
159798
- import { createHash as createHash3 } from "node:crypto";
159799
- import { existsSync as existsSync4, readFileSync } from "node:fs";
160128
+ import { createHash as createHash4 } from "node:crypto";
160129
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
159800
160130
  import { homedir as homedir2, platform } from "node:os";
159801
160131
  import { join as join8 } from "node:path";
159802
160132
 
@@ -159904,7 +160234,7 @@ var recentlySeenApiSizes = new Set;
159904
160234
  var fileCache = null;
159905
160235
  var fileLastAttempt = 0;
159906
160236
  function hashFast(input) {
159907
- return createHash3("sha1").update(input).digest("hex");
160237
+ return createHash4("sha1").update(input).digest("hex");
159908
160238
  }
159909
160239
  function getModelsJsonPath() {
159910
160240
  const explicit = process.env.OPENCODE_MODELS_PATH?.trim();
@@ -159919,10 +160249,10 @@ function getOpencodeConfigPath() {
159919
160249
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
159920
160250
  const configDir = envDir ? envDir : platform() === "win32" ? join8(homedir2(), ".config", "opencode") : join8(process.env.XDG_CONFIG_HOME || join8(homedir2(), ".config"), "opencode");
159921
160251
  const jsonc = join8(configDir, "opencode.jsonc");
159922
- if (existsSync4(jsonc))
160252
+ if (existsSync5(jsonc))
159923
160253
  return jsonc;
159924
160254
  const json2 = join8(configDir, "opencode.json");
159925
- if (existsSync4(json2))
160255
+ if (existsSync5(json2))
159926
160256
  return json2;
159927
160257
  return null;
159928
160258
  }
@@ -159954,9 +160284,9 @@ function loadModelsDevMetadataFromFile() {
159954
160284
  const modelsJsonPath = getModelsJsonPath();
159955
160285
  let fileFound = false;
159956
160286
  try {
159957
- if (existsSync4(modelsJsonPath)) {
160287
+ if (existsSync5(modelsJsonPath)) {
159958
160288
  fileFound = true;
159959
- const raw = readFileSync(modelsJsonPath, "utf-8");
160289
+ const raw = readFileSync2(modelsJsonPath, "utf-8");
159960
160290
  const data = JSON.parse(raw);
159961
160291
  for (const [providerId, provider2] of Object.entries(data)) {
159962
160292
  if (!provider2?.models || typeof provider2.models !== "object")
@@ -159971,8 +160301,8 @@ function loadModelsDevMetadataFromFile() {
159971
160301
  }
159972
160302
  try {
159973
160303
  const configPath = getOpencodeConfigPath();
159974
- if (configPath && existsSync4(configPath)) {
159975
- const config2 = parseJsonc(readFileSync(configPath, "utf-8"));
160304
+ if (configPath && existsSync5(configPath)) {
160305
+ const config2 = parseJsonc(readFileSync2(configPath, "utf-8"));
159976
160306
  if (config2.provider && typeof config2.provider === "object") {
159977
160307
  for (const [providerId, provider2] of Object.entries(config2.provider)) {
159978
160308
  if (!provider2?.models || typeof provider2.models !== "object")
@@ -160267,6 +160597,84 @@ function clearNoteNudgeState(db, sessionId, options) {
160267
160597
  lastDeliveredAt.delete(sessionId);
160268
160598
  }
160269
160599
 
160600
+ // ../plugin/src/hooks/magic-context/todo-view.ts
160601
+ import { createHash as createHash5 } from "node:crypto";
160602
+ var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
160603
+ var TITLE_DONE_STATUSES = new Set(["completed"]);
160604
+ var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
160605
+ function normalizeTodoStateJson(todos) {
160606
+ if (!Array.isArray(todos))
160607
+ return null;
160608
+ const normalized = [];
160609
+ for (const todo of todos) {
160610
+ if (!isTodoItem(todo))
160611
+ return null;
160612
+ normalized.push({
160613
+ content: todo.content,
160614
+ status: todo.status,
160615
+ priority: todo.priority ?? "medium"
160616
+ });
160617
+ }
160618
+ return JSON.stringify(normalized);
160619
+ }
160620
+ function buildSyntheticTodoPart(stateJson) {
160621
+ const todos = parseTodoState(stateJson);
160622
+ if (todos === null || todos.length === 0)
160623
+ return null;
160624
+ if (todos.every((t) => TERMINAL_STATUSES.has(t.status)))
160625
+ return null;
160626
+ const callID = computeSyntheticCallId(stateJson);
160627
+ const activeCount = todos.filter((t) => !TITLE_DONE_STATUSES.has(t.status)).length;
160628
+ const output = JSON.stringify(todos, null, 2);
160629
+ const ts = 0;
160630
+ return {
160631
+ type: "tool",
160632
+ callID,
160633
+ tool: "todowrite",
160634
+ state: {
160635
+ status: "completed",
160636
+ input: { todos },
160637
+ output,
160638
+ title: `${activeCount} todos`,
160639
+ metadata: { todos, truncated: false },
160640
+ time: { start: ts, end: ts }
160641
+ },
160642
+ syntheticTodoMarker: true
160643
+ };
160644
+ }
160645
+ function computeSyntheticCallId(stateJson) {
160646
+ const hash2 = createHash5("sha256").update(stateJson).digest("hex").slice(0, 16);
160647
+ return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
160648
+ }
160649
+ function parseTodoState(stateJson) {
160650
+ if (stateJson.length === 0)
160651
+ return null;
160652
+ try {
160653
+ const parsed = JSON.parse(stateJson);
160654
+ if (!Array.isArray(parsed))
160655
+ return null;
160656
+ const result = [];
160657
+ for (const item of parsed) {
160658
+ if (!isTodoItem(item))
160659
+ continue;
160660
+ result.push({
160661
+ content: item.content,
160662
+ status: item.status,
160663
+ priority: item.priority ?? "medium"
160664
+ });
160665
+ }
160666
+ return result;
160667
+ } catch {
160668
+ return null;
160669
+ }
160670
+ }
160671
+ function isTodoItem(value) {
160672
+ if (value === null || typeof value !== "object")
160673
+ return false;
160674
+ const todo = value;
160675
+ return typeof todo.content === "string" && typeof todo.status === "string" && (todo.priority === undefined || typeof todo.priority === "string");
160676
+ }
160677
+
160270
160678
  // src/index.ts
160271
160679
  init_data_path();
160272
160680
  init_logger();
@@ -160296,15 +160704,15 @@ init_logger();
160296
160704
 
160297
160705
  // src/subagent-runner.ts
160298
160706
  import * as childProcess from "node:child_process";
160299
- import { existsSync as existsSync5 } from "node:fs";
160300
- import { dirname as dirname2, resolve as resolvePath } from "node:path";
160707
+ import { existsSync as existsSync6 } from "node:fs";
160708
+ import { dirname as dirname3, resolve as resolvePath } from "node:path";
160301
160709
  import { createInterface } from "node:readline";
160302
160710
  import { fileURLToPath } from "node:url";
160303
160711
  function resolveSubagentEntryPath() {
160304
160712
  try {
160305
- const here = dirname2(fileURLToPath(import.meta.url));
160713
+ const here = dirname3(fileURLToPath(import.meta.url));
160306
160714
  const candidate = resolvePath(here, "subagent-entry.js");
160307
- if (existsSync5(candidate))
160715
+ if (existsSync6(candidate))
160308
160716
  return candidate;
160309
160717
  return;
160310
160718
  } catch {
@@ -160329,13 +160737,13 @@ class PiSubagentRunner {
160329
160737
  async run(options) {
160330
160738
  const startTime = Date.now();
160331
160739
  const args = buildArgs(options);
160332
- return new Promise((resolve) => {
160740
+ return new Promise((resolve2) => {
160333
160741
  let settled = false;
160334
160742
  const settle = (result) => {
160335
160743
  if (settled)
160336
160744
  return;
160337
160745
  settled = true;
160338
- resolve(result);
160746
+ resolve2(result);
160339
160747
  };
160340
160748
  const emitProgress = (event) => {
160341
160749
  if (!options.onProgress)
@@ -160744,69 +161152,6 @@ ${sidekickText}
160744
161152
  });
160745
161153
  }
160746
161154
 
160747
- // ../plugin/src/features/magic-context/dreamer/queue.ts
160748
- function enqueueDream(db, projectIdentity, reason, force = false) {
160749
- const now = Date.now();
160750
- return db.transaction(() => {
160751
- const staleThresholdMs = force ? 2 * 60 * 1000 : 120 * 60 * 1000;
160752
- db.prepare("DELETE FROM dream_queue WHERE project_path = ? AND started_at IS NOT NULL AND started_at < ?").run([projectIdentity, now - staleThresholdMs]);
160753
- const existing = db.prepare("SELECT id FROM dream_queue WHERE project_path = ?").get(projectIdentity);
160754
- if (existing) {
160755
- return null;
160756
- }
160757
- const result = db.prepare("INSERT INTO dream_queue (project_path, reason, enqueued_at) VALUES (?, ?, ?)").run(projectIdentity, reason, now);
160758
- return {
160759
- id: Number(result.lastInsertRowid),
160760
- projectIdentity,
160761
- reason,
160762
- enqueuedAt: now,
160763
- startedAt: null
160764
- };
160765
- })();
160766
- }
160767
- function peekQueue(db, projectIdentity) {
160768
- const row = projectIdentity ? db.prepare("SELECT id, project_path, reason, enqueued_at FROM dream_queue WHERE started_at IS NULL AND project_path = ? ORDER BY enqueued_at ASC LIMIT 1").get(projectIdentity) : db.prepare("SELECT id, project_path, reason, enqueued_at FROM dream_queue WHERE started_at IS NULL ORDER BY enqueued_at ASC LIMIT 1").get();
160769
- if (!row)
160770
- return null;
160771
- return {
160772
- id: row.id,
160773
- projectIdentity: row.project_path,
160774
- reason: row.reason,
160775
- enqueuedAt: row.enqueued_at,
160776
- startedAt: null
160777
- };
160778
- }
160779
- function dequeueNext(db, projectIdentity) {
160780
- const now = Date.now();
160781
- return db.transaction(() => {
160782
- const entry = peekQueue(db, projectIdentity);
160783
- if (!entry)
160784
- return null;
160785
- const result = db.prepare("UPDATE dream_queue SET started_at = ? WHERE id = ? AND started_at IS NULL").run(now, entry.id);
160786
- if (result.changes === 0)
160787
- return null;
160788
- return { ...entry, startedAt: now };
160789
- })();
160790
- }
160791
- function removeDreamEntry(db, id) {
160792
- db.prepare("DELETE FROM dream_queue WHERE id = ?").run(id);
160793
- }
160794
- function resetDreamEntry(db, id) {
160795
- db.prepare("UPDATE dream_queue SET started_at = NULL, retry_count = COALESCE(retry_count, 0) + 1 WHERE id = ?").run(id);
160796
- }
160797
- function getEntryRetryCount(db, id) {
160798
- const row = db.prepare("SELECT retry_count FROM dream_queue WHERE id = ?").get(id);
160799
- return row?.retry_count ?? 0;
160800
- }
160801
- function clearStaleEntries(db, maxAgeMs) {
160802
- const cutoff = Date.now() - maxAgeMs;
160803
- const result = db.prepare("DELETE FROM dream_queue WHERE started_at IS NOT NULL AND started_at < ?").run(cutoff);
160804
- return result.changes;
160805
- }
160806
-
160807
- // src/commands/ctx-dream.ts
160808
- init_logger();
160809
-
160810
161155
  // ../plugin/src/features/magic-context/dreamer/storage-dream-state.ts
160811
161156
  var getDreamStateStatements = new WeakMap;
160812
161157
  var setDreamStateStatements = new WeakMap;
@@ -160866,6 +161211,16 @@ function isLeaseActive(db) {
160866
161211
  function getLeaseHolder(db) {
160867
161212
  return getDreamState(db, LEASE_HOLDER_KEY);
160868
161213
  }
161214
+ function peekLeaseHolderAndExpiry(db, expectedHolder) {
161215
+ const holder = getDreamState(db, LEASE_HOLDER_KEY);
161216
+ if (holder !== expectedHolder)
161217
+ return false;
161218
+ const expiryStr = getDreamState(db, LEASE_EXPIRY_KEY);
161219
+ if (!expiryStr)
161220
+ return false;
161221
+ const expiry = Number(expiryStr);
161222
+ return Number.isFinite(expiry) && expiry >= Date.now();
161223
+ }
160869
161224
  function acquireLease(db, holderId) {
160870
161225
  return db.transaction(() => {
160871
161226
  if (isLeaseActive(db)) {
@@ -160902,9 +161257,84 @@ function releaseLease(db, holderId) {
160902
161257
  deleteDreamState(db, LEASE_EXPIRY_KEY);
160903
161258
  })();
160904
161259
  }
161260
+
161261
+ // ../plugin/src/features/magic-context/dreamer/queue.ts
161262
+ function hasActiveDreamLease(db) {
161263
+ try {
161264
+ return isLeaseActive(db);
161265
+ } catch (error51) {
161266
+ if (String(error51).includes("no such table: dream_state")) {
161267
+ return false;
161268
+ }
161269
+ throw error51;
161270
+ }
161271
+ }
161272
+ function enqueueDream(db, projectIdentity, reason, force = false) {
161273
+ const now = Date.now();
161274
+ return db.transaction(() => {
161275
+ if (!hasActiveDreamLease(db)) {
161276
+ const staleThresholdMs = force ? 2 * 60 * 1000 : 120 * 60 * 1000;
161277
+ db.prepare("DELETE FROM dream_queue WHERE project_path = ? AND started_at IS NOT NULL AND started_at < ?").run([projectIdentity, now - staleThresholdMs]);
161278
+ }
161279
+ const existing = db.prepare("SELECT id FROM dream_queue WHERE project_path = ?").get(projectIdentity);
161280
+ if (existing) {
161281
+ return null;
161282
+ }
161283
+ const result = db.prepare("INSERT INTO dream_queue (project_path, reason, enqueued_at) VALUES (?, ?, ?)").run(projectIdentity, reason, now);
161284
+ return {
161285
+ id: Number(result.lastInsertRowid),
161286
+ projectIdentity,
161287
+ reason,
161288
+ enqueuedAt: now,
161289
+ startedAt: null
161290
+ };
161291
+ })();
161292
+ }
161293
+ function peekQueue(db, projectIdentity) {
161294
+ const row = projectIdentity ? db.prepare("SELECT id, project_path, reason, enqueued_at FROM dream_queue WHERE started_at IS NULL AND project_path = ? ORDER BY enqueued_at ASC LIMIT 1").get(projectIdentity) : db.prepare("SELECT id, project_path, reason, enqueued_at FROM dream_queue WHERE started_at IS NULL ORDER BY enqueued_at ASC LIMIT 1").get();
161295
+ if (!row)
161296
+ return null;
161297
+ return {
161298
+ id: row.id,
161299
+ projectIdentity: row.project_path,
161300
+ reason: row.reason,
161301
+ enqueuedAt: row.enqueued_at,
161302
+ startedAt: null
161303
+ };
161304
+ }
161305
+ function dequeueNext(db, projectIdentity) {
161306
+ const now = Date.now();
161307
+ return db.transaction(() => {
161308
+ const entry = peekQueue(db, projectIdentity);
161309
+ if (!entry)
161310
+ return null;
161311
+ const result = db.prepare("UPDATE dream_queue SET started_at = ? WHERE id = ? AND started_at IS NULL").run(now, entry.id);
161312
+ if (result.changes === 0)
161313
+ return null;
161314
+ return { ...entry, startedAt: now };
161315
+ })();
161316
+ }
161317
+ function removeDreamEntry(db, id) {
161318
+ db.prepare("DELETE FROM dream_queue WHERE id = ?").run(id);
161319
+ }
161320
+ function resetDreamEntry(db, id) {
161321
+ db.prepare("UPDATE dream_queue SET started_at = NULL, retry_count = COALESCE(retry_count, 0) + 1 WHERE id = ?").run(id);
161322
+ }
161323
+ function getEntryRetryCount(db, id) {
161324
+ const row = db.prepare("SELECT retry_count FROM dream_queue WHERE id = ?").get(id);
161325
+ return row?.retry_count ?? 0;
161326
+ }
161327
+ function clearStaleEntries(db, maxAgeMs) {
161328
+ const cutoff = Date.now() - maxAgeMs;
161329
+ const result = db.prepare("DELETE FROM dream_queue WHERE started_at IS NOT NULL AND started_at < ?").run(cutoff);
161330
+ return result.changes;
161331
+ }
161332
+
161333
+ // src/commands/ctx-dream.ts
161334
+ init_logger();
160905
161335
  // ../plugin/src/features/magic-context/dreamer/runner.ts
160906
- import { existsSync as existsSync6 } from "node:fs";
160907
- import { join as join9 } from "node:path";
161336
+ import { existsSync as existsSync8 } from "node:fs";
161337
+ import { join as join11 } from "node:path";
160908
161338
 
160909
161339
  // ../plugin/src/shared/index.ts
160910
161340
  init_logger();
@@ -161198,187 +161628,519 @@ init_logger();
161198
161628
  await init_sqlite();
161199
161629
 
161200
161630
  // ../plugin/src/features/magic-context/key-files/identify-key-files.ts
161631
+ import { readFileSync as readFileSync4 } from "node:fs";
161632
+ import { join as join10 } from "node:path";
161201
161633
  init_logger();
161202
161634
 
161203
- // ../plugin/src/features/magic-context/key-files/read-stats.ts
161204
- function getSessionReadStats(openCodeDb, sessionId, minReads) {
161205
- const fullReads = openCodeDb.prepare(`
161206
- WITH full_reads AS (
161207
- SELECT
161208
- json_extract(json_extract(data, '$.state'), '$.input.filePath') as file_path,
161209
- LENGTH(json_extract(json_extract(data, '$.state'), '$.output')) as output_bytes,
161210
- p.time_created,
161211
- ROW_NUMBER() OVER (
161212
- PARTITION BY json_extract(json_extract(data, '$.state'), '$.input.filePath')
161213
- ORDER BY p.time_created DESC
161214
- ) as rn
161215
- FROM part p
161216
- WHERE p.session_id = ?
161217
- AND json_extract(data, '$.type') = 'tool'
161218
- AND json_extract(data, '$.tool') = 'read'
161219
- AND json_extract(json_extract(data, '$.state'), '$.input.filePath') IS NOT NULL
161220
- AND json_extract(json_extract(data, '$.state'), '$.input.startLine') IS NULL
161221
- AND json_extract(json_extract(data, '$.state'), '$.input.start_line') IS NULL
161222
- AND json_extract(json_extract(data, '$.state'), '$.input.endLine') IS NULL
161223
- AND json_extract(json_extract(data, '$.state'), '$.input.end_line') IS NULL
161224
- AND json_extract(json_extract(data, '$.state'), '$.input.offset') IS NULL
161225
- AND json_extract(json_extract(data, '$.state'), '$.input.limit') IS NULL
161226
- ),
161227
- file_counts AS (
161228
- SELECT file_path, COUNT(*) as full_read_count
161229
- FROM full_reads
161230
- GROUP BY file_path
161231
- HAVING full_read_count >= ?
161232
- )
161233
- SELECT
161234
- r.file_path,
161235
- fc.full_read_count,
161236
- r.output_bytes as latest_read_bytes
161237
- FROM full_reads r
161238
- JOIN file_counts fc ON r.file_path = fc.file_path
161239
- WHERE r.rn = 1
161240
- ORDER BY fc.full_read_count DESC
161241
- `).all(sessionId, minReads);
161242
- if (fullReads.length === 0)
161243
- return [];
161244
- const editCounts = new Map;
161245
- const editRows = openCodeDb.prepare(`
161246
- SELECT
161247
- json_extract(json_extract(data, '$.state'), '$.input.filePath') as file_path,
161248
- COUNT(*) as edit_count
161249
- FROM part p
161250
- WHERE p.session_id = ?
161251
- AND json_extract(data, '$.type') = 'tool'
161252
- AND json_extract(data, '$.tool') IN ('edit', 'write', 'mcp_edit', 'mcp_write')
161253
- AND json_extract(json_extract(data, '$.state'), '$.input.filePath') IS NOT NULL
161254
- GROUP BY file_path
161255
- `).all(sessionId);
161256
- for (const row of editRows) {
161257
- editCounts.set(row.file_path, row.edit_count);
161258
- }
161259
- return fullReads.map((row) => ({
161260
- filePath: row.file_path,
161261
- fullReadCount: row.full_read_count,
161262
- spreadAcrossCompartments: 0,
161263
- editCount: editCounts.get(row.file_path) ?? 0,
161264
- latestReadBytes: row.latest_read_bytes ?? 0,
161265
- latestReadTokens: Math.ceil((row.latest_read_bytes ?? 0) / 3.5)
161266
- }));
161635
+ // ../plugin/src/features/magic-context/key-files/aft-availability.ts
161636
+ var import_comment_json = __toESM(require_src2(), 1);
161637
+ import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
161638
+ import { homedir as homedir3 } from "node:os";
161639
+ import { join as join9 } from "node:path";
161640
+ var overrideAvailability = null;
161641
+ function parseConfig(path5) {
161642
+ if (!existsSync7(path5))
161643
+ return null;
161644
+ return import_comment_json.parse(readFileSync3(path5, "utf-8"));
161645
+ }
161646
+ function entryMatchesAft(entry) {
161647
+ const value = Array.isArray(entry) ? entry[0] : entry;
161648
+ return typeof value === "string" && (value.includes("@cortexkit/aft") || value.includes("aft-opencode") || value.includes("aft-pi"));
161649
+ }
161650
+ function hasAftInArray(value) {
161651
+ return Array.isArray(value) && value.some(entryMatchesAft);
161652
+ }
161653
+ function hasAftAtKeys(value, keys) {
161654
+ if (!value || typeof value !== "object")
161655
+ return false;
161656
+ const record2 = value;
161657
+ for (const key of keys) {
161658
+ if (hasAftInArray(record2[key]))
161659
+ return true;
161660
+ }
161661
+ return false;
161662
+ }
161663
+ function getAftAvailability() {
161664
+ const home = process.env.HOME || homedir3();
161665
+ const opencodePaths = [
161666
+ join9(home, ".config", "opencode", "opencode.jsonc"),
161667
+ join9(home, ".config", "opencode", "opencode.json")
161668
+ ];
161669
+ const piPaths = [join9(home, ".pi", "agent", "settings.json")];
161670
+ const checkedPaths = [...opencodePaths, ...piPaths];
161671
+ let opencode = false;
161672
+ for (const path5 of opencodePaths) {
161673
+ try {
161674
+ const config2 = parseConfig(path5);
161675
+ if (hasAftAtKeys(config2, ["plugin", "plugins", "mcp", "mcp_servers"])) {
161676
+ opencode = true;
161677
+ break;
161678
+ }
161679
+ } catch {}
161680
+ }
161681
+ let pi = false;
161682
+ for (const path5 of piPaths) {
161683
+ try {
161684
+ const config2 = parseConfig(path5);
161685
+ if (hasAftAtKeys(config2, ["packages", "extensions"])) {
161686
+ pi = true;
161687
+ break;
161688
+ }
161689
+ const agent = config2?.agent;
161690
+ if (hasAftAtKeys(agent, ["packages", "extensions"])) {
161691
+ pi = true;
161692
+ break;
161693
+ }
161694
+ } catch {}
161695
+ }
161696
+ const detected = opencode || pi;
161697
+ return {
161698
+ available: overrideAvailability ?? detected,
161699
+ opencode,
161700
+ pi,
161701
+ checkedPaths
161702
+ };
161703
+ }
161704
+ function isAftAvailable() {
161705
+ return getAftAvailability().available;
161267
161706
  }
161268
161707
 
161269
- // ../plugin/src/features/magic-context/key-files/storage-key-files.ts
161270
- init_logger();
161271
- function getKeyFiles(db, sessionId) {
161272
- try {
161273
- const row = db.prepare("SELECT key_files FROM session_meta WHERE session_id = ?").get(sessionId);
161274
- if (!row?.key_files)
161275
- return [];
161276
- const parsed = JSON.parse(row.key_files);
161277
- if (!Array.isArray(parsed))
161278
- return [];
161279
- return parsed.filter((e) => typeof e === "object" && e !== null && typeof e.filePath === "string" && typeof e.tokens === "number");
161280
- } catch {
161708
+ // ../plugin/src/features/magic-context/key-files/read-history.ts
161709
+ import { realpathSync as realpathSync2 } from "node:fs";
161710
+ import { relative, resolve as resolve2 } from "node:path";
161711
+ function toMs(value) {
161712
+ if (typeof value === "number" && Number.isFinite(value))
161713
+ return value;
161714
+ if (typeof value === "string") {
161715
+ const n = Number(value);
161716
+ if (Number.isFinite(n))
161717
+ return n;
161718
+ const parsed = Date.parse(value);
161719
+ if (Number.isFinite(parsed))
161720
+ return parsed;
161721
+ }
161722
+ return 0;
161723
+ }
161724
+ function toPositiveInt(value) {
161725
+ const n = Number(value);
161726
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
161727
+ }
161728
+ function coalesceRanges(ranges) {
161729
+ if (ranges.length === 0)
161281
161730
  return [];
161731
+ const sorted = [...ranges].sort((a, b) => a.start - b.start || a.end - b.end);
161732
+ const merged = [];
161733
+ for (const range of sorted) {
161734
+ const last = merged.at(-1);
161735
+ if (last && range.start <= last.end + 10) {
161736
+ last.end = Math.max(last.end, range.end);
161737
+ last.count += range.count;
161738
+ last.lastReadAt = Math.max(last.lastReadAt, range.lastReadAt);
161739
+ } else {
161740
+ merged.push({ ...range });
161741
+ }
161282
161742
  }
161743
+ return merged.sort((a, b) => b.lastReadAt - a.lastReadAt || b.count - a.count).slice(0, 3);
161283
161744
  }
161284
- function setKeyFiles(db, sessionId, files) {
161745
+ function normalizeProjectRelativePath(projectPath, filePath) {
161746
+ const root = realpathSync2(projectPath);
161747
+ const abs = filePath.startsWith("/") ? resolve2(filePath) : resolve2(root, filePath);
161748
+ let real = abs;
161285
161749
  try {
161286
- db.prepare("INSERT OR IGNORE INTO session_meta (session_id) VALUES (?)").run(sessionId);
161287
- db.prepare("UPDATE session_meta SET key_files = ? WHERE session_id = ?").run(JSON.stringify(files), sessionId);
161288
- } catch (error51) {
161289
- sessionLog(sessionId, "failed to persist key files:", error51);
161750
+ real = realpathSync2(abs);
161751
+ } catch {}
161752
+ if (!real.startsWith(`${root}/`) && real !== root)
161753
+ return null;
161754
+ const rel = relative(root, real).replaceAll("\\", "/");
161755
+ if (!rel || rel.startsWith(".."))
161756
+ return null;
161757
+ return rel;
161758
+ }
161759
+ function primarySessionIds(db) {
161760
+ try {
161761
+ const rows = db.prepare("SELECT session_id AS sessionId FROM session_meta WHERE is_subagent = 0").all();
161762
+ return new Set(rows.map((row) => row.sessionId));
161763
+ } catch {
161764
+ return new Set;
161290
161765
  }
161291
161766
  }
161292
- function greedyFitFiles(rankedFiles, tokenBudget) {
161293
- const selected = [];
161294
- let remainingBudget = tokenBudget;
161295
- for (const file2 of rankedFiles) {
161296
- if (file2.tokens <= 0)
161767
+ function collectKeyFileCandidates(args) {
161768
+ const primaryIds = primarySessionIds(args.magicDb);
161769
+ if (primaryIds.size === 0)
161770
+ return [];
161771
+ const reads = args.openCodeDb.prepare(`SELECT p.session_id,
161772
+ json_extract(json_extract(p.data, '$.state'), '$.input.filePath') AS file_path,
161773
+ json_extract(json_extract(p.data, '$.state'), '$.input.startLine') AS start_line,
161774
+ json_extract(json_extract(p.data, '$.state'), '$.input.endLine') AS end_line,
161775
+ json_extract(json_extract(p.data, '$.state'), '$.input.offset') AS offset_value,
161776
+ json_extract(json_extract(p.data, '$.state'), '$.input.limit') AS limit_value,
161777
+ LENGTH(json_extract(json_extract(p.data, '$.state'), '$.output')) AS output_bytes,
161778
+ p.time_created
161779
+ FROM part p
161780
+ WHERE json_extract(p.data, '$.type') = 'tool'
161781
+ AND json_extract(p.data, '$.tool') = 'read'
161782
+ AND json_extract(json_extract(p.data, '$.state'), '$.input.filePath') IS NOT NULL`).all();
161783
+ const byPath = new Map;
161784
+ for (const row of reads) {
161785
+ if (!primaryIds.has(row.session_id) || !row.file_path)
161297
161786
  continue;
161298
- if (file2.tokens > remainingBudget)
161787
+ const rel = normalizeProjectRelativePath(args.projectPath, row.file_path);
161788
+ if (!rel)
161299
161789
  continue;
161300
- selected.push({ filePath: file2.filePath, tokens: file2.tokens });
161301
- remainingBudget -= file2.tokens;
161302
- if (remainingBudget <= 0)
161303
- break;
161790
+ const timestamp = toMs(row.time_created);
161791
+ const candidate = byPath.get(rel) ?? {
161792
+ path: rel,
161793
+ totalReads: 0,
161794
+ rangedReads: 0,
161795
+ fullReads: 0,
161796
+ editCount: 0,
161797
+ latestReadBytes: 0,
161798
+ firstReadAt: timestamp || Date.now(),
161799
+ lastReadAt: 0,
161800
+ ranges: [],
161801
+ rangeMap: new Map
161802
+ };
161803
+ candidate.totalReads++;
161804
+ candidate.firstReadAt = Math.min(candidate.firstReadAt, timestamp || candidate.firstReadAt);
161805
+ candidate.lastReadAt = Math.max(candidate.lastReadAt, timestamp);
161806
+ if (timestamp >= candidate.lastReadAt)
161807
+ candidate.latestReadBytes = Number(row.output_bytes ?? 0);
161808
+ const start = toPositiveInt(row.start_line) ?? toPositiveInt(row.offset_value);
161809
+ const explicitEnd = toPositiveInt(row.end_line);
161810
+ const limit = toPositiveInt(row.limit_value);
161811
+ const end = explicitEnd ?? (start && limit ? start + limit - 1 : null);
161812
+ if (start && end) {
161813
+ candidate.rangedReads++;
161814
+ const key = `${start}:${end}`;
161815
+ const existing = candidate.rangeMap.get(key);
161816
+ if (existing) {
161817
+ existing.count++;
161818
+ existing.lastReadAt = Math.max(existing.lastReadAt, timestamp);
161819
+ } else {
161820
+ candidate.rangeMap.set(key, { start, end, count: 1, lastReadAt: timestamp });
161821
+ }
161822
+ } else {
161823
+ candidate.fullReads++;
161824
+ }
161825
+ byPath.set(rel, candidate);
161826
+ }
161827
+ const edits = args.openCodeDb.prepare(`SELECT p.session_id,
161828
+ json_extract(json_extract(p.data, '$.state'), '$.input.filePath') AS file_path,
161829
+ COUNT(*) AS edit_count
161830
+ FROM part p
161831
+ WHERE json_extract(p.data, '$.type') = 'tool'
161832
+ AND json_extract(p.data, '$.tool') IN ('edit', 'write', 'mcp_edit', 'mcp_write')
161833
+ AND json_extract(json_extract(p.data, '$.state'), '$.input.filePath') IS NOT NULL
161834
+ GROUP BY p.session_id, file_path`).all();
161835
+ for (const row of edits) {
161836
+ if (!primaryIds.has(row.session_id) || !row.file_path)
161837
+ continue;
161838
+ const rel = normalizeProjectRelativePath(args.projectPath, row.file_path);
161839
+ if (!rel)
161840
+ continue;
161841
+ const candidate = byPath.get(rel);
161842
+ if (candidate)
161843
+ candidate.editCount += Number(row.edit_count ?? 0);
161304
161844
  }
161305
- return selected;
161845
+ return [...byPath.values()].filter((candidate) => candidate.totalReads >= args.minReads).map(({ rangeMap, ...candidate }) => ({
161846
+ ...candidate,
161847
+ ranges: coalesceRanges([...rangeMap.values()])
161848
+ })).sort((a, b) => b.totalReads - a.totalReads || b.lastReadAt - a.lastReadAt).slice(0, 200);
161306
161849
  }
161307
161850
 
161851
+ // ../plugin/src/features/magic-context/key-files/storage-key-files.ts
161852
+ init_logger();
161853
+
161308
161854
  // ../plugin/src/features/magic-context/key-files/identify-key-files.ts
161309
161855
  var KEY_FILES_SYSTEM_PROMPT = "You are a file importance evaluator. Given read statistics about files in a coding session, identify which are core orientation files worth pinning in context. Return a JSON array.";
161310
- function buildKeyFilesPrompt(candidates, tokenBudget, minReads) {
161311
- const statsText = candidates.map((s) => `- **${s.filePath}** — ${s.fullReadCount} full reads, ${s.editCount} edits, ~${s.latestReadTokens} tokens`).join(`
161856
+ class KeyFilesValidationError extends Error {
161857
+ constructor(message) {
161858
+ super(message);
161859
+ this.name = "KeyFilesValidationError";
161860
+ }
161861
+ }
161862
+ function computeGenerationConfigHash(config2) {
161863
+ return sha256(JSON.stringify({ budget: config2.token_budget, min_reads: config2.min_reads }));
161864
+ }
161865
+ function extractJsonObject(text) {
161866
+ const fenced = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
161867
+ if (fenced?.[1])
161868
+ return fenced[1];
161869
+ const object2 = text.match(/\{[\s\S]*\}/);
161870
+ if (object2?.[0])
161871
+ return object2[0];
161872
+ throw new KeyFilesValidationError("missing JSON object");
161873
+ }
161874
+ function formatCandidates(candidates) {
161875
+ return candidates.map((candidate) => {
161876
+ const ranges = candidate.ranges.map((range) => `${range.start}-${range.end} (${range.count}x)`).join(", ");
161877
+ return `| ${candidate.path} | ${candidate.totalReads} | ${new Date(candidate.lastReadAt).toISOString()} | ${ranges || "full reads"} |`;
161878
+ }).join(`
161879
+ `);
161880
+ }
161881
+ function buildV6KeyFilesPrompt(args) {
161882
+ const current = args.currentRows.map((row) => `- ${row.path}: ${row.localTokenEstimate} tokens, stale=${row.staleReason ?? "fresh"}`).join(`
161312
161883
  `);
161313
- return `## Identify Key Files for Pinning
161884
+ return `## Task: Identify project key files
161314
161885
 
161315
- The following files were fully read ${minReads}+ times during a coding session.
161316
- Identify which ones are **core orientation files** worth keeping permanently in context.
161886
+ You are deciding which files this project's primary agent should always see as orientation context. The injection budget is **${args.config.token_budget} tokens** for all files combined.
161317
161887
 
161318
- ### Signals of a core orientation file:
161319
- - Read many times across different phases of work (not clustered in one task)
161320
- - Read without editing — consulted for understanding, not modification
161321
- - Contains architecture, configuration, types, or key abstractions
161888
+ ## Read history (primary sessions only, project-scoped)
161322
161889
 
161323
- ### Signals of a NON-core file (exclude):
161324
- - Read many times but always edited — actively working on it
161325
- - Very large (>5000 tokens) — too expensive to pin
161326
- - Test files, scripts, or generated files
161890
+ | path | total reads | last read | common line ranges |
161891
+ | --- | ---: | --- | --- |
161892
+ ${formatCandidates(args.candidates)}
161327
161893
 
161328
- ### Token budget: ${tokenBudget} tokens total
161894
+ ## Current key files (if any)
161329
161895
 
161330
- ### Files:
161331
- ${statsText}
161896
+ ${current || "(none)"}
161897
+
161898
+ ## Tools available
161899
+
161900
+ - \`aft_outline(target=<path>)\` — get symbol outline for a file
161901
+ - \`aft_zoom(filePath=<path>, symbol=<name>)\` — get full source of one symbol
161902
+
161903
+ ## Output (strict JSON)
161332
161904
 
161333
- ### Output Format
161334
- Return a JSON array ranked by importance (most important first):
161335
161905
  \`\`\`json
161336
- [
161337
- {"filePath": "src/path/to/file.ts", "tokens": 2500, "reason": "brief reason"}
161338
- ]
161906
+ {
161907
+ "no_change": false,
161908
+ "files": [
161909
+ {
161910
+ "path": "src/example.ts",
161911
+ "content": "...outline + symbol bodies stitched from src/example.ts only...",
161912
+ "approx_token_estimate": 1200
161913
+ }
161914
+ ]
161915
+ }
161339
161916
  \`\`\`
161340
161917
 
161341
- Only include files you're confident are true orientation files. Return empty array if none qualify.`;
161918
+ If current key files are still optimal, return \`{ "no_change": true, "files": [] }\`.
161919
+
161920
+ Rules:
161921
+ - Each row's content MUST come from ONLY the one file at path. If two files matter, emit two rows.
161922
+ - Do NOT emit source_files. One row = one file = one hash.
161923
+ - Total approx_token_estimate must be < ${args.config.token_budget}.
161924
+ - path must be relative, no .., no absolute paths, no symlinks escaping project root.
161925
+ - path must be unique case-sensitively and case-insensitively.
161926
+ - content must be plain text; no XML tags inside.`;
161927
+ }
161928
+ function validateLlmOutput(raw, config2, projectPath) {
161929
+ let obj;
161930
+ try {
161931
+ obj = JSON.parse(extractJsonObject(raw));
161932
+ } catch (error51) {
161933
+ if (error51 instanceof KeyFilesValidationError)
161934
+ throw error51;
161935
+ throw new KeyFilesValidationError(`invalid JSON: ${getErrorMessage(error51)}`);
161936
+ }
161937
+ if (!obj || typeof obj !== "object")
161938
+ throw new KeyFilesValidationError("output must be object");
161939
+ const record2 = obj;
161940
+ if (typeof record2.no_change !== "boolean")
161941
+ throw new KeyFilesValidationError("missing no_change");
161942
+ if (!Array.isArray(record2.files))
161943
+ throw new KeyFilesValidationError("missing files array");
161944
+ if (record2.no_change && record2.files.length > 0)
161945
+ throw new KeyFilesValidationError("no_change=true with files");
161946
+ if (!record2.no_change && record2.files.length === 0)
161947
+ throw new KeyFilesValidationError("no_change=false with empty files");
161948
+ const seen = new Set;
161949
+ const seenLower = new Set;
161950
+ const files = [];
161951
+ for (const item of record2.files) {
161952
+ if (!item || typeof item !== "object")
161953
+ throw new KeyFilesValidationError("bad file entry");
161954
+ const file2 = item;
161955
+ if ("source_files" in file2) {
161956
+ throw new KeyFilesValidationError(`source_files field not allowed (one file per row): ${String(file2.path)}`);
161957
+ }
161958
+ if (typeof file2.path !== "string" || file2.path.length === 0)
161959
+ throw new KeyFilesValidationError("bad path");
161960
+ if (file2.path.startsWith("/") || file2.path.includes(".."))
161961
+ throw new KeyFilesValidationError(`escape: ${file2.path}`);
161962
+ if (seen.has(file2.path))
161963
+ throw new KeyFilesValidationError(`dup path: ${file2.path}`);
161964
+ if (seenLower.has(file2.path.toLowerCase()))
161965
+ throw new KeyFilesValidationError(`case-dup: ${file2.path}`);
161966
+ seen.add(file2.path);
161967
+ seenLower.add(file2.path.toLowerCase());
161968
+ if (!isRelativeProjectFile(projectPath, file2.path))
161969
+ throw new KeyFilesValidationError(`unreadable: ${file2.path}`);
161970
+ if (typeof file2.content !== "string")
161971
+ throw new KeyFilesValidationError(`bad content: ${file2.path}`);
161972
+ if (file2.content.length > 1e5)
161973
+ throw new KeyFilesValidationError(`content >100KB: ${file2.path}`);
161974
+ if (typeof file2.approx_token_estimate !== "number" || file2.approx_token_estimate < 0) {
161975
+ throw new KeyFilesValidationError(`bad token estimate: ${file2.path}`);
161976
+ }
161977
+ const local = estimateTokens(file2.content);
161978
+ if (file2.approx_token_estimate > 0 && (local / file2.approx_token_estimate > 1.5 || local / file2.approx_token_estimate < 0.5)) {
161979
+ log(`key-files: token estimate divergence for ${file2.path}: claimed=${file2.approx_token_estimate}, plugin=${local}`);
161980
+ }
161981
+ files.push({
161982
+ path: file2.path,
161983
+ content: file2.content,
161984
+ approx_token_estimate: file2.approx_token_estimate,
161985
+ local_token_estimate: local
161986
+ });
161987
+ }
161988
+ const total = files.reduce((sum, file2) => sum + file2.local_token_estimate, 0);
161989
+ if (total > config2.token_budget)
161990
+ throw new KeyFilesValidationError(`total ${total} > budget ${config2.token_budget}`);
161991
+ return { no_change: record2.no_change, files };
161342
161992
  }
161343
- function parseKeyFilesOutput(text) {
161344
- const jsonMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/) ?? text.match(/\[[\s\S]*\]/);
161345
- if (!jsonMatch)
161346
- return [];
161993
+ function commitKeyFiles(args) {
161994
+ if (args.validated.no_change)
161995
+ return null;
161996
+ const projectPath = resolveProjectPath(args.projectPath) ?? args.projectPath;
161997
+ const resolved = resolveCommitFiles(projectPath, args.validated.files.map((file2) => ({
161998
+ path: file2.path,
161999
+ content: file2.content,
162000
+ localTokenEstimate: file2.local_token_estimate
162001
+ })));
162002
+ const generatedAt = Date.now();
162003
+ const bump = args.bumpVersion ?? bumpKeyFilesVersion;
162004
+ args.db.exec("BEGIN IMMEDIATE");
162005
+ let committed = false;
161347
162006
  try {
161348
- const raw = jsonMatch[1] ?? jsonMatch[0];
161349
- const parsed = JSON.parse(raw);
161350
- if (!Array.isArray(parsed))
161351
- return [];
161352
- return parsed.filter((item) => typeof item === "object" && item !== null && typeof item.filePath === "string" && typeof item.tokens === "number").map((item) => ({ filePath: item.filePath, tokens: item.tokens }));
161353
- } catch {
161354
- return [];
162007
+ if (!peekLeaseHolderAndExpiry(args.db, args.leaseHolderId)) {
162008
+ log(`key-files commit aborted: lease lost (holder ${args.leaseHolderId})`);
162009
+ return null;
162010
+ }
162011
+ args.db.prepare("DELETE FROM project_key_files WHERE project_path = ?").run(projectPath);
162012
+ insertResolvedKeyFiles(args.db, projectPath, resolved, generatedAt, args.modelId, args.configHash);
162013
+ const version2 = bump(args.db, projectPath);
162014
+ args.db.exec("COMMIT");
162015
+ committed = true;
162016
+ log(`key-files committed: ${resolved.length} files, version=${version2}, ${resolved.filter((r) => r.staleReason).length} pre-stale`);
162017
+ return version2;
162018
+ } finally {
162019
+ if (!committed) {
162020
+ try {
162021
+ args.db.exec("ROLLBACK");
162022
+ } catch {}
162023
+ }
162024
+ }
162025
+ }
162026
+ async function runKeyFilesLlm(args) {
162027
+ const createResponse = await args.client.session.create({
162028
+ body: {
162029
+ ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
162030
+ title: "magic-context-dream-key-files-v6"
162031
+ },
162032
+ query: { directory: args.projectPath }
162033
+ });
162034
+ const created = normalizeSDKResponse(createResponse, null, {
162035
+ preferResponseOnMissingData: true
162036
+ });
162037
+ const agentSessionId = typeof created?.id === "string" ? created.id : null;
162038
+ if (!agentSessionId)
162039
+ throw new Error("Could not create key-file identification session.");
162040
+ try {
162041
+ await promptSyncWithModelSuggestionRetry(args.client, {
162042
+ path: { id: agentSessionId },
162043
+ query: { directory: args.projectPath },
162044
+ body: {
162045
+ agent: DREAMER_AGENT,
162046
+ system: KEY_FILES_SYSTEM_PROMPT,
162047
+ parts: [{ type: "text", text: args.prompt, synthetic: true }]
162048
+ }
162049
+ }, {
162050
+ timeoutMs: Math.min(Math.max(0, args.deadline - Date.now()), 5 * 60 * 1000),
162051
+ fallbackModels: args.fallbackModels,
162052
+ callContext: "dreamer:key-files-v6"
162053
+ });
162054
+ const messagesResponse = await args.client.session.messages({
162055
+ path: { id: agentSessionId },
162056
+ query: { directory: args.projectPath }
162057
+ });
162058
+ const messages = normalizeSDKResponse(messagesResponse, [], {
162059
+ preferResponseOnMissingData: true
162060
+ });
162061
+ const text = extractLatestAssistantText(messages);
162062
+ if (!text)
162063
+ throw new Error("Dreamer returned no key-files output.");
162064
+ return text;
162065
+ } finally {
162066
+ await args.client.session.delete({ path: { id: agentSessionId } }).catch(() => {
162067
+ return;
162068
+ });
161355
162069
  }
161356
162070
  }
161357
- function getKeyFileCandidates(openCodeDb, sessionId, minReads, tokenBudget, projectDirectory) {
161358
- const stats = getSessionReadStats(openCodeDb, sessionId, minReads);
161359
- const maxPerFileTokens = Math.min(tokenBudget / 2, 5000);
161360
- const projectPrefix = projectDirectory ? `${projectDirectory.replace(/\/$/, "")}/` : undefined;
161361
- return stats.filter((s) => s.latestReadTokens > 0 && s.latestReadTokens <= maxPerFileTokens && (!projectPrefix || s.filePath.startsWith(projectPrefix)));
161362
- }
161363
- function applyKeyFileResults(db, sessionId, llmRanked, tokenBudget, candidatePaths) {
161364
- const filtered = candidatePaths ? llmRanked.filter((f) => candidatePaths.has(f.filePath)) : llmRanked;
161365
- const selected = greedyFitFiles(filtered, tokenBudget);
161366
- setKeyFiles(db, sessionId, selected);
161367
- const totalTokens = selected.reduce((sum, f) => sum + f.tokens, 0);
161368
- log(`[key-files][${sessionId}] pinned ${selected.length} files (${totalTokens} tokens): ${selected.map((f) => f.filePath).join(", ")}`);
161369
- return { filesIdentified: selected.length, totalTokens };
161370
- }
161371
- function heuristicKeyFileSelection(db, sessionId, candidates, tokenBudget) {
161372
- const scored = candidates.map((c) => ({
161373
- filePath: c.filePath,
161374
- tokens: c.latestReadTokens,
161375
- score: c.fullReadCount * 2 - c.editCount * 3
161376
- })).filter((c) => c.score > 0).sort((a, b) => b.score - a.score);
161377
- const selected = greedyFitFiles(scored, tokenBudget);
161378
- setKeyFiles(db, sessionId, selected);
161379
- const totalTokens = selected.reduce((sum, f) => sum + f.tokens, 0);
161380
- log(`[key-files][${sessionId}] heuristic pinned ${selected.length} files (${totalTokens} tokens)`);
161381
- return { filesIdentified: selected.length, totalTokens };
162071
+ async function runKeyFilesTask(args) {
162072
+ if (!args.config.enabled)
162073
+ return { committedVersion: null, candidates: 0, noChange: false };
162074
+ if (!isAftAvailable()) {
162075
+ log("[key-files] AFT not available, skipping key-files task");
162076
+ return { committedVersion: null, candidates: 0, noChange: false };
162077
+ }
162078
+ const projectPath = resolveProjectPath(args.projectPath) ?? args.projectPath;
162079
+ const candidates = collectKeyFileCandidates({
162080
+ openCodeDb: args.openCodeDb,
162081
+ magicDb: args.db,
162082
+ projectPath,
162083
+ minReads: args.config.min_reads
162084
+ });
162085
+ if (candidates.length === 0)
162086
+ return { committedVersion: null, candidates: 0, noChange: false };
162087
+ const currentRows = readCurrentKeyFiles(args.db, projectPath);
162088
+ const configHash = computeGenerationConfigHash(args.config);
162089
+ const allRowsFreshAndCurrent = currentRows.length > 0 && currentRows.every((row) => {
162090
+ if (row.staleReason !== null || row.generationConfigHash !== configHash)
162091
+ return false;
162092
+ try {
162093
+ return sha256(readFileSync4(join10(projectPath, row.path))) === row.contentHash;
162094
+ } catch {
162095
+ return false;
162096
+ }
162097
+ });
162098
+ const currentPaths = new Set(currentRows.map((row) => row.path));
162099
+ if (allRowsFreshAndCurrent && candidates.every((candidate) => currentPaths.has(candidate.path))) {
162100
+ log(`key-files: no_change short-circuit (${currentRows.length} rows fresh)`);
162101
+ return { committedVersion: null, candidates: candidates.length, noChange: true };
162102
+ }
162103
+ const prompt = buildV6KeyFilesPrompt({ candidates, currentRows, config: args.config });
162104
+ let validated;
162105
+ const leaseInterval = setInterval(() => {
162106
+ try {
162107
+ if (!renewLease(args.db, args.holderId)) {
162108
+ log("[key-files] lease renewal failed during LLM call");
162109
+ }
162110
+ } catch (error51) {
162111
+ log(`[key-files] lease renewal threw: ${getErrorMessage(error51)}`);
162112
+ }
162113
+ }, 60000);
162114
+ try {
162115
+ try {
162116
+ const raw = await runKeyFilesLlm({
162117
+ client: args.client,
162118
+ parentSessionId: args.parentSessionId,
162119
+ projectPath,
162120
+ prompt,
162121
+ deadline: args.deadline,
162122
+ fallbackModels: args.fallbackModels
162123
+ });
162124
+ validated = validateLlmOutput(raw, args.config, projectPath);
162125
+ } catch (error51) {
162126
+ log(`[key-files] LLM validation failed: ${getErrorMessage(error51)}`);
162127
+ throw error51;
162128
+ }
162129
+ if (validated.no_change)
162130
+ return { committedVersion: null, candidates: candidates.length, noChange: true };
162131
+ const committedVersion = commitKeyFiles({
162132
+ db: args.db,
162133
+ projectPath,
162134
+ validated,
162135
+ configHash,
162136
+ modelId: args.fallbackModels?.[0] ?? "dreamer",
162137
+ leaseHolderId: args.holderId
162138
+ });
162139
+ renewLease(args.db, args.holderId);
162140
+ return { committedVersion, candidates: candidates.length, noChange: false };
162141
+ } finally {
162142
+ clearInterval(leaseInterval);
162143
+ }
161382
162144
  }
161383
162145
 
161384
162146
  // ../plugin/src/features/magic-context/memory/embedding-cache.ts
@@ -162442,11 +163204,11 @@ function logWithStackHead(message, stackHead) {
162442
163204
  log(message, stackHead ? { stackHead } : undefined);
162443
163205
  }
162444
163206
  function getOpenCodeDbPath2() {
162445
- return join9(getDataDir(), "opencode", "opencode.db");
163207
+ return join11(getDataDir(), "opencode", "opencode.db");
162446
163208
  }
162447
163209
  function openOpenCodeDb() {
162448
163210
  const dbPath = getOpenCodeDbPath2();
162449
- if (!existsSync6(dbPath)) {
163211
+ if (!existsSync8(dbPath)) {
162450
163212
  log(`[key-files] OpenCode DB not found at ${dbPath} — skipping`);
162451
163213
  return null;
162452
163214
  }
@@ -162459,178 +163221,6 @@ function openOpenCodeDb() {
162459
163221
  return null;
162460
163222
  }
162461
163223
  }
162462
- function isSessionIdRow(row) {
162463
- if (row === null || typeof row !== "object") {
162464
- return false;
162465
- }
162466
- return typeof row.sessionId === "string";
162467
- }
162468
- function hasExplicitEmptyKeyFilesOutput(text) {
162469
- return /```(?:json)?\s*\[\s*\]\s*```/s.test(text) || /^\s*\[\s*\]\s*$/s.test(text);
162470
- }
162471
- async function getActiveProjectSessionIds(args) {
162472
- try {
162473
- const { withReadOnlySessionDb: withReadOnlySessionDb2 } = await init_read_session_db().then(() => exports_read_session_db);
162474
- const projectSessionIds = withReadOnlySessionDb2((openCodeDb) => {
162475
- const bareIdentity = args.projectIdentity.replace(/^git:/, "");
162476
- const rows = openCodeDb.prepare("SELECT id FROM session WHERE project_id = ? AND parent_id IS NULL ORDER BY time_updated DESC").all(bareIdentity);
162477
- return new Set(rows.map((r) => r.id));
162478
- });
162479
- if (projectSessionIds.size === 0) {
162480
- return [];
162481
- }
162482
- return args.db.prepare("SELECT session_id AS sessionId FROM session_meta WHERE is_subagent = 0 ORDER BY session_id ASC").all().filter(isSessionIdRow).map((row) => row.sessionId).filter((sessionId) => projectSessionIds.has(sessionId));
162483
- } catch (error51) {
162484
- sessionLog(args.projectIdentity, `key-files: OpenCode DB lookup failed, falling back to SDK list: ${getErrorMessage(error51)}`);
162485
- const listResponse = await args.client.session.list({
162486
- query: { directory: args.sessionDirectory ?? args.projectIdentity }
162487
- });
162488
- const sessions = normalizeSDKResponse(listResponse, [], {
162489
- preferResponseOnMissingData: true
162490
- });
162491
- const projectSessionIds = new Set(sessions.map((session) => typeof session?.id === "string" ? session.id : null).filter((sessionId) => Boolean(sessionId)));
162492
- if (projectSessionIds.size === 0) {
162493
- return [];
162494
- }
162495
- return args.db.prepare("SELECT session_id AS sessionId FROM session_meta WHERE is_subagent = 0 ORDER BY session_id ASC").all().filter(isSessionIdRow).map((row) => row.sessionId).filter((sessionId) => projectSessionIds.has(sessionId));
162496
- }
162497
- }
162498
- async function identifyKeyFilesForSession(args) {
162499
- let openCodeDb = null;
162500
- try {
162501
- openCodeDb = openOpenCodeDb();
162502
- if (!openCodeDb) {
162503
- return;
162504
- }
162505
- const candidates = getKeyFileCandidates(openCodeDb, args.sessionId, args.config.min_reads, args.config.token_budget, args.sessionDirectory);
162506
- if (candidates.length === 0) {
162507
- log(`[key-files][${args.sessionId}] no candidates found — skipping`);
162508
- return;
162509
- }
162510
- const prompt = buildKeyFilesPrompt(candidates, args.config.token_budget, args.config.min_reads);
162511
- const applyHeuristicFallback = () => {
162512
- heuristicKeyFileSelection(args.db, args.sessionId, candidates, args.config.token_budget);
162513
- };
162514
- let agentSessionId = null;
162515
- const abortController = new AbortController;
162516
- const leaseInterval = setInterval(() => {
162517
- try {
162518
- if (!renewLease(args.db, args.holderId)) {
162519
- log(`[key-files][${args.sessionId}] lease renewal failed — aborting`);
162520
- abortController.abort();
162521
- }
162522
- } catch {
162523
- abortController.abort();
162524
- }
162525
- }, 60000);
162526
- try {
162527
- const createResponse = await args.client.session.create({
162528
- body: {
162529
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
162530
- title: `magic-context-dream-key-files-${args.sessionId.slice(0, 12)}`
162531
- },
162532
- query: { directory: args.sessionDirectory }
162533
- });
162534
- const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
162535
- agentSessionId = typeof created?.id === "string" ? created.id : null;
162536
- if (!agentSessionId) {
162537
- throw new Error("Could not create key-file identification session.");
162538
- }
162539
- log(`[key-files][${args.sessionId}] child session created ${agentSessionId}`);
162540
- const remainingMs = Math.max(0, args.deadline - Date.now());
162541
- await promptSyncWithModelSuggestionRetry(args.client, {
162542
- path: { id: agentSessionId },
162543
- query: { directory: args.sessionDirectory },
162544
- body: {
162545
- agent: DREAMER_AGENT,
162546
- system: KEY_FILES_SYSTEM_PROMPT,
162547
- parts: [{ type: "text", text: prompt, synthetic: true }]
162548
- }
162549
- }, {
162550
- timeoutMs: Math.min(remainingMs, 300000),
162551
- signal: abortController.signal,
162552
- fallbackModels: args.fallbackModels,
162553
- callContext: `dreamer:key-files:${args.sessionId.slice(0, 12)}`
162554
- });
162555
- const messagesResponse = await args.client.session.messages({
162556
- path: { id: agentSessionId },
162557
- query: { directory: args.sessionDirectory }
162558
- });
162559
- const messages = normalizeSDKResponse(messagesResponse, [], {
162560
- preferResponseOnMissingData: true
162561
- });
162562
- const responseText = extractLatestAssistantText(messages);
162563
- if (!responseText) {
162564
- log(`[key-files][${args.sessionId}] no response from agent — using heuristic fallback`);
162565
- applyHeuristicFallback();
162566
- return;
162567
- }
162568
- const parsed = parseKeyFilesOutput(responseText);
162569
- if (parsed.length > 0 || hasExplicitEmptyKeyFilesOutput(responseText)) {
162570
- const candidatePaths = new Set(candidates.map((c) => c.filePath));
162571
- applyKeyFileResults(args.db, args.sessionId, parsed, args.config.token_budget, candidatePaths);
162572
- return;
162573
- }
162574
- log(`[key-files][${args.sessionId}] could not parse agent output — using heuristic fallback`);
162575
- applyHeuristicFallback();
162576
- } catch (error51) {
162577
- log(`[key-files][${args.sessionId}] identification failed: ${getErrorMessage(error51)} — using heuristic fallback`);
162578
- try {
162579
- applyHeuristicFallback();
162580
- } catch (fallbackError) {
162581
- log(`[key-files][${args.sessionId}] heuristic fallback failed: ${getErrorMessage(fallbackError)}`);
162582
- }
162583
- } finally {
162584
- clearInterval(leaseInterval);
162585
- if (agentSessionId) {
162586
- await args.client.session.delete({
162587
- path: { id: agentSessionId },
162588
- query: { directory: args.sessionDirectory }
162589
- }).catch((error51) => {
162590
- log(`[key-files][${args.sessionId}] session cleanup failed: ${getErrorMessage(error51)}`);
162591
- });
162592
- }
162593
- }
162594
- } finally {
162595
- if (openCodeDb) {
162596
- try {
162597
- closeQuietly(openCodeDb);
162598
- } catch (error51) {
162599
- log(`[key-files][${args.sessionId}] failed to close OpenCode DB: ${getErrorMessage(error51)}`);
162600
- }
162601
- }
162602
- }
162603
- }
162604
- async function identifyKeyFiles(args) {
162605
- const sessionIds = await getActiveProjectSessionIds({
162606
- db: args.db,
162607
- client: args.client,
162608
- projectIdentity: args.projectIdentity,
162609
- sessionDirectory: args.sessionDirectory
162610
- });
162611
- if (sessionIds.length === 0) {
162612
- log(`[key-files] no active sessions found for ${args.projectIdentity}`);
162613
- return;
162614
- }
162615
- log(`[key-files] evaluating ${sessionIds.length} active session(s) for ${args.projectIdentity}`);
162616
- for (const sessionId of sessionIds) {
162617
- if (Date.now() > args.deadline) {
162618
- log("[key-files] deadline reached — stopping key-file identification");
162619
- break;
162620
- }
162621
- await identifyKeyFilesForSession({
162622
- db: args.db,
162623
- client: args.client,
162624
- parentSessionId: args.parentSessionId,
162625
- sessionDirectory: args.sessionDirectory,
162626
- holderId: args.holderId,
162627
- fallbackModels: args.fallbackModels,
162628
- deadline: args.deadline,
162629
- sessionId,
162630
- config: args.config
162631
- });
162632
- }
162633
- }
162634
163224
  async function runDream(args) {
162635
163225
  const holderId = crypto.randomUUID();
162636
163226
  const startedAt = Date.now();
@@ -162681,8 +163271,50 @@ async function runDream(args) {
162681
163271
  let lastErrorSignature = null;
162682
163272
  let consecutiveSameErrorFailures = 0;
162683
163273
  let circuitBreakerTripped = false;
163274
+ let lostLease = false;
163275
+ let lostLeaseReason = null;
163276
+ let lostLeaseRecorded = false;
163277
+ const markLeaseLost = (phase, error51) => {
163278
+ const detail = error51 ? `: ${getErrorMessage(error51)}` : "";
163279
+ lostLeaseReason = `Dream lease lost during ${phase}${detail}`;
163280
+ if (!lostLease) {
163281
+ log(`[dreamer] FATAL: ${lostLeaseReason}; aborting all remaining dream work`);
163282
+ } else {
163283
+ log(`[dreamer] FATAL: ${lostLeaseReason}; dream work is already aborting`);
163284
+ }
163285
+ lostLease = true;
163286
+ };
163287
+ const recordLeaseLostTask = (phase) => {
163288
+ if (lostLeaseRecorded)
163289
+ return;
163290
+ lostLeaseRecorded = true;
163291
+ result.tasks.push({
163292
+ name: "lease-lost",
163293
+ durationMs: 0,
163294
+ result: "",
163295
+ error: lostLeaseReason ?? `Dream lease lost during ${phase}; aborted remaining work`
163296
+ });
163297
+ };
163298
+ const verifyLeaseStillHeld = (phase) => {
163299
+ if (lostLease)
163300
+ return false;
163301
+ try {
163302
+ if (!renewLease(args.db, holderId)) {
163303
+ markLeaseLost(phase);
163304
+ return false;
163305
+ }
163306
+ return true;
163307
+ } catch (error51) {
163308
+ markLeaseLost(phase, error51);
163309
+ return false;
163310
+ }
163311
+ };
162684
163312
  try {
162685
163313
  for (const taskName of args.tasks) {
163314
+ if (!verifyLeaseStillHeld(`before task ${taskName}`)) {
163315
+ recordLeaseLostTask(`before task ${taskName}`);
163316
+ break;
163317
+ }
162686
163318
  if (Date.now() > deadline) {
162687
163319
  log(`[dreamer] deadline reached, stopping after ${result.tasks.length} tasks`);
162688
163320
  break;
@@ -162695,18 +163327,20 @@ async function runDream(args) {
162695
163327
  try {
162696
163328
  if (!renewLease(args.db, holderId)) {
162697
163329
  log(`[dreamer] task ${taskName}: lease renewal failed — aborting LLM call`);
163330
+ markLeaseLost(`task ${taskName} lease renewal`);
162698
163331
  taskAbortController.abort();
162699
163332
  }
162700
163333
  } catch (err) {
162701
163334
  log(`[dreamer] task ${taskName}: lease renewal threw — aborting LLM call: ${err}`);
163335
+ markLeaseLost(`task ${taskName} lease renewal`, err);
162702
163336
  taskAbortController.abort();
162703
163337
  }
162704
163338
  }, 60000);
162705
163339
  try {
162706
163340
  const docsDir = args.sessionDirectory ?? args.projectIdentity;
162707
163341
  const existingDocs = taskName === "maintain-docs" ? {
162708
- architecture: existsSync6(join9(docsDir, "ARCHITECTURE.md")),
162709
- structure: existsSync6(join9(docsDir, "STRUCTURE.md"))
163342
+ architecture: existsSync8(join11(docsDir, "ARCHITECTURE.md")),
163343
+ structure: existsSync8(join11(docsDir, "STRUCTURE.md"))
162710
163344
  } : undefined;
162711
163345
  const userMemories = taskName === "archive-stale" ? getActiveUserMemories(args.db).map((um) => ({
162712
163346
  id: um.id,
@@ -162745,6 +163379,9 @@ async function runDream(args) {
162745
163379
  fallbackModels: args.fallbackModels,
162746
163380
  callContext: `dreamer:${taskName}`
162747
163381
  });
163382
+ if (lostLease) {
163383
+ throw new Error(lostLeaseReason ?? `Dream lease lost during ${taskName}`);
163384
+ }
162748
163385
  const messagesResponse = await args.client.session.messages({
162749
163386
  path: { id: agentSessionId },
162750
163387
  query: { directory: args.sessionDirectory ?? args.projectIdentity }
@@ -162775,7 +163412,10 @@ async function runDream(args) {
162775
163412
  result: null,
162776
163413
  error: errorDescription.brief
162777
163414
  });
162778
- if (shouldSkipCircuitBreaker(error51, errorDescription.brief)) {
163415
+ if (lostLease) {
163416
+ lastErrorSignature = null;
163417
+ consecutiveSameErrorFailures = 0;
163418
+ } else if (shouldSkipCircuitBreaker(error51, errorDescription.brief)) {
162779
163419
  lastErrorSignature = null;
162780
163420
  consecutiveSameErrorFailures = 0;
162781
163421
  } else {
@@ -162801,18 +163441,24 @@ async function runDream(args) {
162801
163441
  clearInterval(leaseRenewalInterval);
162802
163442
  if (agentSessionId) {
162803
163443
  await args.client.session.delete({
162804
- path: { id: agentSessionId },
162805
- query: { directory: args.sessionDirectory ?? args.projectIdentity }
163444
+ path: { id: agentSessionId }
162806
163445
  }).catch((error51) => {
162807
163446
  log("[dreamer] failed to delete child session:", error51);
162808
163447
  });
162809
163448
  }
162810
163449
  }
163450
+ if (lostLease) {
163451
+ recordLeaseLostTask(`task ${taskName}`);
163452
+ break;
163453
+ }
162811
163454
  if (circuitBreakerTripped) {
162812
163455
  break;
162813
163456
  }
162814
163457
  }
162815
- if (circuitBreakerTripped) {
163458
+ if (lostLease) {
163459
+ log("[dreamer] lease lost: skipping all post-task phases");
163460
+ recordLeaseLostTask("post-task phases");
163461
+ } else if (circuitBreakerTripped) {
162816
163462
  log("[dreamer] circuit breaker: skipping post-task phases");
162817
163463
  result.tasks.push({
162818
163464
  name: "post-task-phases",
@@ -162821,9 +163467,12 @@ async function runDream(args) {
162821
163467
  error: "Skipped post-task phases after circuit breaker tripped; configure dreamer model/fallback_models in magic-context.jsonc."
162822
163468
  });
162823
163469
  }
162824
- if (!circuitBreakerTripped && args.experimentalUserMemories?.enabled && Date.now() <= deadline) {
163470
+ if (!circuitBreakerTripped && !lostLease && args.experimentalUserMemories?.enabled && Date.now() <= deadline) {
162825
163471
  const umStart = Date.now();
162826
163472
  try {
163473
+ if (!verifyLeaseStillHeld("before user-memory review")) {
163474
+ throw new Error(lostLeaseReason ?? "Dream lease lost before user-memory review");
163475
+ }
162827
163476
  const reviewResult = await reviewUserMemories({
162828
163477
  db: args.db,
162829
163478
  client: args.client,
@@ -162834,6 +163483,9 @@ async function runDream(args) {
162834
163483
  promotionThreshold: args.experimentalUserMemories.promotionThreshold,
162835
163484
  fallbackModels: args.fallbackModels
162836
163485
  });
163486
+ if (!verifyLeaseStillHeld("after user-memory review")) {
163487
+ throw new Error(lostLeaseReason ?? "Dream lease lost after user-memory review");
163488
+ }
162837
163489
  const umOutput = `promoted=${reviewResult.promoted} merged=${reviewResult.merged} dismissed=${reviewResult.dismissed} consumed=${reviewResult.candidatesConsumed}`;
162838
163490
  if (reviewResult.promoted > 0 || reviewResult.merged > 0 || reviewResult.dismissed > 0) {
162839
163491
  log(`[dreamer] user-memories: ${umOutput}`);
@@ -162853,9 +163505,14 @@ async function runDream(args) {
162853
163505
  error: errorDescription.brief
162854
163506
  });
162855
163507
  }
163508
+ if (lostLease)
163509
+ recordLeaseLostTask("user-memory review");
162856
163510
  }
162857
- if (!circuitBreakerTripped && Date.now() <= deadline) {
163511
+ if (!circuitBreakerTripped && !lostLease && Date.now() <= deadline) {
162858
163512
  try {
163513
+ if (!verifyLeaseStillHeld("before smart-note evaluation")) {
163514
+ throw new Error(lostLeaseReason ?? "Dream lease lost before smart-note evaluation");
163515
+ }
162859
163516
  await evaluateSmartNotes({
162860
163517
  db: args.db,
162861
163518
  client: args.client,
@@ -162865,27 +163522,47 @@ async function runDream(args) {
162865
163522
  holderId,
162866
163523
  deadline,
162867
163524
  result,
162868
- fallbackModels: args.fallbackModels
163525
+ fallbackModels: args.fallbackModels,
163526
+ onLeaseLost: markLeaseLost,
163527
+ isLeaseLost: () => lostLease
162869
163528
  });
163529
+ if (!verifyLeaseStillHeld("after smart-note evaluation")) {
163530
+ throw new Error(lostLeaseReason ?? "Dream lease lost after smart-note evaluation");
163531
+ }
162870
163532
  } catch (error51) {
162871
163533
  const errorDescription = describeError(error51);
162872
163534
  logWithStackHead(`[dreamer] smart note evaluation failed: ${errorDescription.brief}`, errorDescription.stackHead);
162873
163535
  }
163536
+ if (lostLease)
163537
+ recordLeaseLostTask("smart-note evaluation");
162874
163538
  }
162875
- if (!circuitBreakerTripped && args.experimentalPinKeyFiles?.enabled && Date.now() <= deadline) {
163539
+ if (!circuitBreakerTripped && !lostLease && args.experimentalPinKeyFiles?.enabled && Date.now() <= deadline) {
162876
163540
  const kfStart = Date.now();
162877
163541
  try {
162878
- await identifyKeyFiles({
162879
- db: args.db,
162880
- client: args.client,
162881
- projectIdentity: args.projectIdentity,
162882
- parentSessionId,
162883
- sessionDirectory: args.sessionDirectory ?? args.projectIdentity,
162884
- holderId,
162885
- deadline,
162886
- config: args.experimentalPinKeyFiles,
162887
- fallbackModels: args.fallbackModels
162888
- });
163542
+ if (!verifyLeaseStillHeld("before key-file identification")) {
163543
+ throw new Error(lostLeaseReason ?? "Dream lease lost before key-file identification");
163544
+ }
163545
+ const openCodeDb = openOpenCodeDb();
163546
+ if (openCodeDb) {
163547
+ try {
163548
+ await runKeyFilesTask({
163549
+ db: args.db,
163550
+ openCodeDb,
163551
+ client: args.client,
163552
+ projectPath: args.sessionDirectory ?? args.projectIdentity,
163553
+ holderId,
163554
+ deadline,
163555
+ parentSessionId,
163556
+ config: args.experimentalPinKeyFiles,
163557
+ fallbackModels: args.fallbackModels
163558
+ });
163559
+ } finally {
163560
+ closeQuietly(openCodeDb);
163561
+ }
163562
+ }
163563
+ if (!verifyLeaseStillHeld("after key-file identification")) {
163564
+ throw new Error(lostLeaseReason ?? "Dream lease lost after key-file identification");
163565
+ }
162889
163566
  result.tasks.push({
162890
163567
  name: "key files",
162891
163568
  durationMs: Date.now() - kfStart,
@@ -162901,6 +163578,8 @@ async function runDream(args) {
162901
163578
  error: errorDescription.brief
162902
163579
  });
162903
163580
  }
163581
+ if (lostLease)
163582
+ recordLeaseLostTask("key-file identification");
162904
163583
  }
162905
163584
  } finally {
162906
163585
  releaseLease(args.db, holderId);
@@ -162941,7 +163620,7 @@ async function runDream(args) {
162941
163620
  "circuit-breaker"
162942
163621
  ]);
162943
163622
  const hasSuccessfulTask = result.tasks.some((t) => !t.error && !POST_TASK_NAMES.has(t.name));
162944
- if (hasSuccessfulTask) {
163623
+ if (hasSuccessfulTask && !lostLease) {
162945
163624
  setDreamState(args.db, `last_dream_at:${args.projectIdentity}`, String(result.finishedAt));
162946
163625
  setDreamState(args.db, "last_dream_at", String(result.finishedAt));
162947
163626
  }
@@ -162990,9 +163669,11 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
162990
163669
  try {
162991
163670
  if (!renewLease(args.db, args.holderId)) {
162992
163671
  log("[dreamer] smart notes: lease renewal failed — aborting");
163672
+ args.onLeaseLost?.("smart notes");
162993
163673
  abortController.abort();
162994
163674
  }
162995
- } catch {
163675
+ } catch (error51) {
163676
+ args.onLeaseLost?.("smart notes", error51);
162996
163677
  abortController.abort();
162997
163678
  }
162998
163679
  }, 60000);
@@ -163096,8 +163777,7 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
163096
163777
  clearInterval(leaseInterval);
163097
163778
  if (agentSessionId) {
163098
163779
  await args.client.session.delete({
163099
- path: { id: agentSessionId },
163100
- query: { directory: args.sessionDirectory ?? args.projectIdentity }
163780
+ path: { id: agentSessionId }
163101
163781
  }).catch(() => {});
163102
163782
  }
163103
163783
  }
@@ -164215,6 +164895,9 @@ function executeFlush(db, sessionId) {
164215
164895
  }
164216
164896
  }
164217
164897
 
164898
+ // src/context-handler.ts
164899
+ import * as crypto2 from "node:crypto";
164900
+
164218
164901
  // ../plugin/src/features/magic-context/scheduler.ts
164219
164902
  init_logger();
164220
164903
  var TTL_PATTERN = /^(\d+)([smh])$/;
@@ -164271,7 +164954,6 @@ function createScheduler(config2) {
164271
164954
  }
164272
164955
  };
164273
164956
  }
164274
-
164275
164957
  // ../plugin/src/features/magic-context/tagger.ts
164276
164958
  var TOOL_COMPOSITE_KEY_SEP = "\x00";
164277
164959
  function makeToolCompositeKey(ownerMsgId, callId) {
@@ -164613,6 +165295,30 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
164613
165295
  return didMutateMessage;
164614
165296
  }
164615
165297
 
165298
+ // ../plugin/src/hooks/magic-context/boundary-execution.ts
165299
+ var FORCE_MATERIALIZE_PERCENTAGE = 85;
165300
+ function detectMidTurnBypassReason(input) {
165301
+ if (input.contextUsage.percentage >= FORCE_MATERIALIZE_PERCENTAGE)
165302
+ return "force-materialize";
165303
+ if (input.historyRefreshSessions.has(input.sessionId))
165304
+ return "explicit-bust";
165305
+ if (input.sessionMeta.isSubagent)
165306
+ return "subagent";
165307
+ return "none";
165308
+ }
165309
+ function applyMidTurnDeferral(input) {
165310
+ if (input.base === "defer") {
165311
+ return { midTurnAdjustedSchedulerDecision: "defer", sideEffect: "none" };
165312
+ }
165313
+ if (input.bypassReason !== "none") {
165314
+ return { midTurnAdjustedSchedulerDecision: "execute", sideEffect: "none" };
165315
+ }
165316
+ if (input.midTurn) {
165317
+ return { midTurnAdjustedSchedulerDecision: "defer", sideEffect: "set-flag" };
165318
+ }
165319
+ return { midTurnAdjustedSchedulerDecision: "execute", sideEffect: "none" };
165320
+ }
165321
+
164616
165322
  // ../plugin/src/hooks/magic-context/caveman.ts
164617
165323
  var PRESERVATION_PATTERNS = [
164618
165324
  /```[\s\S]*?```/g,
@@ -165310,16 +166016,24 @@ function trimMemoriesToBudget(sessionId, memories, budgetTokens) {
165310
166016
  function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, projectPath, injectionBudgetTokens, temporalAwareness) {
165311
166017
  const cached2 = injectionCache.get(sessionId);
165312
166018
  if (!isCacheBusting && cached2) {
165313
- if (cached2.compartmentEndMessageId.length > 0) {
165314
- const cutoffIndex2 = messages.findIndex((message) => message.info.id === cached2.compartmentEndMessageId);
165315
- if (cutoffIndex2 >= 0) {
165316
- const remaining = messages.slice(cutoffIndex2 + 1);
165317
- messages.splice(0, messages.length, ...remaining);
165318
- } else {
165319
- sessionLog(sessionId, `compartment injection: cached boundary ${cached2.compartmentEndMessageId} not in messages (already trimmed), reusing cache`);
166019
+ if (cached2.kind === "empty") {
166020
+ return null;
166021
+ }
166022
+ const prepared = cached2.injection;
166023
+ if (prepared.compartmentEndMessageId === null) {
166024
+ sessionLog(sessionId, "compartment injection cache in degraded mode (null boundary), forcing rebuild");
166025
+ } else {
166026
+ if (prepared.compartmentEndMessageId.length > 0) {
166027
+ const cutoffIndex2 = messages.findIndex((message) => message.info.id === prepared.compartmentEndMessageId);
166028
+ if (cutoffIndex2 >= 0) {
166029
+ const remaining = messages.slice(cutoffIndex2 + 1);
166030
+ messages.splice(0, messages.length, ...remaining);
166031
+ } else {
166032
+ sessionLog(sessionId, `compartment injection: cached boundary ${prepared.compartmentEndMessageId} not in messages (already trimmed), reusing cache`);
166033
+ }
165320
166034
  }
166035
+ return { ...prepared, rebuiltFromDb: false };
165321
166036
  }
165322
- return cached2;
165323
166037
  }
165324
166038
  const compartments = getCompartments(db, sessionId);
165325
166039
  const facts = getSessionFacts(db, sessionId);
@@ -165351,7 +166065,11 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
165351
166065
  }
165352
166066
  }
165353
166067
  if (compartments.length === 0 && facts.length === 0 && !memoryBlock) {
165354
- injectionCache.delete(sessionId);
166068
+ injectionCache.set(sessionId, {
166069
+ kind: "empty",
166070
+ compartmentEndMessageId: "",
166071
+ renderedBytes: 0
166072
+ });
165355
166073
  return null;
165356
166074
  }
165357
166075
  let dateRanges;
@@ -165384,9 +166102,10 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
165384
166102
  compartmentCount: 0,
165385
166103
  skippedVisibleMessages: 0,
165386
166104
  factCount: facts.length,
165387
- memoryCount
166105
+ memoryCount,
166106
+ rebuiltFromDb: true
165388
166107
  };
165389
- injectionCache.set(sessionId, result2);
166108
+ injectionCache.set(sessionId, { kind: "populated", injection: result2 });
165390
166109
  return result2;
165391
166110
  }
165392
166111
  const lastCompartment = compartments[compartments.length - 1];
@@ -165404,9 +166123,10 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
165404
166123
  compartmentCount: compartments.length,
165405
166124
  skippedVisibleMessages: 0,
165406
166125
  factCount: facts.length,
165407
- memoryCount
166126
+ memoryCount,
166127
+ rebuiltFromDb: true
165408
166128
  };
165409
- injectionCache.set(sessionId, result2);
166129
+ injectionCache.set(sessionId, { kind: "populated", injection: result2 });
165410
166130
  return result2;
165411
166131
  }
165412
166132
  let skippedVisibleMessages = 0;
@@ -165415,17 +166135,20 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
165415
166135
  skippedVisibleMessages = cutoffIndex + 1;
165416
166136
  const remaining = messages.slice(cutoffIndex + 1);
165417
166137
  messages.splice(0, messages.length, ...remaining);
166138
+ } else {
166139
+ sessionLog(sessionId, `compartment injection entering degraded mode: boundary ${lastEndMessageId} not in visible messages`);
165418
166140
  }
165419
166141
  const result = {
165420
166142
  block,
165421
166143
  compartmentEndMessage: lastEnd,
165422
- compartmentEndMessageId: lastEndMessageId,
166144
+ compartmentEndMessageId: cutoffIndex >= 0 ? lastEndMessageId : null,
165423
166145
  compartmentCount: compartments.length,
165424
166146
  skippedVisibleMessages,
165425
166147
  factCount: facts.length,
165426
- memoryCount
166148
+ memoryCount,
166149
+ rebuiltFromDb: true
165427
166150
  };
165428
- injectionCache.set(sessionId, result);
166151
+ injectionCache.set(sessionId, { kind: "populated", injection: result });
165429
166152
  return result;
165430
166153
  }
165431
166154
 
@@ -166367,10 +167090,10 @@ var DEFAULT_MIN_PROMPT_CHARS = 20;
166367
167090
  async function unifiedSearchWithTimeout(db, sessionId, projectPath, prompt, options, timeoutMs) {
166368
167091
  const controller = new AbortController;
166369
167092
  let timer;
166370
- const timeoutPromise = new Promise((resolve) => {
167093
+ const timeoutPromise = new Promise((resolve3) => {
166371
167094
  timer = setTimeout(() => {
166372
167095
  controller.abort();
166373
- resolve(null);
167096
+ resolve3(null);
166374
167097
  }, timeoutMs);
166375
167098
  });
166376
167099
  try {
@@ -166968,7 +167691,7 @@ function injectSessionHistoryIntoPi(db, sessionId, piMessages, isCacheBusting, p
166968
167691
  };
166969
167692
  }
166970
167693
  const boundaryId = prepared.compartmentEndMessageId;
166971
- const skippedVisible = boundaryId.length > 0 ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : prepared.skippedVisibleMessages;
167694
+ const skippedVisible = boundaryId != null && boundaryId.length > 0 ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : prepared.skippedVisibleMessages;
166972
167695
  const historyBlock = `<session-history>
166973
167696
  ${prepared.block}
166974
167697
  </session-history>`;
@@ -168018,17 +168741,17 @@ function getReducedRecompTokenBudget(currentBudget) {
168018
168741
  }
168019
168742
 
168020
168743
  // ../plugin/src/hooks/magic-context/historian-state-file.ts
168021
- import { mkdirSync as mkdirSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
168022
- import { tmpdir as tmpdir2 } from "node:os";
168023
- import { join as join10 } from "node:path";
168744
+ init_data_path();
168745
+ import { mkdirSync as mkdirSync5, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
168746
+ import { join as join12 } from "node:path";
168024
168747
  var HISTORIAN_STATE_INLINE_THRESHOLD = 30000;
168025
- var HISTORIAN_STATE_DIR = join10(tmpdir2(), "magic-context-historian");
168026
- function maybeWriteHistorianStateFile(sessionId, existingState) {
168748
+ function maybeWriteHistorianStateFile(sessionId, existingState, directory) {
168027
168749
  if (existingState.length <= HISTORIAN_STATE_INLINE_THRESHOLD)
168028
168750
  return;
168029
168751
  try {
168030
- mkdirSync4(HISTORIAN_STATE_DIR, { recursive: true });
168031
- const path5 = join10(HISTORIAN_STATE_DIR, `state-${sessionId}-${Date.now()}.xml`);
168752
+ const dir = getProjectMagicContextHistorianDir(directory);
168753
+ mkdirSync5(dir, { recursive: true });
168754
+ const path5 = join12(dir, `state-${sessionId}-${Date.now()}.xml`);
168032
168755
  writeFileSync2(path5, existingState, "utf8");
168033
168756
  return path5;
168034
168757
  } catch {
@@ -168045,6 +168768,238 @@ function cleanupHistorianStateFile(path5) {
168045
168768
 
168046
168769
  // src/pi-historian-runner.ts
168047
168770
  init_logger();
168771
+
168772
+ // src/read-session-pi.ts
168773
+ function isMidTurnPi(event, _sessionId) {
168774
+ const messages = event?.messages;
168775
+ if (!Array.isArray(messages))
168776
+ return false;
168777
+ let latestAssistantIndex = -1;
168778
+ let latestAssistant = null;
168779
+ for (let i = messages.length - 1;i >= 0; i--) {
168780
+ const msg = messages[i];
168781
+ if (msg !== null && typeof msg === "object") {
168782
+ const record2 = msg;
168783
+ if (record2.role === "assistant") {
168784
+ latestAssistantIndex = i;
168785
+ latestAssistant = record2;
168786
+ break;
168787
+ }
168788
+ }
168789
+ }
168790
+ if (latestAssistant === null)
168791
+ return false;
168792
+ if (latestAssistant.stopReason === "toolUse")
168793
+ return true;
168794
+ const toolCallIds = getToolCallIds(latestAssistant.content);
168795
+ if (toolCallIds.size === 0)
168796
+ return false;
168797
+ const pairedToolResultIds = new Set;
168798
+ for (const msg of messages.slice(latestAssistantIndex + 1)) {
168799
+ if (msg === null || typeof msg !== "object")
168800
+ continue;
168801
+ const record2 = msg;
168802
+ if (record2.role !== "toolResult")
168803
+ continue;
168804
+ if (typeof record2.toolCallId === "string") {
168805
+ pairedToolResultIds.add(record2.toolCallId);
168806
+ }
168807
+ }
168808
+ for (const id of toolCallIds) {
168809
+ if (!pairedToolResultIds.has(id))
168810
+ return true;
168811
+ }
168812
+ return false;
168813
+ }
168814
+ function getToolCallIds(content) {
168815
+ const ids = new Set;
168816
+ if (!Array.isArray(content))
168817
+ return ids;
168818
+ for (const item of content) {
168819
+ if (item === null || typeof item !== "object")
168820
+ continue;
168821
+ const record2 = item;
168822
+ if (record2.type === "toolCall" && typeof record2.id === "string") {
168823
+ ids.add(record2.id);
168824
+ }
168825
+ }
168826
+ return ids;
168827
+ }
168828
+ function readPiSessionMessages(ctx) {
168829
+ const sm = ctx.sessionManager;
168830
+ if (sm === undefined)
168831
+ return [];
168832
+ const getBranch = sm.getBranch;
168833
+ if (typeof getBranch !== "function")
168834
+ return [];
168835
+ let entries;
168836
+ try {
168837
+ entries = getBranch.call(sm);
168838
+ } catch {
168839
+ return [];
168840
+ }
168841
+ if (!Array.isArray(entries))
168842
+ return [];
168843
+ return convertEntriesToRawMessages(entries);
168844
+ }
168845
+ function convertEntriesToRawMessages(entries) {
168846
+ const result = [];
168847
+ let nextOrdinal = 1;
168848
+ let pendingToolParts = [];
168849
+ let pendingFirstRealId = "";
168850
+ for (const entry of entries) {
168851
+ if (!isMessageEntry(entry)) {
168852
+ continue;
168853
+ }
168854
+ const msg = entry.message;
168855
+ const role = msg.role;
168856
+ if (role === "toolResult") {
168857
+ pendingToolParts.push(...synthesizeToolResultParts(msg));
168858
+ if (pendingFirstRealId === "") {
168859
+ pendingFirstRealId = entry.id;
168860
+ }
168861
+ continue;
168862
+ }
168863
+ if (role === "user") {
168864
+ const parts = [
168865
+ ...pendingToolParts,
168866
+ ...synthesizeUserParts(msg)
168867
+ ];
168868
+ pendingToolParts = [];
168869
+ pendingFirstRealId = "";
168870
+ result.push({
168871
+ ordinal: nextOrdinal++,
168872
+ id: entry.id,
168873
+ role: "user",
168874
+ parts
168875
+ });
168876
+ continue;
168877
+ }
168878
+ if (role === "assistant") {
168879
+ if (pendingToolParts.length > 0) {
168880
+ result.push({
168881
+ ordinal: nextOrdinal++,
168882
+ id: pendingFirstRealId,
168883
+ role: "user",
168884
+ parts: pendingToolParts
168885
+ });
168886
+ pendingToolParts = [];
168887
+ pendingFirstRealId = "";
168888
+ }
168889
+ result.push({
168890
+ ordinal: nextOrdinal++,
168891
+ id: entry.id,
168892
+ role: "assistant",
168893
+ parts: synthesizeAssistantParts(msg)
168894
+ });
168895
+ continue;
168896
+ }
168897
+ result.push({
168898
+ ordinal: nextOrdinal++,
168899
+ id: entry.id,
168900
+ role: typeof role === "string" ? role : "unknown",
168901
+ parts: []
168902
+ });
168903
+ }
168904
+ if (pendingToolParts.length > 0) {
168905
+ result.push({
168906
+ ordinal: nextOrdinal,
168907
+ id: pendingFirstRealId,
168908
+ role: "user",
168909
+ parts: pendingToolParts
168910
+ });
168911
+ }
168912
+ return result;
168913
+ }
168914
+ function isMessageEntry(value) {
168915
+ if (value === null || typeof value !== "object")
168916
+ return false;
168917
+ const v = value;
168918
+ if (v.type !== "message")
168919
+ return false;
168920
+ if (typeof v.id !== "string")
168921
+ return false;
168922
+ if (v.message === null || typeof v.message !== "object")
168923
+ return false;
168924
+ return true;
168925
+ }
168926
+ function synthesizeUserParts(msg) {
168927
+ const m = msg;
168928
+ if (typeof m.content === "string") {
168929
+ if (m.content.trim().length === 0)
168930
+ return [];
168931
+ return [{ type: "text", text: m.content }];
168932
+ }
168933
+ if (!Array.isArray(m.content))
168934
+ return [];
168935
+ const parts = [];
168936
+ for (const c of m.content) {
168937
+ if (c === null || typeof c !== "object")
168938
+ continue;
168939
+ const cc = c;
168940
+ if (cc.type === "text" && typeof cc.text === "string") {
168941
+ parts.push({ type: "text", text: cc.text });
168942
+ }
168943
+ }
168944
+ return parts;
168945
+ }
168946
+ function synthesizeAssistantParts(msg) {
168947
+ const m = msg;
168948
+ if (!Array.isArray(m.content))
168949
+ return [];
168950
+ const parts = [];
168951
+ for (const c of m.content) {
168952
+ if (c === null || typeof c !== "object")
168953
+ continue;
168954
+ const cc = c;
168955
+ if (cc.type === "text" && typeof cc.text === "string") {
168956
+ parts.push({ type: "text", text: cc.text });
168957
+ } else if (cc.type === "toolCall" && typeof cc.id === "string") {
168958
+ parts.push({
168959
+ type: "tool",
168960
+ tool: typeof cc.name === "string" ? cc.name : "unknown",
168961
+ callID: cc.id,
168962
+ state: {
168963
+ input: cc.arguments ?? {}
168964
+ }
168965
+ });
168966
+ }
168967
+ }
168968
+ return parts;
168969
+ }
168970
+ function synthesizeToolResultParts(msg) {
168971
+ const m = msg;
168972
+ const callID = typeof m.toolCallId === "string" ? m.toolCallId : "";
168973
+ const tool = typeof m.toolName === "string" ? m.toolName : "unknown";
168974
+ if (!callID)
168975
+ return [];
168976
+ let output = "";
168977
+ if (Array.isArray(m.content)) {
168978
+ const fragments = [];
168979
+ for (const c of m.content) {
168980
+ if (c === null || typeof c !== "object")
168981
+ continue;
168982
+ const cc = c;
168983
+ if (cc.type === "text" && typeof cc.text === "string") {
168984
+ fragments.push(cc.text);
168985
+ }
168986
+ }
168987
+ output = fragments.join(`
168988
+ `);
168989
+ }
168990
+ return [
168991
+ {
168992
+ type: "tool",
168993
+ tool,
168994
+ callID,
168995
+ state: {
168996
+ output
168997
+ }
168998
+ }
168999
+ ];
169000
+ }
169001
+
169002
+ // src/pi-historian-runner.ts
168048
169003
  var HISTORIAN_AGENT_NAME = "magic-context-historian";
168049
169004
  var DEFAULT_HISTORIAN_TIMEOUT_MS2 = 120000;
168050
169005
  var HISTORIAN_ALERT_COOLDOWN_MS = 60 * 1000;
@@ -168128,7 +169083,7 @@ async function runPiHistorian(deps) {
168128
169083
  const existingState = priorCompartments.length > 0 || priorFacts.length > 0 ? buildExistingStateXml(priorCompartments, priorFacts, memoryBlock) : memoryBlock ? `${memoryBlock}
168129
169084
 
168130
169085
  This is your first run. No existing compartments or facts.` : "This is your first run. No existing state.";
168131
- stateFilePath = maybeWriteHistorianStateFile(sessionId, existingState);
169086
+ stateFilePath = maybeWriteHistorianStateFile(sessionId, existingState, directory);
168132
169087
  if (stateFilePath) {
168133
169088
  sessionLog(sessionId, `pi-historian: existing state offloaded to file (${existingState.length} chars) → ${stateFilePath}`);
168134
169089
  }
@@ -168339,191 +169294,160 @@ function appendPiNativeCompaction(args) {
168339
169294
  }
168340
169295
  }
168341
169296
  function findFirstKeptEntryId(entries, lastCompactedOrdinal) {
168342
- let ordinal = 0;
168343
- for (const entry of entries) {
168344
- if (!entry || typeof entry !== "object")
168345
- continue;
168346
- const e = entry;
168347
- if (e.type !== "message")
168348
- continue;
168349
- const role = e.message?.role;
168350
- if (role !== "user" && role !== "assistant")
168351
- continue;
168352
- ordinal++;
168353
- if (ordinal === lastCompactedOrdinal + 1) {
168354
- return typeof e.id === "string" && e.id.length > 0 ? e.id : null;
168355
- }
168356
- }
168357
- return null;
169297
+ const rawMessages = convertEntriesToRawMessages(entries);
169298
+ const target = lastCompactedOrdinal + 1;
169299
+ const match = rawMessages.find((m) => m.ordinal === target);
169300
+ if (!match)
169301
+ return null;
169302
+ return match.id.length > 0 ? match.id : null;
168358
169303
  }
168359
169304
 
168360
- // src/read-session-pi.ts
168361
- function readPiSessionMessages(ctx) {
168362
- const sm = ctx.sessionManager;
168363
- if (sm === undefined)
168364
- return [];
168365
- const getBranch = sm.getBranch;
168366
- if (typeof getBranch !== "function")
168367
- return [];
168368
- let entries;
168369
- try {
168370
- entries = getBranch.call(sm);
168371
- } catch {
168372
- return [];
169305
+ // src/pi-todo-inject.ts
169306
+ function getMessageId(message) {
169307
+ if (typeof message.responseId === "string" && message.responseId.length > 0) {
169308
+ return message.responseId;
168373
169309
  }
168374
- if (!Array.isArray(entries))
168375
- return [];
168376
- return convertEntriesToRawMessages(entries);
169310
+ if (typeof message.timestamp === "number") {
169311
+ return `pi-ts-${message.timestamp}`;
169312
+ }
169313
+ return "";
168377
169314
  }
168378
- function convertEntriesToRawMessages(entries) {
168379
- const result = [];
168380
- let nextOrdinal = 1;
168381
- let pendingToolParts = [];
168382
- for (const entry of entries) {
168383
- if (!isMessageEntry(entry)) {
168384
- continue;
168385
- }
168386
- const msg = entry.message;
168387
- const role = msg.role;
168388
- if (role === "toolResult") {
168389
- pendingToolParts.push(...synthesizeToolResultParts(msg));
168390
- continue;
168391
- }
168392
- if (role === "user") {
168393
- const parts = [
168394
- ...pendingToolParts,
168395
- ...synthesizeUserParts(msg)
168396
- ];
168397
- pendingToolParts = [];
168398
- result.push({
168399
- ordinal: nextOrdinal++,
168400
- id: entry.id,
168401
- role: "user",
168402
- parts
168403
- });
168404
- continue;
168405
- }
168406
- if (role === "assistant") {
168407
- if (pendingToolParts.length > 0) {
168408
- result.push({
168409
- ordinal: nextOrdinal++,
168410
- id: "",
168411
- role: "user",
168412
- parts: pendingToolParts
168413
- });
168414
- pendingToolParts = [];
168415
- }
168416
- result.push({
168417
- ordinal: nextOrdinal++,
168418
- id: entry.id,
168419
- role: "assistant",
168420
- parts: synthesizeAssistantParts(msg)
168421
- });
169315
+ function hasToolCallWithId(message, callId) {
169316
+ if (!Array.isArray(message.content))
169317
+ return false;
169318
+ for (const block of message.content) {
169319
+ if (!block || typeof block !== "object")
168422
169320
  continue;
168423
- }
168424
- result.push({
168425
- ordinal: nextOrdinal++,
168426
- id: entry.id,
168427
- role: typeof role === "string" ? role : "unknown",
168428
- parts: []
168429
- });
169321
+ const t = block.type;
169322
+ const id = block.id;
169323
+ if (t === "toolCall" && id === callId)
169324
+ return true;
168430
169325
  }
168431
- if (pendingToolParts.length > 0) {
168432
- result.push({
168433
- ordinal: nextOrdinal,
168434
- id: "",
168435
- role: "user",
168436
- parts: pendingToolParts
168437
- });
169326
+ return false;
169327
+ }
169328
+ function findToolResultAfter(messages, assistantIndex, callId) {
169329
+ for (let i = assistantIndex + 1;i < messages.length; i += 1) {
169330
+ const m = messages[i];
169331
+ if (!m || typeof m !== "object")
169332
+ continue;
169333
+ const role = m.role;
169334
+ const tcId = m.toolCallId;
169335
+ if (role === "toolResult" && tcId === callId)
169336
+ return i;
169337
+ if (role === "assistant")
169338
+ return -1;
168438
169339
  }
168439
- return result;
169340
+ return -1;
168440
169341
  }
168441
- function isMessageEntry(value) {
168442
- if (value === null || typeof value !== "object")
168443
- return false;
168444
- const v = value;
168445
- if (v.type !== "message")
168446
- return false;
168447
- if (typeof v.id !== "string")
168448
- return false;
168449
- if (v.message === null || typeof v.message !== "object")
168450
- return false;
168451
- return true;
169342
+ function piBlocksFromSynthetic(part) {
169343
+ return {
169344
+ call: {
169345
+ type: "toolCall",
169346
+ id: part.callID,
169347
+ name: part.tool,
169348
+ arguments: { todos: part.state.input.todos },
169349
+ syntheticTodoMarker: true
169350
+ },
169351
+ result: {
169352
+ role: "toolResult",
169353
+ toolCallId: part.callID,
169354
+ toolName: part.tool,
169355
+ content: [{ type: "text", text: part.state.output }],
169356
+ timestamp: 0,
169357
+ syntheticTodoMarker: true
169358
+ }
169359
+ };
168452
169360
  }
168453
- function synthesizeUserParts(msg) {
168454
- const m = msg;
168455
- if (typeof m.content === "string") {
168456
- if (m.content.trim().length === 0)
168457
- return [];
168458
- return [{ type: "text", text: m.content }];
168459
- }
168460
- if (!Array.isArray(m.content))
168461
- return [];
168462
- const parts = [];
168463
- for (const c of m.content) {
168464
- if (c === null || typeof c !== "object")
169361
+ function injectByAssistantId(messages, messageId, part) {
169362
+ for (let i = 0;i < messages.length; i += 1) {
169363
+ const m = messages[i];
169364
+ if (!m || typeof m !== "object")
168465
169365
  continue;
168466
- const cc = c;
168467
- if (cc.type === "text" && typeof cc.text === "string") {
168468
- parts.push({ type: "text", text: cc.text });
169366
+ if (m.role !== "assistant")
169367
+ continue;
169368
+ const assistant = m;
169369
+ if (getMessageId(assistant) !== messageId)
169370
+ continue;
169371
+ if (hasToolCallWithId(assistant, part.callID)) {
169372
+ if (findToolResultAfter(messages, i, part.callID) >= 0)
169373
+ return true;
168469
169374
  }
169375
+ const { call, result } = piBlocksFromSynthetic(part);
169376
+ if (!Array.isArray(assistant.content))
169377
+ assistant.content = [];
169378
+ if (!hasToolCallWithId(assistant, part.callID)) {
169379
+ assistant.content.push(call);
169380
+ }
169381
+ if (findToolResultAfter(messages, i, part.callID) < 0) {
169382
+ messages.splice(i + 1, 0, result);
169383
+ }
169384
+ return true;
168470
169385
  }
168471
- return parts;
169386
+ return false;
168472
169387
  }
168473
- function synthesizeAssistantParts(msg) {
168474
- const m = msg;
168475
- if (!Array.isArray(m.content))
168476
- return [];
168477
- const parts = [];
168478
- for (const c of m.content) {
168479
- if (c === null || typeof c !== "object")
169388
+ function injectIntoLatestAssistant(messages, part) {
169389
+ for (let i = messages.length - 1;i >= 0; i -= 1) {
169390
+ const m = messages[i];
169391
+ if (!m || typeof m !== "object")
168480
169392
  continue;
168481
- const cc = c;
168482
- if (cc.type === "text" && typeof cc.text === "string") {
168483
- parts.push({ type: "text", text: cc.text });
168484
- } else if (cc.type === "toolCall" && typeof cc.id === "string") {
168485
- parts.push({
168486
- type: "tool",
168487
- tool: typeof cc.name === "string" ? cc.name : "unknown",
168488
- callID: cc.id,
168489
- state: {
168490
- input: cc.arguments ?? {}
168491
- }
168492
- });
169393
+ if (m.role !== "assistant")
169394
+ continue;
169395
+ const assistant = m;
169396
+ const id = getMessageId(assistant);
169397
+ if (id.length === 0)
169398
+ continue;
169399
+ if (hasToolCallWithId(assistant, part.callID)) {
169400
+ if (findToolResultAfter(messages, i, part.callID) >= 0)
169401
+ return id;
169402
+ }
169403
+ const { call, result } = piBlocksFromSynthetic(part);
169404
+ if (!Array.isArray(assistant.content))
169405
+ assistant.content = [];
169406
+ if (!hasToolCallWithId(assistant, part.callID)) {
169407
+ assistant.content.push(call);
169408
+ }
169409
+ if (findToolResultAfter(messages, i, part.callID) < 0) {
169410
+ messages.splice(i + 1, 0, result);
168493
169411
  }
169412
+ return id;
168494
169413
  }
168495
- return parts;
169414
+ return null;
168496
169415
  }
168497
- function synthesizeToolResultParts(msg) {
168498
- const m = msg;
168499
- const callID = typeof m.toolCallId === "string" ? m.toolCallId : "";
168500
- const tool = typeof m.toolName === "string" ? m.toolName : "unknown";
168501
- if (!callID)
168502
- return [];
168503
- let output = "";
168504
- if (Array.isArray(m.content)) {
168505
- const fragments = [];
168506
- for (const c of m.content) {
168507
- if (c === null || typeof c !== "object")
168508
- continue;
168509
- const cc = c;
168510
- if (cc.type === "text" && typeof cc.text === "string") {
168511
- fragments.push(cc.text);
169416
+ function injectSyntheticTodowriteForPi(args) {
169417
+ if (args.isSubagent)
169418
+ return args.messages;
169419
+ const persistedAnchor = getPersistedTodoSyntheticAnchor(args.db, args.sessionId);
169420
+ if (args.isCacheBusting) {
169421
+ const part2 = buildSyntheticTodoPart(args.lastTodoState);
169422
+ if (part2 === null) {
169423
+ if (persistedAnchor) {
169424
+ clearPersistedTodoSyntheticAnchor(args.db, args.sessionId);
168512
169425
  }
169426
+ return args.messages;
168513
169427
  }
168514
- output = fragments.join(`
168515
- `);
168516
- }
168517
- return [
168518
- {
168519
- type: "tool",
168520
- tool,
168521
- callID,
168522
- state: {
168523
- output
169428
+ if (persistedAnchor && persistedAnchor.callId === part2.callID && injectByAssistantId(args.messages, persistedAnchor.messageId, part2)) {
169429
+ if (persistedAnchor.stateJson.length === 0) {
169430
+ setPersistedTodoSyntheticAnchor(args.db, args.sessionId, persistedAnchor.callId, persistedAnchor.messageId, args.lastTodoState);
168524
169431
  }
169432
+ return args.messages;
168525
169433
  }
168526
- ];
169434
+ const anchoredMessageId = injectIntoLatestAssistant(args.messages, part2);
169435
+ if (anchoredMessageId) {
169436
+ setPersistedTodoSyntheticAnchor(args.db, args.sessionId, part2.callID, anchoredMessageId, args.lastTodoState);
169437
+ } else if (persistedAnchor) {
169438
+ clearPersistedTodoSyntheticAnchor(args.db, args.sessionId);
169439
+ }
169440
+ return args.messages;
169441
+ }
169442
+ if (!persistedAnchor || persistedAnchor.stateJson.length === 0) {
169443
+ return args.messages;
169444
+ }
169445
+ const part = buildSyntheticTodoPart(persistedAnchor.stateJson);
169446
+ if (part === null || part.callID !== persistedAnchor.callId) {
169447
+ return args.messages;
169448
+ }
169449
+ injectByAssistantId(args.messages, persistedAnchor.messageId, part);
169450
+ return args.messages;
168527
169451
  }
168528
169452
 
168529
169453
  // src/reasoning-replay-pi.ts
@@ -169294,6 +170218,17 @@ var recentReduceBySession = new Map;
169294
170218
  var liveModelBySession = new Map;
169295
170219
  var toolUsageSinceUserTurn = new Map;
169296
170220
  var latestUserMessageBySession = new Map;
170221
+ function resolvePiContextModelKey(ctx) {
170222
+ const model = ctx.model;
170223
+ if (!model)
170224
+ return;
170225
+ if (typeof model.provider !== "string" || model.provider.length === 0) {
170226
+ return;
170227
+ }
170228
+ if (typeof model.id !== "string" || model.id.length === 0)
170229
+ return;
170230
+ return `${model.provider}/${model.id}`;
170231
+ }
169297
170232
  function readPiSessionMessageById(ctx, messageId) {
169298
170233
  return readPiSessionMessages(ctx).find((message) => message.id === messageId) ?? null;
169299
170234
  }
@@ -169516,8 +170451,32 @@ function registerPiContextHandler(pi, options) {
169516
170451
  const isFirstContextPassForSession = !firstContextPassSeenBySession.has(sessionId);
169517
170452
  firstContextPassSeenBySession.add(sessionId);
169518
170453
  const piUsage = ctx.getContextUsage?.();
169519
- let usagePercentage = typeof piUsage?.percent === "number" ? piUsage.percent : 0;
169520
- const usageInputTokens = typeof piUsage?.tokens === "number" ? piUsage.tokens : 0;
170454
+ const previousModelKey = liveModelBySession.get(sessionId);
170455
+ const currentModelKey = resolvePiContextModelKey(ctx);
170456
+ const modelChanged = previousModelKey !== undefined && currentModelKey !== undefined && previousModelKey !== currentModelKey;
170457
+ if (currentModelKey !== undefined) {
170458
+ liveModelBySession.set(sessionId, currentModelKey);
170459
+ }
170460
+ const sessionMetaForUsage = getOrCreateSessionMeta(options.db, sessionId);
170461
+ if ((isFirstContextPassForSession || modelChanged) && (sessionMetaForUsage.lastContextPercentage > 0 || sessionMetaForUsage.lastInputTokens > 0)) {
170462
+ const reason = isFirstContextPassForSession ? "first pass" : `model switch ${previousModelKey} -> ${currentModelKey}`;
170463
+ sessionLog(sessionId, `pi transform: ${reason} reset — percentage=${sessionMetaForUsage.lastContextPercentage.toFixed(1)}% tokens=${sessionMetaForUsage.lastInputTokens} — clearing stale usage state`);
170464
+ updateSessionMeta(options.db, sessionId, {
170465
+ lastContextPercentage: 0,
170466
+ lastInputTokens: 0
170467
+ });
170468
+ sessionMetaForUsage.lastContextPercentage = 0;
170469
+ sessionMetaForUsage.lastInputTokens = 0;
170470
+ }
170471
+ let usagePercentage = 0;
170472
+ let usageInputTokens = 0;
170473
+ if (sessionMetaForUsage.lastContextPercentage > 0 && sessionMetaForUsage.lastInputTokens > 0) {
170474
+ usagePercentage = sessionMetaForUsage.lastContextPercentage;
170475
+ usageInputTokens = sessionMetaForUsage.lastInputTokens;
170476
+ } else {
170477
+ usagePercentage = typeof piUsage?.percent === "number" ? piUsage.percent : 0;
170478
+ usageInputTokens = typeof piUsage?.tokens === "number" ? piUsage.tokens : 0;
170479
+ }
169521
170480
  let usageContextLimit = typeof piUsage?.contextWindow === "number" && piUsage.contextWindow > 0 ? piUsage.contextWindow : undefined;
169522
170481
  try {
169523
170482
  const overflowState = getOverflowState(options.db, sessionId);
@@ -169531,7 +170490,7 @@ function registerPiContextHandler(pi, options) {
169531
170490
  } catch (err) {
169532
170491
  sessionLog(sessionId, `pi transform: overflow state read failed: ${err instanceof Error ? err.message : String(err)}`);
169533
170492
  }
169534
- const sessionMeta = getOrCreateSessionMeta(options.db, sessionId);
170493
+ const sessionMeta = sessionMetaForUsage;
169535
170494
  const modelKey = liveModelBySession.get(sessionId);
169536
170495
  let schedulerDecision;
169537
170496
  try {
@@ -169546,6 +170505,29 @@ function registerPiContextHandler(pi, options) {
169546
170505
  schedulerDecision = "execute";
169547
170506
  sessionLog(sessionId, `pi transform: large imported session detected (${piMessageCount} messages, no usage baseline) — forcing execute on first pass`);
169548
170507
  }
170508
+ const schedulerDecisionEarly = schedulerDecision;
170509
+ const midTurn = isMidTurnPi(event, sessionId);
170510
+ const bypassReason = detectMidTurnBypassReason({
170511
+ contextUsage: { percentage: usagePercentage },
170512
+ sessionMeta,
170513
+ historyRefreshSessions,
170514
+ sessionId
170515
+ });
170516
+ const { midTurnAdjustedSchedulerDecision, sideEffect } = applyMidTurnDeferral({
170517
+ base: schedulerDecisionEarly,
170518
+ bypassReason,
170519
+ midTurn
170520
+ });
170521
+ if (sideEffect === "set-flag") {
170522
+ const flagPayload = {
170523
+ id: crypto2.randomUUID(),
170524
+ reason: `${schedulerDecisionEarly}-${bypassReason}`,
170525
+ recordedAt: Date.now()
170526
+ };
170527
+ setDeferredExecutePendingIfAbsent(options.db, sessionId, flagPayload);
170528
+ }
170529
+ schedulerDecision = midTurnAdjustedSchedulerDecision;
170530
+ sessionLog(sessionId, `pi [boundary-exec] base=${schedulerDecisionEarly} bypass=${bypassReason} midTurn=${midTurn} effective=${midTurnAdjustedSchedulerDecision} sideEffect=${sideEffect}`);
169549
170531
  const forceMaterialization = usagePercentage >= FORCE_MATERIALIZATION_PERCENTAGE;
169550
170532
  const isEmergency = usagePercentage >= EMERGENCY_BLOCK_PERCENTAGE;
169551
170533
  if (isEmergency) {
@@ -169560,7 +170542,7 @@ function registerPiContextHandler(pi, options) {
169560
170542
  try {
169561
170543
  await Promise.race([
169562
170544
  histPromise,
169563
- new Promise((resolve) => setTimeout(resolve, 30000))
170545
+ new Promise((resolve3) => setTimeout(resolve3, 30000))
169564
170546
  ]);
169565
170547
  sessionLog(sessionId, "pi EMERGENCY: historian wait completed (or timed out)");
169566
170548
  } catch {}
@@ -169652,6 +170634,22 @@ function registerPiContextHandler(pi, options) {
169652
170634
  sessionLog(sessionId, `pi auto-search failed: ${err instanceof Error ? err.message : String(err)}`);
169653
170635
  }
169654
170636
  }
170637
+ try {
170638
+ const sessionMetaForTodo = getOrCreateSessionMeta(options.db, sessionId);
170639
+ if (!sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
170640
+ const isCacheBustingForTodo = isCacheBusting || result.executedWorkThisPass;
170641
+ outputMessages = injectSyntheticTodowriteForPi({
170642
+ db: options.db,
170643
+ sessionId,
170644
+ isSubagent: sessionMetaForTodo.isSubagent,
170645
+ isCacheBusting: isCacheBustingForTodo,
170646
+ lastTodoState: sessionMetaForTodo.lastTodoState,
170647
+ messages: outputMessages
170648
+ });
170649
+ }
170650
+ } catch (err) {
170651
+ sessionLog(sessionId, `pi synthetic todowrite injection failed: ${err instanceof Error ? err.message : String(err)}`);
170652
+ }
169655
170653
  clearLastTransformErrorIfSet(options.db, sessionId);
169656
170654
  return { messages: outputMessages };
169657
170655
  } catch (err) {
@@ -169833,16 +170831,25 @@ function maybeFireHistorian(args) {
169833
170831
  }
169834
170832
  let usage;
169835
170833
  try {
169836
- const piUsage = ctx.getContextUsage?.();
169837
- if (!piUsage || piUsage.tokens === null || piUsage.percent === null || piUsage.contextWindow === 0) {
169838
- sessionLog(sessionId, `pi-historian trigger eval: no usage info yet (tokens=${piUsage?.tokens ?? "<no piUsage>"}, percent=${piUsage?.percent ?? "<no piUsage>"}, contextWindow=${piUsage?.contextWindow ?? "<no piUsage>"})`);
169839
- return;
170834
+ const sessionMetaForUsage = getOrCreateSessionMeta(db, sessionId);
170835
+ if (sessionMetaForUsage.lastContextPercentage > 0 && sessionMetaForUsage.lastInputTokens > 0) {
170836
+ usage = {
170837
+ percentage: sessionMetaForUsage.lastContextPercentage,
170838
+ inputTokens: sessionMetaForUsage.lastInputTokens
170839
+ };
170840
+ sessionLog(sessionId, `pi-historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [from session_meta], checking trigger...`);
170841
+ } else {
170842
+ const piUsage = ctx.getContextUsage?.();
170843
+ if (!piUsage || piUsage.tokens === null || piUsage.percent === null || piUsage.contextWindow === 0) {
170844
+ sessionLog(sessionId, `pi-historian trigger eval: no usage info yet (tokens=${piUsage?.tokens ?? "<no piUsage>"}, percent=${piUsage?.percent ?? "<no piUsage>"}, contextWindow=${piUsage?.contextWindow ?? "<no piUsage>"})`);
170845
+ return;
170846
+ }
170847
+ usage = {
170848
+ percentage: piUsage.percent,
170849
+ inputTokens: piUsage.tokens
170850
+ };
170851
+ sessionLog(sessionId, `pi-historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [piUsage fallback], checking trigger...`);
169840
170852
  }
169841
- usage = {
169842
- percentage: piUsage.percent,
169843
- inputTokens: piUsage.tokens
169844
- };
169845
- sessionLog(sessionId, `pi-historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens), checking trigger...`);
169846
170853
  } catch (err) {
169847
170854
  sessionLog(sessionId, `pi-historian trigger eval: getContextUsage threw: ${err instanceof Error ? err.message : String(err)}`);
169848
170855
  return;
@@ -169854,6 +170861,11 @@ function maybeFireHistorian(args) {
169854
170861
  let triggered = false;
169855
170862
  try {
169856
170863
  if (isFirstContextPassForSession) {
170864
+ const sessionMeta2 = getOrCreateSessionMeta(db, sessionId);
170865
+ if (sessionMeta2.compartmentInProgress && !inFlightHistorian.has(sessionId)) {
170866
+ updateSessionMeta(db, sessionId, { compartmentInProgress: false });
170867
+ sessionLog(sessionId, "pi-historian: cleared stale compartmentInProgress flag on first context pass after restart");
170868
+ }
169857
170869
  const failureState = getHistorianFailureState(db, sessionId);
169858
170870
  const shouldRecoverOnFirstPass = failureState.failureCount > 0 && hasEligiblePiCompartmentHistory(db, sessionId);
169859
170871
  if (shouldRecoverOnFirstPass) {
@@ -169897,6 +170909,7 @@ Historian previously failed ${failureState.failureCount} time(s), so magic-conte
169897
170909
  }
169898
170910
  }
169899
170911
  async function runPipeline(args) {
170912
+ let executedWorkThisPass = false;
169900
170913
  if (args.temporalAwareness) {
169901
170914
  try {
169902
170915
  const injected = injectPiTemporalMarkers(args.messages);
@@ -169927,9 +170940,15 @@ async function runPipeline(args) {
169927
170940
  }
169928
170941
  const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
169929
170942
  if (args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal) {
169930
- applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags);
169931
- if (hasPendingMaterializeSignal) {
169932
- consumePendingMaterialization(args.sessionId);
170943
+ try {
170944
+ applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags);
170945
+ executedWorkThisPass = true;
170946
+ if (hasPendingMaterializeSignal) {
170947
+ consumePendingMaterialization(args.sessionId);
170948
+ }
170949
+ } catch (err) {
170950
+ sessionLog(args.sessionId, `pi pending operations failed: ${err instanceof Error ? err.message : String(err)}`);
170951
+ throw err;
169933
170952
  }
169934
170953
  }
169935
170954
  const flushedSliceTags = getTagsByNumbers(args.db, args.sessionId, [
@@ -169983,6 +171002,7 @@ async function runPipeline(args) {
169983
171002
  caveman: args.heuristics.caveman
169984
171003
  });
169985
171004
  heuristicsExecuted = true;
171005
+ executedWorkThisPass = true;
169986
171006
  } catch (err) {
169987
171007
  sessionLog(args.sessionId, `pi heuristic cleanup failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
169988
171008
  }
@@ -170005,6 +171025,7 @@ async function runPipeline(args) {
170005
171025
  sessionLog(args.sessionId, `pi reasoning cleared on execute: cleared=${clearOutcome.cleared} watermark=${prevWatermark}->${clearOutcome.newWatermark}`);
170006
171026
  }
170007
171027
  }
171028
+ executedWorkThisPass = true;
170008
171029
  } catch (err) {
170009
171030
  sessionLog(args.sessionId, `pi reasoning clearing failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
170010
171031
  }
@@ -170027,6 +171048,16 @@ async function runPipeline(args) {
170027
171048
  messages: args.messages,
170028
171049
  isCacheBusting: args.isCacheBusting
170029
171050
  });
171051
+ if (executedWorkThisPass) {
171052
+ try {
171053
+ const currentFlag = peekDeferredExecutePending(args.db, args.sessionId);
171054
+ if (currentFlag !== null) {
171055
+ clearDeferredExecutePendingIfMatches(args.db, args.sessionId, currentFlag);
171056
+ }
171057
+ } catch (err) {
171058
+ sessionLog(args.sessionId, `pi [boundary-exec] drain failed (continuing): ${err}`);
171059
+ }
171060
+ }
170030
171061
  const outputMessages = transcript.getOutputMessages();
170031
171062
  try {
170032
171063
  const counts = tokenizePiMessages(outputMessages);
@@ -170040,6 +171071,7 @@ async function runPipeline(args) {
170040
171071
  return {
170041
171072
  messages: outputMessages,
170042
171073
  heuristicsExecuted,
171074
+ executedWorkThisPass,
170043
171075
  historyInjected: injectionResult?.injected ?? false,
170044
171076
  heuristicsResult,
170045
171077
  injectionResult
@@ -170236,7 +171268,7 @@ init_logger();
170236
171268
  init_data_path();
170237
171269
  init_logger();
170238
171270
  await init_sqlite();
170239
- import { join as join11 } from "node:path";
171271
+ import { join as join13 } from "node:path";
170240
171272
  var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
170241
171273
  function randomBase62(length) {
170242
171274
  const chars = [];
@@ -170257,7 +171289,7 @@ function generatePartId(timestampMs, counter = 0n) {
170257
171289
  return generateId("prt", timestampMs, counter);
170258
171290
  }
170259
171291
  function getOpenCodeDbPath3() {
170260
- return join11(getDataDir(), "opencode", "opencode.db");
171292
+ return join13(getDataDir(), "opencode", "opencode.db");
170261
171293
  }
170262
171294
  var cachedWriteDb = null;
170263
171295
  var REQUIRED_MESSAGE_COLUMNS = ["id", "session_id", "time_created", "time_updated", "data"];
@@ -170807,7 +171839,7 @@ async function runCompressorPass2(args) {
170807
171839
  return null;
170808
171840
  } finally {
170809
171841
  if (agentSessionId) {
170810
- await client.session.delete({ path: { id: agentSessionId }, query: { directory } }).catch((e) => {
171842
+ await client.session.delete({ path: { id: agentSessionId } }).catch((e) => {
170811
171843
  sessionLog(sessionId, "compressor: session cleanup failed:", getErrorMessage(e));
170812
171844
  });
170813
171845
  }
@@ -170815,11 +171847,13 @@ async function runCompressorPass2(args) {
170815
171847
  }
170816
171848
 
170817
171849
  // ../plugin/src/hooks/magic-context/compartment-runner-historian.ts
170818
- import { mkdirSync as mkdirSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
170819
- import { tmpdir as tmpdir3 } from "node:os";
170820
- import { join as join12 } from "node:path";
171850
+ import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
171851
+ import { join as join14 } from "node:path";
170821
171852
  init_magic_context();
170822
- var HISTORIAN_RESPONSE_DUMP_DIR = join12(tmpdir3(), "magic-context-historian");
171853
+ init_data_path();
171854
+ function historianResponseDumpDir(directory) {
171855
+ return getProjectMagicContextHistorianDir(directory);
171856
+ }
170823
171857
  var MAX_HISTORIAN_RETRIES = 2;
170824
171858
  async function runValidatedHistorianPass(args) {
170825
171859
  const firstRun = await runHistorianPrompt({
@@ -170971,7 +172005,7 @@ async function runHistorianPrompt(args) {
170971
172005
  if (!result) {
170972
172006
  return { ok: false, error: "Historian returned no assistant output." };
170973
172007
  }
170974
- const dumpPath = dumpHistorianResponse(parentSessionId, dumpLabel ?? "historian-response", result);
172008
+ const dumpPath = dumpHistorianResponse(parentSessionId, sessionDirectory, dumpLabel ?? "historian-response", result);
170975
172009
  return { ok: true, result, dumpPath };
170976
172010
  } catch (modelError) {
170977
172011
  const desc = describeError(modelError);
@@ -170982,7 +172016,7 @@ async function runHistorianPrompt(args) {
170982
172016
  };
170983
172017
  } finally {
170984
172018
  if (agentSessionId) {
170985
- await client.session.delete({ path: { id: agentSessionId }, query: { directory: sessionDirectory } }).catch((e) => {
172019
+ await client.session.delete({ path: { id: agentSessionId } }).catch((e) => {
170986
172020
  sessionLog(parentSessionId, "compartment agent: session cleanup failed", getErrorMessage(e));
170987
172021
  });
170988
172022
  }
@@ -171047,8 +172081,8 @@ function isTransientHistorianPromptError(message) {
171047
172081
  ].some((token) => normalized.includes(token));
171048
172082
  }
171049
172083
  function sleep(ms) {
171050
- return new Promise((resolve) => {
171051
- setTimeout(resolve, ms);
172084
+ return new Promise((resolve3) => {
172085
+ setTimeout(resolve3, ms);
171052
172086
  });
171053
172087
  }
171054
172088
  function cleanupHistorianDump(sessionId, dumpPath) {
@@ -171063,12 +172097,13 @@ function cleanupHistorianDump(sessionId, dumpPath) {
171063
172097
  });
171064
172098
  }
171065
172099
  }
171066
- function dumpHistorianResponse(sessionId, label, text) {
172100
+ function dumpHistorianResponse(sessionId, directory, label, text) {
171067
172101
  try {
171068
- mkdirSync5(HISTORIAN_RESPONSE_DUMP_DIR, { recursive: true });
172102
+ const dumpDir = historianResponseDumpDir(directory);
172103
+ mkdirSync6(dumpDir, { recursive: true });
171069
172104
  const safeSessionId = sanitizeDumpName(sessionId);
171070
172105
  const safeLabel = sanitizeDumpName(label);
171071
- const dumpPath = join12(HISTORIAN_RESPONSE_DUMP_DIR, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
172106
+ const dumpPath = join14(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
171072
172107
  writeFileSync3(dumpPath, text, "utf8");
171073
172108
  sessionLog(sessionId, "compartment agent: historian response dumped", {
171074
172109
  label,
@@ -171293,9 +172328,18 @@ async function executePartialRecompInternal(deps, range) {
171293
172328
  }
171294
172329
  setRecompPartialRange(db, sessionId, null);
171295
172330
  clearCompressionDepthRange(db, sessionId, snapStart, snapEnd);
171296
- clearInjectionCache(sessionId);
171297
- deps.onInjectionCacheCleared?.(sessionId);
172331
+ if (deps.preserveInjectionCacheUntilConsumed !== true) {
172332
+ clearInjectionCache(sessionId);
172333
+ }
172334
+ deps.onCompartmentStatePublished?.(sessionId);
171298
172335
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
172336
+ if (deps.experimentalCompactionMarkers && lastEnd > 0) {
172337
+ updateCompactionMarkerAfterPublication(db, sessionId, lastEnd, deps.directory);
172338
+ const stalePending = getPendingCompactionMarkerState(db, sessionId);
172339
+ if (stalePending) {
172340
+ clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
172341
+ }
172342
+ }
171299
172343
  return { compartmentCount: merged.length, lastEndMessage: lastEnd };
171300
172344
  };
171301
172345
  const protectedTailStart = getProtectedTailStartOrdinal(sessionId);
@@ -171510,8 +172554,10 @@ async function executeContextRecompInternal(deps) {
171510
172554
  if (!promoted2)
171511
172555
  return null;
171512
172556
  clearCompressionDepth(db, sessionId);
171513
- clearInjectionCache(sessionId);
171514
- deps.onInjectionCacheCleared?.(sessionId);
172557
+ if (deps.preserveInjectionCacheUntilConsumed !== true) {
172558
+ clearInjectionCache(sessionId);
172559
+ }
172560
+ deps.onCompartmentStatePublished?.(sessionId);
171515
172561
  if (deps.directory && deps.memoryEnabled !== false && deps.autoPromote !== false) {
171516
172562
  promoteSessionFactsToMemory(db, sessionId, resolveProjectIdentity(deps.directory), promoted2.facts);
171517
172563
  }
@@ -171521,6 +172567,10 @@ async function executeContextRecompInternal(deps) {
171521
172567
  }
171522
172568
  if (deps.experimentalCompactionMarkers && lastCompartmentEnd2 > 0) {
171523
172569
  updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd2, deps.directory);
172570
+ const stalePending = getPendingCompactionMarkerState(db, sessionId);
172571
+ if (stalePending) {
172572
+ clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
172573
+ }
171524
172574
  }
171525
172575
  return [
171526
172576
  `Persisted ${promoted2.compartments.length} compartment${promoted2.compartments.length === 1 ? "" : "s"} from ${passCount} successful pass${passCount === 1 ? "" : "es"}.`,
@@ -171586,7 +172636,7 @@ Nothing was written.`;
171586
172636
 
171587
172637
  This is your first run. No existing compartments or facts.` : "This is your first run. No existing state.";
171588
172638
  cleanupHistorianStateFile(currentStateFilePath);
171589
- currentStateFilePath = maybeWriteHistorianStateFile(sessionId, existingState);
172639
+ currentStateFilePath = maybeWriteHistorianStateFile(sessionId, existingState, sessionDirectory);
171590
172640
  const prompt = buildCompartmentAgentPrompt(existingState, `Messages ${chunk.startIndex}-${chunk.endIndex}:
171591
172641
 
171592
172642
  ${chunk.text}`, { stateFilePath: currentStateFilePath });
@@ -171682,8 +172732,10 @@ Nothing was written.`;
171682
172732
  clearRecompStaging(db, sessionId);
171683
172733
  }
171684
172734
  clearCompressionDepth(db, sessionId);
171685
- clearInjectionCache(sessionId);
171686
- deps.onInjectionCacheCleared?.(sessionId);
172735
+ if (deps.preserveInjectionCacheUntilConsumed !== true) {
172736
+ clearInjectionCache(sessionId);
172737
+ }
172738
+ deps.onCompartmentStatePublished?.(sessionId);
171687
172739
  const finalCompartments = promoted?.compartments ?? candidateCompartments;
171688
172740
  const finalFacts = promoted?.facts ?? candidateFacts;
171689
172741
  if (deps.directory && deps.memoryEnabled !== false && deps.autoPromote !== false) {
@@ -171732,21 +172784,39 @@ Staging data preserved for resume on next attempt.`;
171732
172784
 
171733
172785
  // ../plugin/src/hooks/magic-context/compartment-runner.ts
171734
172786
  var activeRuns = new Map;
172787
+ function markActiveCompartmentRunPublished(sessionId) {
172788
+ const activeRun = activeRuns.get(sessionId);
172789
+ if (activeRun)
172790
+ activeRun.published = true;
172791
+ }
172792
+ function withPublishedCallback(deps) {
172793
+ return {
172794
+ ...deps,
172795
+ onCompartmentStatePublished: (sid) => {
172796
+ markActiveCompartmentRunPublished(sid);
172797
+ deps.onCompartmentStatePublished?.(sid);
172798
+ }
172799
+ };
172800
+ }
171735
172801
  async function executeContextRecomp(deps, options = {}) {
171736
172802
  const { sessionId } = deps;
171737
172803
  if (activeRuns.has(sessionId)) {
171738
172804
  return "## Magic Recomp\n\nHistorian is already running for this session. Wait for it to finish, then try `/ctx-recomp` again.";
171739
172805
  }
171740
- const promise2 = options.range ? executePartialRecompInternal(deps, options.range) : executeContextRecompInternal(deps);
171741
- activeRuns.set(sessionId, promise2.then(() => {
172806
+ const runnerDeps = withPublishedCallback(deps);
172807
+ const promise2 = options.range ? executePartialRecompInternal(runnerDeps, options.range) : executeContextRecompInternal(runnerDeps);
172808
+ const wrappedPromise = promise2.then(() => {
171742
172809
  return;
171743
172810
  }).catch((err) => {
171744
172811
  sessionLog(sessionId, "compartment agent: recomp unhandled rejection:", err);
171745
- }));
172812
+ });
172813
+ activeRuns.set(sessionId, { promise: wrappedPromise, published: false });
171746
172814
  try {
171747
172815
  return await promise2;
171748
172816
  } finally {
171749
- activeRuns.delete(sessionId);
172817
+ if (activeRuns.get(sessionId)?.promise === wrappedPromise) {
172818
+ activeRuns.delete(sessionId);
172819
+ }
171750
172820
  }
171751
172821
  }
171752
172822
 
@@ -172235,11 +173305,11 @@ import {
172235
173305
  matchesKey,
172236
173306
  truncateToWidth,
172237
173307
  visibleWidth
172238
- } from "@mariozechner/pi-tui";
173308
+ } from "@earendil-works/pi-tui";
172239
173309
  // package.json
172240
173310
  var package_default = {
172241
173311
  name: "@cortexkit/pi-magic-context",
172242
- version: "0.18.0",
173312
+ version: "0.20.0",
172243
173313
  type: "module",
172244
173314
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
172245
173315
  main: "dist/index.js",
@@ -172265,7 +173335,7 @@ var package_default = {
172265
173335
  "README.md"
172266
173336
  ],
172267
173337
  scripts: {
172268
- build: "bun build src/index.ts src/subagent-entry.ts --outdir dist --target node --format esm --external @mariozechner/pi-coding-agent --external @mariozechner/pi-tui --external @huggingface/transformers --external better-sqlite3",
173338
+ build: "bun build src/index.ts src/subagent-entry.ts --outdir dist --target node --format esm --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external @huggingface/transformers --external better-sqlite3",
172269
173339
  typecheck: "tsc --noEmit",
172270
173340
  test: "bun test",
172271
173341
  lint: "biome check src",
@@ -172285,15 +173355,15 @@ var package_default = {
172285
173355
  },
172286
173356
  devDependencies: {
172287
173357
  "@biomejs/biome": "^2.4.7",
172288
- "@mariozechner/pi-coding-agent": "^0.71.0",
172289
- "@mariozechner/pi-tui": "^0.71.0",
173358
+ "@earendil-works/pi-coding-agent": "^0.74.0",
173359
+ "@earendil-works/pi-tui": "^0.74.0",
172290
173360
  "@types/better-sqlite3": "^7.6.13",
172291
173361
  "@types/node": "^22.0.0",
172292
173362
  typescript: "^5.8.0"
172293
173363
  },
172294
173364
  peerDependencies: {
172295
- "@mariozechner/pi-coding-agent": "^0.71.0",
172296
- "@mariozechner/pi-tui": "^0.71.0"
173365
+ "@earendil-works/pi-coding-agent": "^0.74.0",
173366
+ "@earendil-works/pi-tui": "^0.74.0"
172297
173367
  },
172298
173368
  exports: {
172299
173369
  ".": {
@@ -172776,14 +173846,14 @@ function readHistorianState(db, sessionId, meta3) {
172776
173846
 
172777
173847
  // src/config/index.ts
172778
173848
  init_magic_context();
172779
- import { existsSync as existsSync8, readFileSync as readFileSync3 } from "node:fs";
172780
- import { homedir as homedir4 } from "node:os";
172781
- import { join as join13 } from "node:path";
173849
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
173850
+ import { homedir as homedir5 } from "node:os";
173851
+ import { join as join15 } from "node:path";
172782
173852
 
172783
173853
  // ../plugin/src/config/variable.ts
172784
- import { existsSync as existsSync7, readFileSync as readFileSync2 } from "node:fs";
172785
- import { homedir as homedir3 } from "node:os";
172786
- import { dirname as dirname3, isAbsolute, resolve } from "node:path";
173854
+ import { existsSync as existsSync9, readFileSync as readFileSync5 } from "node:fs";
173855
+ import { homedir as homedir4 } from "node:os";
173856
+ import { dirname as dirname4, isAbsolute, resolve as resolve3 } from "node:path";
172787
173857
  var ENV_PATTERN = /\{env:([^}]+)\}/g;
172788
173858
  var FILE_PATTERN = /\{file:([^}]+)\}/g;
172789
173859
  function substituteConfigVariables(input) {
@@ -172802,7 +173872,7 @@ function substituteConfigVariables(input) {
172802
173872
  if (fileMatches.length === 0) {
172803
173873
  return { text, warnings };
172804
173874
  }
172805
- const configDir = input.configPath ? dirname3(input.configPath) : process.cwd();
173875
+ const configDir = input.configPath ? dirname4(input.configPath) : process.cwd();
172806
173876
  let output = "";
172807
173877
  let cursor = 0;
172808
173878
  for (const match of fileMatches) {
@@ -172820,17 +173890,17 @@ function substituteConfigVariables(input) {
172820
173890
  }
172821
173891
  let filePath = rawPath.trim();
172822
173892
  if (filePath.startsWith("~/")) {
172823
- filePath = resolve(homedir3(), filePath.slice(2));
173893
+ filePath = resolve3(homedir4(), filePath.slice(2));
172824
173894
  } else if (!isAbsolute(filePath)) {
172825
- filePath = resolve(configDir, filePath);
173895
+ filePath = resolve3(configDir, filePath);
172826
173896
  }
172827
- if (!existsSync7(filePath)) {
173897
+ if (!existsSync9(filePath)) {
172828
173898
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
172829
173899
  continue;
172830
173900
  }
172831
173901
  let contents;
172832
173902
  try {
172833
- contents = readFileSync2(filePath, "utf-8").trim();
173903
+ contents = readFileSync5(filePath, "utf-8").trim();
172834
173904
  } catch (error51) {
172835
173905
  const message = error51 instanceof Error ? error51.message : String(error51);
172836
173906
  warnings.push(`Failed to read file for ${token} (${filePath}): ${message}; using empty string`);
@@ -172843,23 +173913,23 @@ function substituteConfigVariables(input) {
172843
173913
  }
172844
173914
 
172845
173915
  // src/config/index.ts
172846
- var import_comment_json = __toESM(require_src2(), 1);
173916
+ var import_comment_json2 = __toESM(require_src2(), 1);
172847
173917
  var CONFIG_FILE_NAME = "magic-context";
172848
173918
  function getProjectConfigPaths(cwd) {
172849
- const basePath = join13(cwd, ".pi", CONFIG_FILE_NAME);
173919
+ const basePath = join15(cwd, ".pi", CONFIG_FILE_NAME);
172850
173920
  return [`${basePath}.jsonc`, `${basePath}.json`];
172851
173921
  }
172852
173922
  function getUserConfigPaths() {
172853
- const home = process.env.HOME ?? homedir4();
172854
- const basePath = join13(home, ".pi", "agent", CONFIG_FILE_NAME);
173923
+ const home = process.env.HOME ?? homedir5();
173924
+ const basePath = join15(home, ".pi", "agent", CONFIG_FILE_NAME);
172855
173925
  return [`${basePath}.jsonc`, `${basePath}.json`];
172856
173926
  }
172857
173927
  function resolveFirstExisting(paths) {
172858
- return paths.find((path5) => existsSync8(path5));
173928
+ return paths.find((path5) => existsSync10(path5));
172859
173929
  }
172860
173930
  function loadConfigFile(path5, scope) {
172861
173931
  try {
172862
- const rawText = readFileSync3(path5, "utf-8");
173932
+ const rawText = readFileSync6(path5, "utf-8");
172863
173933
  const substituted = substituteConfigVariables({
172864
173934
  text: rawText,
172865
173935
  configPath: path5
@@ -172867,7 +173937,7 @@ function loadConfigFile(path5, scope) {
172867
173937
  return {
172868
173938
  path: path5,
172869
173939
  scope,
172870
- config: import_comment_json.parse(substituted.text),
173940
+ config: import_comment_json2.parse(substituted.text),
172871
173941
  warnings: substituted.warnings.map((warning) => `${path5}: ${warning}`)
172872
173942
  };
172873
173943
  } catch (error51) {
@@ -172981,6 +174051,42 @@ function loadPiConfig(opts = {}) {
172981
174051
  };
172982
174052
  }
172983
174053
 
174054
+ // src/pi-pressure.ts
174055
+ function extractAssistantUsage(message) {
174056
+ if (!message || typeof message !== "object")
174057
+ return null;
174058
+ const m = message;
174059
+ if (m.role !== "assistant")
174060
+ return null;
174061
+ if (!m.usage || typeof m.usage !== "object")
174062
+ return null;
174063
+ const u = m.usage;
174064
+ const result = {};
174065
+ if (typeof u.input === "number")
174066
+ result.input = u.input;
174067
+ if (typeof u.output === "number")
174068
+ result.output = u.output;
174069
+ if (typeof u.cacheRead === "number")
174070
+ result.cacheRead = u.cacheRead;
174071
+ if (typeof u.cacheWrite === "number")
174072
+ result.cacheWrite = u.cacheWrite;
174073
+ if (typeof u.totalTokens === "number")
174074
+ result.totalTokens = u.totalTokens;
174075
+ return result;
174076
+ }
174077
+ function computePiPressure(usage, contextLimit) {
174078
+ if (!usage)
174079
+ return null;
174080
+ const input = usage.input ?? 0;
174081
+ const cacheRead = usage.cacheRead ?? 0;
174082
+ const cacheWrite = usage.cacheWrite ?? 0;
174083
+ const inputTokens = input + cacheRead + cacheWrite;
174084
+ if (inputTokens === 0)
174085
+ return null;
174086
+ const percentage = contextLimit > 0 ? inputTokens / contextLimit * 100 : 0;
174087
+ return { inputTokens, percentage };
174088
+ }
174089
+
172984
174090
  // src/strip-tag-prefix.ts
172985
174091
  var TAG_PREFIX_REGEX2 = /^(\u00a7\d+\u00a7\s*)+/;
172986
174092
  function stripTagPrefixFromAssistantMessage(message) {
@@ -173006,9 +174112,9 @@ function stripTagPrefixFromAssistantMessage(message) {
173006
174112
  }
173007
174113
 
173008
174114
  // src/system-prompt.ts
173009
- import { createHash as createHash4 } from "node:crypto";
173010
- import { existsSync as existsSync9, readFileSync as readFileSync4, realpathSync } from "node:fs";
173011
- import { join as join14, resolve as resolve2, sep } from "node:path";
174115
+ import { createHash as createHash6 } from "node:crypto";
174116
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
174117
+ import { join as join17 } from "node:path";
173012
174118
 
173013
174119
  // ../plugin/src/agents/magic-context-prompt.ts
173014
174120
  function getToolHistoryGuidance(dropToolStructure) {
@@ -173097,23 +174203,146 @@ ${GENERIC_SECTION}
173097
174203
  Prefer many small targeted operations over one large blanket operation. Compress early and often — don't wait for warnings.`;
173098
174204
  }
173099
174205
 
174206
+ // ../plugin/src/hooks/magic-context/key-files-block.ts
174207
+ import { readFileSync as readFileSync7, realpathSync as realpathSync3 } from "node:fs";
174208
+ import { join as join16, sep as sep2 } from "node:path";
174209
+ init_logger();
174210
+ var cachedKeyFilesBySession = new Map;
174211
+ var staleUpdates = new Map;
174212
+ function staleKey(update) {
174213
+ return `${update.projectPath}\x00${update.path}\x00${update.generatedAtWitness}\x00${update.staleReason}`;
174214
+ }
174215
+ function queueStaleUpdate(projectPath, path5, generatedAtWitness, staleReason) {
174216
+ const update = { projectPath, path: path5, generatedAtWitness, staleReason };
174217
+ staleUpdates.set(staleKey(update), update);
174218
+ }
174219
+ function flushStaleUpdates(db) {
174220
+ if (staleUpdates.size === 0)
174221
+ return 0;
174222
+ const updates = [...staleUpdates.values()];
174223
+ staleUpdates.clear();
174224
+ const stmt = db.prepare(`UPDATE project_key_files
174225
+ SET stale_reason = ?1
174226
+ WHERE project_path = ?2
174227
+ AND path = ?3
174228
+ AND generated_at = ?4
174229
+ AND (stale_reason IS NULL OR stale_reason != ?1)`);
174230
+ let changed = 0;
174231
+ for (const update of updates) {
174232
+ try {
174233
+ changed += stmt.run(update.staleReason, update.projectPath, update.path, update.generatedAtWitness).changes;
174234
+ } catch (error51) {
174235
+ log("[key-files] flushStaleUpdates failed:", error51);
174236
+ }
174237
+ }
174238
+ return changed;
174239
+ }
174240
+ function clearKeyFilesCacheForSession(sessionId) {
174241
+ cachedKeyFilesBySession.delete(sessionId);
174242
+ }
174243
+ function isUnderProject(projectPath, absPath) {
174244
+ const root = realpathSync3(projectPath);
174245
+ return absPath.startsWith(root + sep2) || absPath === root;
174246
+ }
174247
+ function buildKeyFilesBlock(db, projectPath, config2 = { enabled: true, tokenBudget: 1e4 }) {
174248
+ if (!config2.enabled)
174249
+ return null;
174250
+ if (!isAftAvailable())
174251
+ return null;
174252
+ const rows = readCurrentKeyFiles(db, projectPath);
174253
+ if (rows.length === 0)
174254
+ return null;
174255
+ for (const row of rows) {
174256
+ if (row.staleReason !== null)
174257
+ continue;
174258
+ let nextStale = null;
174259
+ let observed = false;
174260
+ try {
174261
+ const absPath = join16(projectPath, row.path);
174262
+ const real = realpathSync3(absPath);
174263
+ if (!isUnderProject(projectPath, real)) {
174264
+ nextStale = "missing";
174265
+ observed = true;
174266
+ } else {
174267
+ const diskHash = sha256(readFileSync7(real));
174268
+ if (diskHash !== row.contentHash)
174269
+ nextStale = "content_drift";
174270
+ observed = true;
174271
+ }
174272
+ } catch (error51) {
174273
+ const code = error51?.code;
174274
+ if (code === "ENOENT" || code === "ELOOP") {
174275
+ nextStale = "missing";
174276
+ observed = true;
174277
+ } else {
174278
+ log(`[key-files] freshness check transient failure: ${row.path}: ${error51 instanceof Error ? error51.message : String(error51)}`);
174279
+ }
174280
+ }
174281
+ if (observed && nextStale !== null) {
174282
+ queueStaleUpdate(row.projectPath, row.path, row.generatedAt, nextStale);
174283
+ }
174284
+ }
174285
+ const blocks = [];
174286
+ let used = 0;
174287
+ for (const row of rows) {
174288
+ if (used + row.localTokenEstimate > config2.tokenBudget)
174289
+ break;
174290
+ blocks.push(` <key-file path="${escapeXmlAttr(row.path)}">
174291
+ ${escapeXmlContent(row.content)}
174292
+ </key-file>`);
174293
+ used += row.localTokenEstimate;
174294
+ }
174295
+ if (blocks.length === 0)
174296
+ return null;
174297
+ const rendered = `<key-files>
174298
+ ${blocks.join(`
174299
+ `)}
174300
+ </key-files>`;
174301
+ flushStaleUpdates(db);
174302
+ return rendered;
174303
+ }
174304
+ function readVersionedKeyFiles(args) {
174305
+ const config2 = args.config ?? { enabled: true, tokenBudget: 1e4 };
174306
+ if (args.sessionMeta.isSubagent)
174307
+ return null;
174308
+ if (!config2.enabled)
174309
+ return null;
174310
+ if (!isAftAvailable())
174311
+ return null;
174312
+ const projectPath = resolveProjectPath(args.directory ?? args.sessionMeta.sessionId);
174313
+ if (!projectPath)
174314
+ return null;
174315
+ const currentVersion = getKeyFilesVersion(args.db, projectPath);
174316
+ if (args.sessionId) {
174317
+ const cached2 = cachedKeyFilesBySession.get(args.sessionId);
174318
+ if (cached2 && !args.isCacheBusting && cached2.version === currentVersion) {
174319
+ return cached2.value;
174320
+ }
174321
+ }
174322
+ const value = buildKeyFilesBlock(args.db, projectPath, config2);
174323
+ if (args.sessionId) {
174324
+ cachedKeyFilesBySession.set(args.sessionId, { value, version: currentVersion });
174325
+ if (value)
174326
+ sessionLog(args.sessionId, `loaded key-files block (v${currentVersion}, ${value.length} chars)`);
174327
+ }
174328
+ return value;
174329
+ }
174330
+
173100
174331
  // src/system-prompt.ts
173101
174332
  init_logger();
173102
174333
  var PROJECT_DOCS_MARKER = "<project-docs>";
173103
174334
  var USER_PROFILE_MARKER = "<user-profile>";
173104
- var KEY_FILES_MARKER = "<key-files>";
173105
174335
  var stickyDateBySession = new Map;
173106
174336
  var cachedDocsBySession = new Map;
173107
174337
  var cachedUserProfileBySession = new Map;
173108
- var cachedKeyFilesBySession = new Map;
173109
174338
  var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
173110
174339
  function readProjectDocs(directory) {
173111
174340
  const sections = [];
173112
174341
  for (const filename of DOC_FILES) {
173113
- const filePath = join14(directory, filename);
174342
+ const filePath = join17(directory, filename);
173114
174343
  try {
173115
- if (existsSync9(filePath)) {
173116
- const content = readFileSync4(filePath, "utf-8").trim();
174344
+ if (existsSync11(filePath)) {
174345
+ const content = readFileSync8(filePath, "utf-8").trim();
173117
174346
  if (content.length > 0) {
173118
174347
  sections.push(`<${filename}>
173119
174348
  ${content}
@@ -173142,56 +174371,6 @@ function buildUserProfileBlock(db) {
173142
174371
  ${items}
173143
174372
  </user-profile>`;
173144
174373
  }
173145
- function buildKeyFilesBlock(db, sessionId, directory, tokenBudget) {
173146
- const entries = getKeyFiles(db, sessionId);
173147
- if (entries.length === 0)
173148
- return null;
173149
- const sections = [];
173150
- const projectRoot = resolve2(directory);
173151
- let remainingBudgetTokens = tokenBudget;
173152
- for (const entry of entries) {
173153
- try {
173154
- const absPath = resolve2(directory, entry.filePath);
173155
- if (!absPath.startsWith(projectRoot + sep) && absPath !== projectRoot) {
173156
- log(`[magic-context-pi] key file path escapes project root, skipping: ${entry.filePath}`);
173157
- continue;
173158
- }
173159
- if (!existsSync9(absPath))
173160
- continue;
173161
- let realPath;
173162
- try {
173163
- realPath = realpathSync(absPath);
173164
- } catch {
173165
- continue;
173166
- }
173167
- if (!realPath.startsWith(projectRoot + sep) && realPath !== projectRoot) {
173168
- log(`[magic-context-pi] key file symlink escapes project root, skipping: ${entry.filePath} → ${realPath}`);
173169
- continue;
173170
- }
173171
- const content = readFileSync4(realPath, "utf-8").trim();
173172
- if (content.length === 0)
173173
- continue;
173174
- const fileTokens = estimateTokens(content);
173175
- if (fileTokens > remainingBudgetTokens) {
173176
- log(`[magic-context-pi] key file ${entry.filePath} exceeds remaining budget (${fileTokens} > ${remainingBudgetTokens}), skipping`);
173177
- continue;
173178
- }
173179
- remainingBudgetTokens -= fileTokens;
173180
- sections.push(`<file path="${escapeXmlAttr(entry.filePath)}">
173181
- ${escapeXmlContent(content)}
173182
- </file>`);
173183
- } catch (error51) {
173184
- log(`[magic-context-pi] failed to read key file ${entry.filePath}:`, error51);
173185
- }
173186
- }
173187
- if (sections.length === 0)
173188
- return null;
173189
- return `${KEY_FILES_MARKER}
173190
- ${sections.join(`
173191
-
173192
- `)}
173193
- </key-files>`;
173194
- }
173195
174374
  function buildMagicContextBlock(opts) {
173196
174375
  const sections = [];
173197
174376
  const sessionId = opts.sessionId;
@@ -173219,13 +174398,23 @@ function buildMagicContextBlock(opts) {
173219
174398
  sections.push(profileBlock);
173220
174399
  }
173221
174400
  if (opts.pinKeyFilesEnabled && sessionId) {
173222
- const keyFilesBlock = readCachedAdjunct({
173223
- cache: cachedKeyFilesBySession,
174401
+ let sessionMeta = null;
174402
+ try {
174403
+ sessionMeta = getOrCreateSessionMeta(opts.db, sessionId);
174404
+ } catch (error51) {
174405
+ sessionLog(sessionId, "Pi key-files session meta load failed:", error51);
174406
+ }
174407
+ const keyFilesBlock = sessionMeta ? readVersionedKeyFiles({
174408
+ db: opts.db,
173224
174409
  sessionId,
174410
+ sessionMeta,
174411
+ directory: opts.cwd,
173225
174412
  isCacheBusting,
173226
- compute: () => buildKeyFilesBlock(opts.db, sessionId, opts.cwd, opts.pinKeyFilesTokenBudget ?? 1e4),
173227
- describe: "key files"
173228
- });
174413
+ config: {
174414
+ enabled: opts.pinKeyFilesEnabled,
174415
+ tokenBudget: opts.pinKeyFilesTokenBudget ?? 1e4
174416
+ }
174417
+ }) : null;
173229
174418
  if (keyFilesBlock)
173230
174419
  sections.push(keyFilesBlock);
173231
174420
  }
@@ -173290,7 +174479,7 @@ function processSystemPromptForCache(args) {
173290
174479
  sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
173291
174480
  }
173292
174481
  }
173293
- const currentHash = createHash4("md5").update(frozenPrompt).digest("hex");
174482
+ const currentHash = createHash6("md5").update(frozenPrompt).digest("hex");
173294
174483
  const hashChanged = !isFirstHash && currentHash !== previousHash;
173295
174484
  if (hashChanged) {
173296
174485
  sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
@@ -173318,7 +174507,7 @@ function clearPiSystemPromptSession(sessionId) {
173318
174507
  stickyDateBySession.delete(sessionId);
173319
174508
  cachedDocsBySession.delete(sessionId);
173320
174509
  cachedUserProfileBySession.delete(sessionId);
173321
- cachedKeyFilesBySession.delete(sessionId);
174510
+ clearKeyFilesCacheForSession(sessionId);
173322
174511
  }
173323
174512
 
173324
174513
  // ../plugin/src/tools/ctx-expand/constants.ts
@@ -178208,6 +179397,70 @@ function createCtxSearchTool(deps) {
178208
179397
  };
178209
179398
  }
178210
179399
 
179400
+ // src/tools/todowrite.ts
179401
+ var STATUS_VALUES = [
179402
+ "pending",
179403
+ "in_progress",
179404
+ "completed",
179405
+ "cancelled"
179406
+ ];
179407
+ var PRIORITY_VALUES = ["high", "medium", "low"];
179408
+ var TodoItem = exports_typebox.Object({
179409
+ content: exports_typebox.String({ description: "Brief description of the task" }),
179410
+ status: exports_typebox.Union(STATUS_VALUES.map((v) => exports_typebox.Literal(v))),
179411
+ priority: exports_typebox.Optional(exports_typebox.Union(PRIORITY_VALUES.map((v) => exports_typebox.Literal(v)))),
179412
+ id: exports_typebox.Optional(exports_typebox.String({ description: "Optional stable id for the todo" }))
179413
+ });
179414
+ var TodowriteParams = exports_typebox.Object({
179415
+ todos: exports_typebox.Array(TodoItem, {
179416
+ 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."
179417
+ })
179418
+ });
179419
+ var TOOL_DESCRIPTION = [
179420
+ "Manage your task list for this session.",
179421
+ "",
179422
+ "Use this tool to plan multi-step work, track in-flight tasks, and",
179423
+ "mark progress as you complete steps. Pass the COMPLETE updated list",
179424
+ "of todos every time — this tool replaces the prior list rather than",
179425
+ "appending to it.",
179426
+ "",
179427
+ "Task states:",
179428
+ " - pending: not started yet",
179429
+ " - in_progress: currently working on (limit to ONE task at a time)",
179430
+ " - completed: finished successfully",
179431
+ " - cancelled: no longer needed",
179432
+ "",
179433
+ "Use this tool proactively for non-trivial work spanning 3+ steps.",
179434
+ "Skip it for single-shot answers or trivial 1-2 step tasks."
179435
+ ].join(`
179436
+ `);
179437
+ function createTodowriteTool() {
179438
+ return {
179439
+ name: "todowrite",
179440
+ label: "Todos",
179441
+ description: TOOL_DESCRIPTION,
179442
+ parameters: TodowriteParams,
179443
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
179444
+ const todos = params.todos ?? [];
179445
+ const completed = todos.filter((t) => t.status === "completed").length;
179446
+ const active = todos.length - completed;
179447
+ return {
179448
+ content: [
179449
+ {
179450
+ type: "text",
179451
+ text: JSON.stringify(todos, null, 2)
179452
+ }
179453
+ ],
179454
+ details: {
179455
+ todos,
179456
+ title: `${active} todos`,
179457
+ truncated: false
179458
+ }
179459
+ };
179460
+ }
179461
+ };
179462
+ }
179463
+
178211
179464
  // src/tools/index.ts
178212
179465
  function registerMagicContextTools(pi, opts) {
178213
179466
  pi.registerTool(createCtxSearchTool({
@@ -178227,6 +179480,7 @@ function registerMagicContextTools(pi, opts) {
178227
179480
  dreamerEnabled: opts.dreamerEnabled ?? false
178228
179481
  }));
178229
179482
  pi.registerTool(createCtxExpandTool({ db: opts.db }));
179483
+ pi.registerTool(createTodowriteTool());
178230
179484
  if (opts.ctxReduceEnabled === true) {
178231
179485
  pi.registerTool(createCtxReduceTool({
178232
179486
  db: opts.db,
@@ -178341,7 +179595,7 @@ function resolveDreamerFromConfig(config2) {
178341
179595
  }
178342
179596
  async function src_default2(pi) {
178343
179597
  const storageDir = getMagicContextStorageDir();
178344
- const dbPath = join15(storageDir, "context.db");
179598
+ const dbPath = join18(storageDir, "context.db");
178345
179599
  let db;
178346
179600
  try {
178347
179601
  db = openDatabase();
@@ -178551,9 +179805,17 @@ ${block}` : event.systemPrompt;
178551
179805
  if (event.toolName === "todowrite") {
178552
179806
  const todoArgs = event.args;
178553
179807
  const todos = todoArgs?.todos;
179808
+ const sessionMeta = Array.isArray(todos) ? getOrCreateSessionMeta(db, sessionId) : null;
179809
+ if (sessionMeta && !sessionMeta.isSubagent) {
179810
+ const normalizedTodos = normalizeTodoStateJson(todos);
179811
+ if (normalizedTodos !== null) {
179812
+ updateSessionMeta(db, sessionId, {
179813
+ lastTodoState: normalizedTodos
179814
+ });
179815
+ }
179816
+ }
178554
179817
  if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
178555
- const sessionMeta = getOrCreateSessionMeta(db, sessionId);
178556
- if (!sessionMeta.isSubagent) {
179818
+ if (sessionMeta && !sessionMeta.isSubagent) {
178557
179819
  onNoteTrigger(db, sessionId, "todos_complete");
178558
179820
  }
178559
179821
  }
@@ -178607,14 +179869,62 @@ ${block}` : event.systemPrompt;
178607
179869
  cacheTtlConfig: config2.cache_ttl
178608
179870
  });
178609
179871
  const piUsage = ctx.getContextUsage?.();
178610
- const updates = { lastResponseTime: Date.now() };
178611
- if (piUsage && typeof piUsage.percent === "number") {
178612
- updates.lastContextPercentage = piUsage.percent;
179872
+ const piContextWindow = piUsage && typeof piUsage.contextWindow === "number" ? piUsage.contextWindow : 0;
179873
+ let effectiveContextLimit = piContextWindow;
179874
+ try {
179875
+ const overflowState = getOverflowState(db, sessionId);
179876
+ if (overflowState.detectedContextLimit > 0) {
179877
+ effectiveContextLimit = effectiveContextLimit > 0 ? Math.min(effectiveContextLimit, overflowState.detectedContextLimit) : overflowState.detectedContextLimit;
179878
+ }
179879
+ } catch (err5) {
179880
+ warn("message_end: getOverflowState failed:", err5);
178613
179881
  }
178614
- if (piUsage && typeof piUsage.tokens === "number") {
179882
+ const usage = extractAssistantUsage(event.message);
179883
+ const pressure = computePiPressure(usage, effectiveContextLimit);
179884
+ const updates = { lastResponseTime: Date.now() };
179885
+ if (pressure) {
179886
+ updates.lastContextPercentage = pressure.percentage;
179887
+ updates.lastInputTokens = pressure.inputTokens;
179888
+ } else if (piUsage && typeof piUsage.tokens === "number") {
178615
179889
  updates.lastInputTokens = piUsage.tokens;
179890
+ if (piUsage.contextWindow > 0) {
179891
+ updates.lastContextPercentage = piUsage.tokens / piUsage.contextWindow * 100;
179892
+ }
178616
179893
  }
178617
179894
  updateSessionMeta(db, sessionId, updates);
179895
+ try {
179896
+ const sessionMetaForTodo = getOrCreateSessionMeta(db, sessionId);
179897
+ if (!sessionMetaForTodo.isSubagent) {
179898
+ const msg = event.message;
179899
+ if (msg && msg.role === "assistant" && Array.isArray(msg.content)) {
179900
+ for (const block of msg.content) {
179901
+ if (!block || typeof block !== "object")
179902
+ continue;
179903
+ const b = block;
179904
+ if (b.type !== "toolCall")
179905
+ continue;
179906
+ if (typeof b.name !== "string")
179907
+ continue;
179908
+ if (!/^todo.*write$|^write.*todo$|^todowrite$/i.test(b.name)) {
179909
+ continue;
179910
+ }
179911
+ const args = b.arguments;
179912
+ const todos = args?.todos;
179913
+ if (!Array.isArray(todos))
179914
+ continue;
179915
+ const normalized = normalizeTodoStateJson(todos);
179916
+ if (normalized === null)
179917
+ continue;
179918
+ updateSessionMeta(db, sessionId, {
179919
+ lastTodoState: normalized
179920
+ });
179921
+ break;
179922
+ }
179923
+ }
179924
+ }
179925
+ } catch (err5) {
179926
+ warn("message_end: synthetic todowrite capture failed:", err5);
179927
+ }
178618
179928
  } catch (err5) {
178619
179929
  warn("message_end: persist session_meta usage failed:", err5);
178620
179930
  }
@@ -178646,7 +179956,7 @@ ${block}` : event.systemPrompt;
178646
179956
  try {
178647
179957
  await Promise.race([
178648
179958
  awaitInFlightHistorians(),
178649
- new Promise((resolve3) => setTimeout(resolve3, SHUTDOWN_DRAIN_MS))
179959
+ new Promise((resolve4) => setTimeout(resolve4, SHUTDOWN_DRAIN_MS))
178650
179960
  ]);
178651
179961
  } catch (err5) {
178652
179962
  warn("shutdown: historian drain threw:", err5);
@@ -178654,7 +179964,7 @@ ${block}` : event.systemPrompt;
178654
179964
  try {
178655
179965
  await Promise.race([
178656
179966
  awaitInFlightDreamers(),
178657
- new Promise((resolve3) => setTimeout(resolve3, SHUTDOWN_DRAIN_MS))
179967
+ new Promise((resolve4) => setTimeout(resolve4, SHUTDOWN_DRAIN_MS))
178658
179968
  ]);
178659
179969
  } catch (err5) {
178660
179970
  warn("shutdown: dreamer drain threw:", err5);