@cortexkit/pi-magic-context 0.21.5 → 0.21.7

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
@@ -8163,10 +8163,10 @@ var require_stringify = __commonJS((exports, module) => {
8163
8163
  replacer = null;
8164
8164
  indent = EMPTY;
8165
8165
  };
8166
- 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;
8166
+ var join10 = (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;
8167
8167
  var join_content = (inside, value, gap) => {
8168
8168
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
8169
- return join9(comment, inside, gap);
8169
+ return join10(comment, inside, gap);
8170
8170
  };
8171
8171
  var stringify_string = (holder, key, value) => {
8172
8172
  const raw = get_raw_string_literal(holder, key);
@@ -8188,13 +8188,13 @@ var require_stringify = __commonJS((exports, module) => {
8188
8188
  if (i !== 0) {
8189
8189
  inside += COMMA;
8190
8190
  }
8191
- const before = join9(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
8191
+ const before = join10(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
8192
8192
  inside += before || LF + deeper_gap;
8193
8193
  inside += stringify(i, value, deeper_gap) || STR_NULL;
8194
8194
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
8195
8195
  after_comma = process_comments(value, AFTER(i), deeper_gap);
8196
8196
  }
8197
- inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8197
+ inside += join10(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8198
8198
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
8199
8199
  };
8200
8200
  var object_stringify = (value, gap) => {
@@ -8215,13 +8215,13 @@ var require_stringify = __commonJS((exports, module) => {
8215
8215
  inside += COMMA;
8216
8216
  }
8217
8217
  first = false;
8218
- const before = join9(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
8218
+ const before = join10(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
8219
8219
  inside += before || LF + deeper_gap;
8220
8220
  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);
8221
8221
  after_comma = process_comments(value, AFTER(key), deeper_gap);
8222
8222
  };
8223
8223
  keys.forEach(iteratee);
8224
- inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8224
+ inside += join10(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8225
8225
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
8226
8226
  };
8227
8227
  function stringify(key, holder, gap) {
@@ -8343,7 +8343,57 @@ var init_rpc_notifications = __esm(() => {
8343
8343
 
8344
8344
  // src/index.ts
8345
8345
  import { createRequire as createRequire4 } from "node:module";
8346
- import { join as join19 } from "node:path";
8346
+ import { join as join20 } from "node:path";
8347
+
8348
+ // ../plugin/src/config/agent-disable.ts
8349
+ function isDreamerRunnable(config) {
8350
+ return !!config.dreamer && config.dreamer.disable !== true;
8351
+ }
8352
+ function clonePlainObject(value) {
8353
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
8354
+ return;
8355
+ }
8356
+ return { ...value };
8357
+ }
8358
+ function migrateLegacyEnabledForAgent(args) {
8359
+ const agent = clonePlainObject(args.patched[args.agentName]);
8360
+ if (!agent || !("enabled" in agent))
8361
+ return;
8362
+ const enabled = agent.enabled;
8363
+ const disable = agent.disable;
8364
+ delete agent.enabled;
8365
+ if (args.agentName === "historian") {
8366
+ args.warnings.push('Removed invalid "historian.enabled" in-memory (run doctor to persist).');
8367
+ args.patched.historian = agent;
8368
+ return;
8369
+ }
8370
+ if (args.agentName === "dreamer") {
8371
+ if (disable !== true && enabled === false) {
8372
+ agent.disable = true;
8373
+ args.warnings.push('Migrated "dreamer.enabled=false" → "dreamer.disable=true" in-memory (run doctor to persist). This now also disables manual /ctx-dream; for manual-only remove disable and set schedule="".');
8374
+ }
8375
+ args.patched.dreamer = agent;
8376
+ return;
8377
+ }
8378
+ if (disable !== true && enabled === false) {
8379
+ agent.disable = true;
8380
+ args.warnings.push('Migrated "sidekick.enabled=false" → "sidekick.disable=true" in-memory (run doctor to persist).');
8381
+ }
8382
+ args.patched.sidekick = agent;
8383
+ }
8384
+ function migrateLegacyAgentEnabledInMemory(rawConfig, warnings) {
8385
+ const shouldPatch = ["dreamer", "sidekick", "historian"].some((key) => {
8386
+ const agent = rawConfig[key];
8387
+ return typeof agent === "object" && agent !== null && !Array.isArray(agent) && "enabled" in agent;
8388
+ });
8389
+ if (!shouldPatch)
8390
+ return rawConfig;
8391
+ const patched = { ...rawConfig };
8392
+ migrateLegacyEnabledForAgent({ patched, agentName: "dreamer", warnings });
8393
+ migrateLegacyEnabledForAgent({ patched, agentName: "sidekick", warnings });
8394
+ migrateLegacyEnabledForAgent({ patched, agentName: "historian", warnings });
8395
+ return patched;
8396
+ }
8347
8397
 
8348
8398
  // ../plugin/src/features/magic-context/memory/project-identity.ts
8349
8399
  import { execSync } from "node:child_process";
@@ -141830,6 +141880,40 @@ function parseReportedLimit(message) {
141830
141880
  }
141831
141881
  return;
141832
141882
  }
141883
+ // ../plugin/src/features/magic-context/compartment-lease.ts
141884
+ var COMPARTMENT_LEASE_TTL_MS = 5 * 60 * 1000;
141885
+ var COMPARTMENT_LEASE_RENEWAL_MS = 60 * 1000;
141886
+ function acquireCompartmentLease(db, sessionId, holderId) {
141887
+ const acquiredAt = Date.now();
141888
+ const expiresAt = acquiredAt + COMPARTMENT_LEASE_TTL_MS;
141889
+ const result = db.prepare(`INSERT INTO compartment_state_lease (session_id, holder_id, acquired_at, expires_at)
141890
+ VALUES (?, ?, ?, ?)
141891
+ ON CONFLICT(session_id) DO UPDATE SET
141892
+ holder_id = excluded.holder_id,
141893
+ acquired_at = excluded.acquired_at,
141894
+ expires_at = excluded.expires_at
141895
+ WHERE compartment_state_lease.holder_id = excluded.holder_id
141896
+ OR compartment_state_lease.expires_at <= ?`).run(sessionId, holderId, acquiredAt, expiresAt, acquiredAt);
141897
+ if (result.changes !== 1) {
141898
+ return null;
141899
+ }
141900
+ return { sessionId, holderId, acquiredAt, expiresAt };
141901
+ }
141902
+ function renewCompartmentLease(db, sessionId, holderId) {
141903
+ const now = Date.now();
141904
+ const expiresAt = now + COMPARTMENT_LEASE_TTL_MS;
141905
+ const result = db.prepare(`UPDATE compartment_state_lease
141906
+ SET expires_at = ?, acquired_at = ?
141907
+ WHERE session_id = ? AND holder_id = ? AND expires_at > ?`).run(expiresAt, now, sessionId, holderId, now);
141908
+ return result.changes === 1;
141909
+ }
141910
+ function releaseCompartmentLease(db, sessionId, holderId) {
141911
+ db.prepare("DELETE FROM compartment_state_lease WHERE session_id = ? AND holder_id = ?").run(sessionId, holderId);
141912
+ }
141913
+ function isCompartmentLeaseHeld(db, sessionId, holderId) {
141914
+ const row = db.prepare("SELECT 1 FROM compartment_state_lease WHERE session_id = ? AND holder_id = ? AND expires_at > ?").get(sessionId, holderId, Date.now());
141915
+ return row != null;
141916
+ }
141833
141917
 
141834
141918
  // ../plugin/src/features/magic-context/compartment-storage.ts
141835
141919
  var insertCompartmentStatements = new WeakMap;
@@ -141940,6 +142024,38 @@ function replaceAllCompartmentState(db, sessionId, compartments, facts) {
141940
142024
  db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE session_id = ?").run(sessionId);
141941
142025
  })();
141942
142026
  }
142027
+ function replaceAllCompartmentStateAndBumpDepth(db, holderId, sessionId, compartments, facts, depthStartOrdinal, depthEndOrdinal) {
142028
+ const now = Date.now();
142029
+ db.exec("BEGIN IMMEDIATE");
142030
+ let finished = false;
142031
+ try {
142032
+ if (!isCompartmentLeaseHeld(db, sessionId, holderId)) {
142033
+ db.exec("ROLLBACK");
142034
+ finished = true;
142035
+ return false;
142036
+ }
142037
+ db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
142038
+ db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
142039
+ insertCompartmentRows(db, sessionId, compartments, now);
142040
+ insertFactRows(db, sessionId, facts, now);
142041
+ db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE session_id = ?").run(sessionId);
142042
+ if (depthEndOrdinal >= depthStartOrdinal) {
142043
+ const stmt = getIncrementDepthStatement(db);
142044
+ for (let ordinal = depthStartOrdinal;ordinal <= depthEndOrdinal; ordinal += 1) {
142045
+ stmt.run(sessionId, ordinal, getHarness());
142046
+ }
142047
+ }
142048
+ db.exec("COMMIT");
142049
+ finished = true;
142050
+ return true;
142051
+ } finally {
142052
+ if (!finished) {
142053
+ try {
142054
+ db.exec("ROLLBACK");
142055
+ } catch {}
142056
+ }
142057
+ }
142058
+ }
141943
142059
  function buildCompartmentBlock(compartments, facts, memoryBlock, dateRanges) {
141944
142060
  const lines = [];
141945
142061
  if (memoryBlock) {
@@ -142007,12 +142123,37 @@ function getRecompStaging(db, sessionId) {
142007
142123
  lastEndMessage: lastEnd
142008
142124
  };
142009
142125
  }
142010
- function promoteRecompStaging(db, sessionId) {
142126
+ function promoteRecompStaging(db, sessionId, holderId) {
142011
142127
  const now = Date.now();
142012
- return db.transaction(() => {
142128
+ if (!holderId) {
142129
+ return db.transaction(() => {
142130
+ const staging = getRecompStaging(db, sessionId);
142131
+ if (!staging || staging.compartments.length === 0)
142132
+ return null;
142133
+ db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
142134
+ db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
142135
+ insertCompartmentRows(db, sessionId, staging.compartments, now);
142136
+ insertFactRows(db, sessionId, staging.facts, now);
142137
+ db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
142138
+ db.prepare("DELETE FROM recomp_facts WHERE session_id = ?").run(sessionId);
142139
+ db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE session_id = ?").run(sessionId);
142140
+ return { compartments: staging.compartments, facts: staging.facts };
142141
+ })();
142142
+ }
142143
+ db.exec("BEGIN IMMEDIATE");
142144
+ let finished = false;
142145
+ try {
142146
+ if (!isCompartmentLeaseHeld(db, sessionId, holderId)) {
142147
+ db.exec("ROLLBACK");
142148
+ finished = true;
142149
+ return null;
142150
+ }
142013
142151
  const staging = getRecompStaging(db, sessionId);
142014
- if (!staging || staging.compartments.length === 0)
142152
+ if (!staging || staging.compartments.length === 0) {
142153
+ db.exec("ROLLBACK");
142154
+ finished = true;
142015
142155
  return null;
142156
+ }
142016
142157
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
142017
142158
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
142018
142159
  insertCompartmentRows(db, sessionId, staging.compartments, now);
@@ -142020,8 +142161,16 @@ function promoteRecompStaging(db, sessionId) {
142020
142161
  db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
142021
142162
  db.prepare("DELETE FROM recomp_facts WHERE session_id = ?").run(sessionId);
142022
142163
  db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE session_id = ?").run(sessionId);
142164
+ db.exec("COMMIT");
142165
+ finished = true;
142023
142166
  return { compartments: staging.compartments, facts: staging.facts };
142024
- })();
142167
+ } finally {
142168
+ if (!finished) {
142169
+ try {
142170
+ db.exec("ROLLBACK");
142171
+ } catch {}
142172
+ }
142173
+ }
142025
142174
  }
142026
142175
  function invalidateAllMemoryBlockCaches(db) {
142027
142176
  try {
@@ -142669,6 +142818,22 @@ var MIGRATIONS = [
142669
142818
  db.exec("ALTER TABLE session_meta ADD COLUMN pending_pi_compaction_marker_state TEXT");
142670
142819
  }
142671
142820
  }
142821
+ },
142822
+ {
142823
+ version: 19,
142824
+ description: "Add compartment state lease table",
142825
+ up: (db) => {
142826
+ db.exec(`
142827
+ CREATE TABLE IF NOT EXISTS compartment_state_lease (
142828
+ session_id TEXT PRIMARY KEY NOT NULL,
142829
+ holder_id TEXT NOT NULL,
142830
+ acquired_at INTEGER NOT NULL,
142831
+ expires_at INTEGER NOT NULL
142832
+ );
142833
+ CREATE INDEX IF NOT EXISTS idx_compartment_state_lease_expires
142834
+ ON compartment_state_lease(expires_at);
142835
+ `);
142836
+ }
142672
142837
  }
142673
142838
  ];
142674
142839
  function ensureMigrationsTable(db) {
@@ -143124,6 +143289,15 @@ function initializeDatabase(db) {
143124
143289
  );
143125
143290
  CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
143126
143291
 
143292
+ CREATE TABLE IF NOT EXISTS compartment_state_lease (
143293
+ session_id TEXT PRIMARY KEY NOT NULL,
143294
+ holder_id TEXT NOT NULL,
143295
+ acquired_at INTEGER NOT NULL,
143296
+ expires_at INTEGER NOT NULL
143297
+ );
143298
+ CREATE INDEX IF NOT EXISTS idx_compartment_state_lease_expires
143299
+ ON compartment_state_lease(expires_at);
143300
+
143127
143301
  CREATE TABLE IF NOT EXISTS compression_depth (
143128
143302
  session_id TEXT NOT NULL,
143129
143303
  message_ordinal INTEGER NOT NULL,
@@ -144046,6 +144220,7 @@ function getPendingPiCompactionMarkerState(db, sessionId) {
144046
144220
  return parsed;
144047
144221
  }
144048
144222
  } catch {}
144223
+ db.prepare("UPDATE session_meta SET pending_pi_compaction_marker_state = NULL WHERE session_id = ? AND pending_pi_compaction_marker_state = ?").run(sessionId, raw);
144049
144224
  return null;
144050
144225
  }
144051
144226
  function setPendingPiCompactionMarkerState(db, sessionId, state) {
@@ -145266,10 +145441,98 @@ function isTodoItem(value) {
145266
145441
  return typeof todo.content === "string" && typeof todo.status === "string" && (todo.priority === undefined || typeof todo.priority === "string");
145267
145442
  }
145268
145443
 
145444
+ // ../plugin/src/shared/announcement.ts
145445
+ init_data_path();
145446
+ import * as fs2 from "node:fs";
145447
+ import * as path5 from "node:path";
145448
+ var ANNOUNCEMENT_VERSION = "0.21.7";
145449
+ var ANNOUNCEMENT_FEATURES = [
145450
+ "Pi parity sweep: 44 audit findings fixed, including a critical SHIP-BLOCKER where /ctx-flush did not drain the pending Pi compaction queue.",
145451
+ "Pi historian recovery fix: empty/no-op historian returns now clear emergency recovery so sessions cannot loop forever at 95%.",
145452
+ "trimPiMessagesToBoundary now sweeps non-contiguous tool-result orphans, fixing provider 400s after compaction in long Pi sessions.",
145453
+ "Hidden subagent tool isolation: historian, dreamer, and sidekick can no longer spawn subagents or run unsafe tools.",
145454
+ "TUI sidebar and /ctx-status header now show execute threshold inline: '47.5% / 65%' on the left, '475K / 1.0M' on the right.",
145455
+ "doctor --issue now caps GitHub issue bodies at ~60KB with a dedicated 'Recent errors' section so reports stay submittable."
145456
+ ];
145457
+ var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
145458
+ var STATE_FILENAME = "last_announced_version";
145459
+ function getStateFilePath() {
145460
+ return path5.join(getMagicContextStorageDir(), STATE_FILENAME);
145461
+ }
145462
+ function readLastAnnouncedVersion() {
145463
+ try {
145464
+ const file = getStateFilePath();
145465
+ if (!fs2.existsSync(file))
145466
+ return "";
145467
+ return fs2.readFileSync(file, "utf-8").trim();
145468
+ } catch {
145469
+ return "";
145470
+ }
145471
+ }
145472
+ function markAnnouncementSeen(version) {
145473
+ if (!version)
145474
+ return;
145475
+ try {
145476
+ const dir = getMagicContextStorageDir();
145477
+ fs2.mkdirSync(dir, { recursive: true });
145478
+ fs2.writeFileSync(getStateFilePath(), version);
145479
+ } catch {}
145480
+ }
145481
+ function shouldShowAnnouncement() {
145482
+ if (!ANNOUNCEMENT_VERSION || ANNOUNCEMENT_FEATURES.length === 0)
145483
+ return false;
145484
+ return readLastAnnouncedVersion() !== ANNOUNCEMENT_VERSION;
145485
+ }
145486
+
145269
145487
  // src/index.ts
145270
145488
  init_data_path();
145271
145489
  init_logger();
145272
145490
 
145491
+ // ../plugin/src/shared/resolve-fallbacks.ts
145492
+ function resolveFallbackChain(agentName, userFallbacks) {
145493
+ const userList = normalizeUserFallbacks(userFallbacks);
145494
+ if (userList.length > 0) {
145495
+ return dedupe(userList.filter(isValidModelSpec));
145496
+ }
145497
+ const builtin = getAgentFallbackModels(agentName);
145498
+ if (!builtin || builtin.length === 0)
145499
+ return [];
145500
+ return dedupe(builtin.filter(isValidModelSpec));
145501
+ }
145502
+ function normalizeUserFallbacks(userFallbacks) {
145503
+ if (!userFallbacks)
145504
+ return [];
145505
+ if (typeof userFallbacks === "string") {
145506
+ const trimmed = userFallbacks.trim();
145507
+ return trimmed ? [trimmed] : [];
145508
+ }
145509
+ return userFallbacks.map((s) => s.trim()).filter((s) => s.length > 0);
145510
+ }
145511
+ function isValidModelSpec(spec) {
145512
+ const slash = spec.indexOf("/");
145513
+ return slash > 0 && slash < spec.length - 1;
145514
+ }
145515
+ function dedupe(list) {
145516
+ const seen = new Set;
145517
+ const out = [];
145518
+ for (const item of list) {
145519
+ if (seen.has(item))
145520
+ continue;
145521
+ seen.add(item);
145522
+ out.push(item);
145523
+ }
145524
+ return out;
145525
+ }
145526
+ function parseProviderModel(spec) {
145527
+ const slash = spec.indexOf("/");
145528
+ if (slash < 1 || slash >= spec.length - 1)
145529
+ return null;
145530
+ return {
145531
+ providerID: spec.slice(0, slash).trim(),
145532
+ modelID: spec.slice(slash + 1).trim()
145533
+ };
145534
+ }
145535
+
145273
145536
  // ../plugin/src/features/magic-context/sidekick/core.ts
145274
145537
  var SIDEKICK_SYSTEM_PROMPT = `You are Sidekick, a focused memory-retrieval subagent for an AI coding assistant.
145275
145538
 
@@ -145295,17 +145558,17 @@ init_logger();
145295
145558
 
145296
145559
  // src/subagent-runner.ts
145297
145560
  import * as childProcess from "node:child_process";
145298
- import { existsSync as existsSync6 } from "node:fs";
145561
+ import { existsSync as existsSync7 } from "node:fs";
145299
145562
  import { createRequire as createRequire3 } from "node:module";
145300
- import { dirname as dirname3, join as join8, resolve as resolvePath } from "node:path";
145563
+ import { dirname as dirname3, join as join9, resolve as resolvePath } from "node:path";
145301
145564
  import { createInterface } from "node:readline";
145302
145565
  import { fileURLToPath } from "node:url";
145303
145566
  function resolveBundledPiCli() {
145304
145567
  try {
145305
145568
  const require_ = createRequire3(import.meta.url);
145306
145569
  const pkgJson = require_.resolve("@earendil-works/pi-coding-agent/package.json");
145307
- const cliPath = join8(dirname3(pkgJson), "dist/cli.js");
145308
- if (existsSync6(cliPath))
145570
+ const cliPath = join9(dirname3(pkgJson), "dist/cli.js");
145571
+ if (existsSync7(cliPath))
145309
145572
  return cliPath;
145310
145573
  return null;
145311
145574
  } catch {
@@ -145316,7 +145579,7 @@ function resolveSubagentEntryPath() {
145316
145579
  try {
145317
145580
  const here = dirname3(fileURLToPath(import.meta.url));
145318
145581
  const candidate = resolvePath(here, "subagent-entry.js");
145319
- if (existsSync6(candidate))
145582
+ if (existsSync7(candidate))
145320
145583
  return candidate;
145321
145584
  return;
145322
145585
  } catch {
@@ -145348,6 +145611,27 @@ class PiSubagentRunner {
145348
145611
  this.spawnImpl = options.spawnImpl ?? childProcess.spawn;
145349
145612
  }
145350
145613
  async run(options) {
145614
+ const models = [options.model, ...options.fallbackModels ?? []].filter((model) => typeof model === "string" && model.length > 0);
145615
+ const attempts = models.length > 0 ? models : [undefined];
145616
+ let lastResult = null;
145617
+ for (let index = 0;index < attempts.length; index += 1) {
145618
+ const model = attempts[index];
145619
+ const attemptOptions = {
145620
+ ...options,
145621
+ model,
145622
+ fallbackModels: undefined
145623
+ };
145624
+ const result = await this.runOnce(attemptOptions);
145625
+ if (result.ok)
145626
+ return result;
145627
+ lastResult = result;
145628
+ if (index >= attempts.length - 1 || !isFallbackEligible(result.reason)) {
145629
+ return result;
145630
+ }
145631
+ }
145632
+ return lastResult ?? this.runOnce({ ...options, fallbackModels: undefined });
145633
+ }
145634
+ async runOnce(options) {
145351
145635
  const startTime = Date.now();
145352
145636
  if (options.signal?.aborted) {
145353
145637
  return {
@@ -145484,7 +145768,7 @@ class PiSubagentRunner {
145484
145768
  const m = e.message;
145485
145769
  if (m.role === "assistant") {
145486
145770
  const hasToolCall = Array.isArray(m.content) && m.content.some((c) => typeof c === "object" && c !== null && c.type === "toolCall");
145487
- const isTerminalStopReason = typeof m.stopReason === "string" && (m.stopReason === "stop" || m.stopReason === "length" || m.stopReason === "error" || m.stopReason === "aborted");
145771
+ const isTerminalStopReason = typeof m.stopReason === "string" && (m.stopReason === "stop" || m.stopReason === "length" || m.stopReason === "aborted");
145488
145772
  if (isTerminalStopReason && !hasToolCall) {
145489
145773
  sawAgentEnd = true;
145490
145774
  const result = extractFinalAssistant(accumulatedMessages);
@@ -145506,6 +145790,10 @@ class PiSubagentRunner {
145506
145790
  }
145507
145791
  if (sawAgentEnd && !drainTimerStarted) {
145508
145792
  drainTimerStarted = true;
145793
+ if (timeoutHandle) {
145794
+ clearTimeout(timeoutHandle);
145795
+ timeoutHandle = undefined;
145796
+ }
145509
145797
  drainTimerHandle = setTimeout(() => {
145510
145798
  if (settled)
145511
145799
  return;
@@ -145588,6 +145876,20 @@ class PiSubagentRunner {
145588
145876
  });
145589
145877
  return;
145590
145878
  }
145879
+ if (code !== 0 || signal !== null) {
145880
+ settle({
145881
+ ok: false,
145882
+ reason: "non_zero_exit",
145883
+ error: `pi exited (code=${code}, signal=${signal}) after terminal assistant event. stderr: ${stderr.slice(0, 500) || "(empty)"}`,
145884
+ durationMs: Date.now() - startTime,
145885
+ meta: {
145886
+ stderr: stderr.length > 0 ? stderr : undefined,
145887
+ exitCode: code,
145888
+ signal
145889
+ }
145890
+ });
145891
+ return;
145892
+ }
145591
145893
  if (finalStopReason === "error" || finalStopReason === "aborted" || finalStopReason === "length") {
145592
145894
  settle({
145593
145895
  ok: false,
@@ -145649,6 +145951,9 @@ class PiSubagentRunner {
145649
145951
  });
145650
145952
  }
145651
145953
  }
145954
+ function isFallbackEligible(reason) {
145955
+ return reason === "model_failed" || reason === "truncated" || reason === "non_zero_exit" || reason === "no_assistant";
145956
+ }
145652
145957
  function buildArgs(options) {
145653
145958
  const args = [
145654
145959
  "--print",
@@ -145659,7 +145964,8 @@ function buildArgs(options) {
145659
145964
  "--no-skills",
145660
145965
  "--no-prompt-templates"
145661
145966
  ];
145662
- if (SUBAGENT_ENTRY_PATH) {
145967
+ const shouldLoadSubagentExtension = SUBAGENT_ENTRY_PATH && (options.agent === "sidekick" || DREAMER_ACTION_AGENTS.has(options.agent));
145968
+ if (shouldLoadSubagentExtension) {
145663
145969
  args.push("--extension", SUBAGENT_ENTRY_PATH);
145664
145970
  if (DREAMER_ACTION_AGENTS.has(options.agent)) {
145665
145971
  args.push("--magic-context-dreamer-actions");
@@ -145668,11 +145974,8 @@ function buildArgs(options) {
145668
145974
  if (options.systemPrompt && options.systemPrompt.length > 0) {
145669
145975
  args.push("--system-prompt", options.systemPrompt);
145670
145976
  }
145671
- const models = [options.model, ...options.fallbackModels ?? []].filter((model) => typeof model === "string" && model.length > 0);
145672
- if (models.length > 1) {
145673
- args.push("--models", models.join(","));
145674
- } else if (models.length === 1) {
145675
- args.push("--model", models[0]);
145977
+ if (typeof options.model === "string" && options.model.length > 0) {
145978
+ args.push("--model", options.model);
145676
145979
  }
145677
145980
  if (options.thinkingLevel) {
145678
145981
  args.push("--thinking", options.thinkingLevel);
@@ -145762,6 +146065,7 @@ function registerCtxAugCommand(pi, config) {
145762
146065
  systemPrompt: config.systemPrompt ?? SIDEKICK_SYSTEM_PROMPT,
145763
146066
  userMessage: prompt,
145764
146067
  model: config.model,
146068
+ fallbackModels: config.fallbackModels,
145765
146069
  timeoutMs: config.timeoutMs ?? 30000,
145766
146070
  cwd: ctx.cwd,
145767
146071
  signal: ctx.signal,
@@ -145972,61 +146276,14 @@ function clearStaleEntries(db, maxAgeMs) {
145972
146276
  // src/commands/ctx-dream.ts
145973
146277
  init_logger();
145974
146278
  // ../plugin/src/features/magic-context/dreamer/runner.ts
145975
- import { existsSync as existsSync8 } from "node:fs";
145976
- import { join as join11 } from "node:path";
146279
+ import { existsSync as existsSync9 } from "node:fs";
146280
+ import { join as join12 } from "node:path";
145977
146281
 
145978
146282
  // ../plugin/src/shared/index.ts
145979
146283
  init_logger();
145980
146284
 
145981
146285
  // ../plugin/src/shared/model-suggestion-retry.ts
145982
146286
  init_logger();
145983
-
145984
- // ../plugin/src/shared/resolve-fallbacks.ts
145985
- function resolveFallbackChain(agentName, userFallbacks) {
145986
- const userList = normalizeUserFallbacks(userFallbacks);
145987
- if (userList.length > 0) {
145988
- return dedupe(userList.filter(isValidModelSpec));
145989
- }
145990
- const builtin = getAgentFallbackModels(agentName);
145991
- if (!builtin || builtin.length === 0)
145992
- return [];
145993
- return dedupe(builtin.filter(isValidModelSpec));
145994
- }
145995
- function normalizeUserFallbacks(userFallbacks) {
145996
- if (!userFallbacks)
145997
- return [];
145998
- if (typeof userFallbacks === "string") {
145999
- const trimmed = userFallbacks.trim();
146000
- return trimmed ? [trimmed] : [];
146001
- }
146002
- return userFallbacks.map((s) => s.trim()).filter((s) => s.length > 0);
146003
- }
146004
- function isValidModelSpec(spec) {
146005
- const slash = spec.indexOf("/");
146006
- return slash > 0 && slash < spec.length - 1;
146007
- }
146008
- function dedupe(list) {
146009
- const seen = new Set;
146010
- const out = [];
146011
- for (const item of list) {
146012
- if (seen.has(item))
146013
- continue;
146014
- seen.add(item);
146015
- out.push(item);
146016
- }
146017
- return out;
146018
- }
146019
- function parseProviderModel(spec) {
146020
- const slash = spec.indexOf("/");
146021
- if (slash < 1 || slash >= spec.length - 1)
146022
- return null;
146023
- return {
146024
- providerID: spec.slice(0, slash).trim(),
146025
- modelID: spec.slice(slash + 1).trim()
146026
- };
146027
- }
146028
-
146029
- // ../plugin/src/shared/model-suggestion-retry.ts
146030
146287
  function extractMessage(error) {
146031
146288
  if (typeof error === "string")
146032
146289
  return error;
@@ -146267,20 +146524,20 @@ init_logger();
146267
146524
  await init_sqlite();
146268
146525
 
146269
146526
  // ../plugin/src/features/magic-context/key-files/identify-key-files.ts
146270
- import { readFileSync as readFileSync4 } from "node:fs";
146271
- import { join as join10 } from "node:path";
146527
+ import { readFileSync as readFileSync5 } from "node:fs";
146528
+ import { join as join11 } from "node:path";
146272
146529
  init_logger();
146273
146530
 
146274
146531
  // ../plugin/src/features/magic-context/key-files/aft-availability.ts
146275
146532
  var import_comment_json = __toESM(require_src2(), 1);
146276
- import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
146533
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
146277
146534
  import { homedir as homedir3 } from "node:os";
146278
- import { join as join9 } from "node:path";
146535
+ import { join as join10 } from "node:path";
146279
146536
  var overrideAvailability = null;
146280
- function parseConfig(path5) {
146281
- if (!existsSync7(path5))
146537
+ function parseConfig(path6) {
146538
+ if (!existsSync8(path6))
146282
146539
  return null;
146283
- return import_comment_json.parse(readFileSync3(path5, "utf-8"));
146540
+ return import_comment_json.parse(readFileSync4(path6, "utf-8"));
146284
146541
  }
146285
146542
  function entryMatchesAft(entry) {
146286
146543
  const value = Array.isArray(entry) ? entry[0] : entry;
@@ -146302,15 +146559,15 @@ function hasAftAtKeys(value, keys) {
146302
146559
  function getAftAvailability() {
146303
146560
  const home = process.env.HOME || homedir3();
146304
146561
  const opencodePaths = [
146305
- join9(home, ".config", "opencode", "opencode.jsonc"),
146306
- join9(home, ".config", "opencode", "opencode.json")
146562
+ join10(home, ".config", "opencode", "opencode.jsonc"),
146563
+ join10(home, ".config", "opencode", "opencode.json")
146307
146564
  ];
146308
- const piPaths = [join9(home, ".pi", "agent", "settings.json")];
146565
+ const piPaths = [join10(home, ".pi", "agent", "settings.json")];
146309
146566
  const checkedPaths = [...opencodePaths, ...piPaths];
146310
146567
  let opencode = false;
146311
- for (const path5 of opencodePaths) {
146568
+ for (const path6 of opencodePaths) {
146312
146569
  try {
146313
- const config = parseConfig(path5);
146570
+ const config = parseConfig(path6);
146314
146571
  if (hasAftAtKeys(config, ["plugin", "plugins", "mcp", "mcp_servers"])) {
146315
146572
  opencode = true;
146316
146573
  break;
@@ -146318,9 +146575,9 @@ function getAftAvailability() {
146318
146575
  } catch {}
146319
146576
  }
146320
146577
  let pi = false;
146321
- for (const path5 of piPaths) {
146578
+ for (const path6 of piPaths) {
146322
146579
  try {
146323
- const config = parseConfig(path5);
146580
+ const config = parseConfig(path6);
146324
146581
  if (hasAftAtKeys(config, ["packages", "extensions"])) {
146325
146582
  pi = true;
146326
146583
  break;
@@ -146769,7 +147026,7 @@ async function runKeyFilesTask(args) {
146769
147026
  if (row.staleReason !== null || row.generationConfigHash !== configHash)
146770
147027
  return false;
146771
147028
  try {
146772
- return sha256(readFileSync4(join10(projectPath, row.path))) === row.contentHash;
147029
+ return sha256(readFileSync5(join11(projectPath, row.path))) === row.contentHash;
146773
147030
  } catch {
146774
147031
  return false;
146775
147032
  }
@@ -147693,6 +147950,17 @@ function buildDreamTaskPrompt(task, args) {
147693
147950
  }
147694
147951
 
147695
147952
  // ../plugin/src/features/magic-context/user-memory/storage-user-memory.ts
147953
+ function insertUserMemoryCandidates(db, candidates) {
147954
+ if (candidates.length === 0)
147955
+ return;
147956
+ const now = Date.now();
147957
+ const stmt = db.prepare("INSERT INTO user_memory_candidates (content, session_id, source_compartment_start, source_compartment_end, created_at) VALUES (?, ?, ?, ?, ?)");
147958
+ db.transaction(() => {
147959
+ for (const c of candidates) {
147960
+ stmt.run(c.content, c.sessionId, c.sourceCompartmentStart ?? null, c.sourceCompartmentEnd ?? null, now);
147961
+ }
147962
+ })();
147963
+ }
147696
147964
  function getUserMemoryCandidates(db) {
147697
147965
  const rows = db.prepare("SELECT id, content, session_id, source_compartment_start, source_compartment_end, created_at FROM user_memory_candidates ORDER BY created_at ASC").all();
147698
147966
  return rows.map((r) => ({
@@ -147966,11 +148234,11 @@ function logWithStackHead(message, stackHead) {
147966
148234
  log(message, stackHead ? { stackHead } : undefined);
147967
148235
  }
147968
148236
  function getOpenCodeDbPath2() {
147969
- return join11(getDataDir(), "opencode", "opencode.db");
148237
+ return join12(getDataDir(), "opencode", "opencode.db");
147970
148238
  }
147971
148239
  function openOpenCodeDb() {
147972
148240
  const dbPath = getOpenCodeDbPath2();
147973
- if (!existsSync8(dbPath)) {
148241
+ if (!existsSync9(dbPath)) {
147974
148242
  log(`[key-files] OpenCode DB not found at ${dbPath} — skipping`);
147975
148243
  return null;
147976
148244
  }
@@ -148101,8 +148369,8 @@ async function runDream(args) {
148101
148369
  try {
148102
148370
  const docsDir = args.sessionDirectory ?? args.projectIdentity;
148103
148371
  const existingDocs = taskName === "maintain-docs" ? {
148104
- architecture: existsSync8(join11(docsDir, "ARCHITECTURE.md")),
148105
- structure: existsSync8(join11(docsDir, "STRUCTURE.md"))
148372
+ architecture: existsSync9(join12(docsDir, "ARCHITECTURE.md")),
148373
+ structure: existsSync9(join12(docsDir, "STRUCTURE.md"))
148106
148374
  } : undefined;
148107
148375
  const userMemories = taskName === "archive-stale" ? getActiveUserMemories(args.db).map((um) => ({
148108
148376
  id: um.id,
@@ -149518,10 +149786,10 @@ function mergeDefs(...defs) {
149518
149786
  function cloneDef(schema) {
149519
149787
  return mergeDefs(schema._zod.def);
149520
149788
  }
149521
- function getElementAtPath(obj, path5) {
149522
- if (!path5)
149789
+ function getElementAtPath(obj, path6) {
149790
+ if (!path6)
149523
149791
  return obj;
149524
- return path5.reduce((acc, key) => acc?.[key], obj);
149792
+ return path6.reduce((acc, key) => acc?.[key], obj);
149525
149793
  }
149526
149794
  function promiseAllObject(promisesObj) {
149527
149795
  const keys = Object.keys(promisesObj);
@@ -149929,11 +150197,11 @@ function explicitlyAborted(x, startIndex = 0) {
149929
150197
  }
149930
150198
  return false;
149931
150199
  }
149932
- function prefixIssues(path5, issues) {
150200
+ function prefixIssues(path6, issues) {
149933
150201
  return issues.map((iss) => {
149934
150202
  var _a2;
149935
150203
  (_a2 = iss).path ?? (_a2.path = []);
149936
- iss.path.unshift(path5);
150204
+ iss.path.unshift(path6);
149937
150205
  return iss;
149938
150206
  });
149939
150207
  }
@@ -150080,16 +150348,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
150080
150348
  }
150081
150349
  function formatError(error, mapper = (issue2) => issue2.message) {
150082
150350
  const fieldErrors = { _errors: [] };
150083
- const processError = (error2, path5 = []) => {
150351
+ const processError = (error2, path6 = []) => {
150084
150352
  for (const issue2 of error2.issues) {
150085
150353
  if (issue2.code === "invalid_union" && issue2.errors.length) {
150086
- issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
150354
+ issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
150087
150355
  } else if (issue2.code === "invalid_key") {
150088
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
150356
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
150089
150357
  } else if (issue2.code === "invalid_element") {
150090
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
150358
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
150091
150359
  } else {
150092
- const fullpath = [...path5, ...issue2.path];
150360
+ const fullpath = [...path6, ...issue2.path];
150093
150361
  if (fullpath.length === 0) {
150094
150362
  fieldErrors._errors.push(mapper(issue2));
150095
150363
  } else {
@@ -150116,17 +150384,17 @@ function formatError(error, mapper = (issue2) => issue2.message) {
150116
150384
  }
150117
150385
  function treeifyError(error, mapper = (issue2) => issue2.message) {
150118
150386
  const result = { errors: [] };
150119
- const processError = (error2, path5 = []) => {
150387
+ const processError = (error2, path6 = []) => {
150120
150388
  var _a2, _b;
150121
150389
  for (const issue2 of error2.issues) {
150122
150390
  if (issue2.code === "invalid_union" && issue2.errors.length) {
150123
- issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
150391
+ issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
150124
150392
  } else if (issue2.code === "invalid_key") {
150125
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
150393
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
150126
150394
  } else if (issue2.code === "invalid_element") {
150127
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
150395
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
150128
150396
  } else {
150129
- const fullpath = [...path5, ...issue2.path];
150397
+ const fullpath = [...path6, ...issue2.path];
150130
150398
  if (fullpath.length === 0) {
150131
150399
  result.errors.push(mapper(issue2));
150132
150400
  continue;
@@ -150158,8 +150426,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
150158
150426
  }
150159
150427
  function toDotPath(_path) {
150160
150428
  const segs = [];
150161
- const path5 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
150162
- for (const seg of path5) {
150429
+ const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
150430
+ for (const seg of path6) {
150163
150431
  if (typeof seg === "number")
150164
150432
  segs.push(`[${seg}]`);
150165
150433
  else if (typeof seg === "symbol")
@@ -162618,13 +162886,13 @@ function resolveRef(ref, ctx) {
162618
162886
  if (!ref.startsWith("#")) {
162619
162887
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
162620
162888
  }
162621
- const path5 = ref.slice(1).split("/").filter(Boolean);
162622
- if (path5.length === 0) {
162889
+ const path6 = ref.slice(1).split("/").filter(Boolean);
162890
+ if (path6.length === 0) {
162623
162891
  return ctx.rootSchema;
162624
162892
  }
162625
162893
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
162626
- if (path5[0] === defsKey) {
162627
- const key = path5[1];
162894
+ if (path6[0] === defsKey) {
162895
+ const key = path6[1];
162628
162896
  if (!key || !ctx.defs[key]) {
162629
162897
  throw new Error(`Reference not found: ${ref}`);
162630
162898
  }
@@ -163093,7 +163361,6 @@ var DEFAULT_DREAMER_TASKS = [
163093
163361
  ];
163094
163362
  var PiThinkingLevelSchema = exports_external.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional();
163095
163363
  var DreamerConfigSchema = AgentOverrideConfigSchema.merge(exports_external.object({
163096
- enabled: exports_external.boolean().default(false),
163097
163364
  schedule: exports_external.string().default("02:00-06:00"),
163098
163365
  max_runtime_minutes: exports_external.number().min(10).default(120),
163099
163366
  tasks: exports_external.array(DreamingTaskSchema).default(DEFAULT_DREAMER_TASKS),
@@ -163111,7 +163378,6 @@ var DreamerConfigSchema = AgentOverrideConfigSchema.merge(exports_external.objec
163111
163378
  thinking_level: PiThinkingLevelSchema
163112
163379
  }));
163113
163380
  var SidekickConfigSchema = AgentOverrideConfigSchema.extend({
163114
- enabled: exports_external.boolean().default(false),
163115
163381
  timeout_ms: exports_external.number().default(30000),
163116
163382
  system_prompt: exports_external.string().optional(),
163117
163383
  thinking_level: PiThinkingLevelSchema
@@ -163295,9 +163561,9 @@ function getEmbeddingProviderIdentity(config2) {
163295
163561
  }
163296
163562
 
163297
163563
  // ../plugin/src/features/magic-context/memory/embedding-local.ts
163298
- import { mkdirSync as mkdirSync4 } from "node:fs";
163564
+ import { mkdirSync as mkdirSync5 } from "node:fs";
163299
163565
  import { open, stat, unlink, writeFile } from "node:fs/promises";
163300
- import { dirname as dirname4, join as join12 } from "node:path";
163566
+ import { dirname as dirname4, join as join13 } from "node:path";
163301
163567
  import { pathToFileURL } from "node:url";
163302
163568
  init_data_path();
163303
163569
  init_logger();
@@ -163364,7 +163630,7 @@ async function injectWasmOrtForElectron() {
163364
163630
  const { createRequire: createRequireFn } = await import("node:module");
163365
163631
  const requireFn = createRequireFn(import.meta.url);
163366
163632
  const pkgPath = requireFn.resolve("onnxruntime-web/package.json");
163367
- const distDir = join12(dirname4(pkgPath), "dist");
163633
+ const distDir = join13(dirname4(pkgPath), "dist");
163368
163634
  const wasmPathsPrefix = `${pathToFileURL(distDir).href}/`;
163369
163635
  if (ortWeb.env?.wasm) {
163370
163636
  ortWeb.env.wasm.wasmPaths = wasmPathsPrefix;
@@ -163475,15 +163741,15 @@ class LocalEmbeddingProvider {
163475
163741
  if (LogLevel && "ERROR" in LogLevel) {
163476
163742
  env.logLevel = LogLevel.ERROR;
163477
163743
  }
163478
- const modelCacheDir = join12(getMagicContextStorageDir(), "models");
163744
+ const modelCacheDir = join13(getMagicContextStorageDir(), "models");
163479
163745
  try {
163480
- mkdirSync4(modelCacheDir, { recursive: true });
163746
+ mkdirSync5(modelCacheDir, { recursive: true });
163481
163747
  env.cacheDir = modelCacheDir;
163482
163748
  } catch {
163483
163749
  log("[magic-context] could not create model cache dir, using library default");
163484
163750
  }
163485
163751
  const createPipeline = transformersModule.pipeline;
163486
- const lockPath = join12(modelCacheDir, ".load.lock");
163752
+ const lockPath = join13(modelCacheDir, ".load.lock");
163487
163753
  const releaseLock = await acquireModelLoadLock(lockPath);
163488
163754
  const stopHeartbeat = startLockHeartbeat(lockPath);
163489
163755
  try {
@@ -164651,7 +164917,7 @@ async function startDreamScheduleTimer(args) {
164651
164917
  const db = openDatabase();
164652
164918
  await args.ensureRegistered(args.directory, db);
164653
164919
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
164654
- const dreamingEnabled = Boolean(args.dreamerConfig?.enabled && args.dreamerConfig.schedule?.trim());
164920
+ const dreamingEnabled = Boolean(args.dreamerConfig && args.dreamerConfig.disable !== true && args.dreamerConfig.schedule?.trim());
164655
164921
  const embeddingSweepEnabled = snapshot?.enabled ?? false;
164656
164922
  const commitIndexingEnabled = snapshot?.gitCommitEnabled ?? false;
164657
164923
  if (!dreamingEnabled && !embeddingSweepEnabled && !commitIndexingEnabled) {
@@ -164707,7 +164973,7 @@ function runTick(origin) {
164707
164973
  })();
164708
164974
  }
164709
164975
  async function sweepProject(reg, origin, db = openDatabase(), gitCommitEnabled = getProjectEmbeddingSnapshot(reg.projectIdentity)?.gitCommitEnabled === true) {
164710
- const dreamingEnabled = Boolean(reg.dreamerConfig?.enabled && reg.dreamerConfig.schedule?.trim());
164976
+ const dreamingEnabled = Boolean(reg.dreamerConfig && reg.dreamerConfig.disable !== true && reg.dreamerConfig.schedule?.trim());
164711
164977
  if (gitCommitEnabled && reg.gitCommitIndexing) {
164712
164978
  await sweepGitCommits({
164713
164979
  directory: reg.directory,
@@ -164835,12 +165101,12 @@ function handleUntrustedLoad(db, projectIdentity, directory, detailed) {
164835
165101
  }
164836
165102
 
164837
165103
  // src/config/index.ts
164838
- import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
165104
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
164839
165105
  import { homedir as homedir5 } from "node:os";
164840
- import { join as join13 } from "node:path";
165106
+ import { join as join14 } from "node:path";
164841
165107
 
164842
165108
  // ../plugin/src/config/variable.ts
164843
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "node:fs";
165109
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
164844
165110
  import { homedir as homedir4 } from "node:os";
164845
165111
  import { dirname as dirname5, isAbsolute, resolve as resolve3 } from "node:path";
164846
165112
  var ENV_PATTERN = /\{env:([^}]+)\}/g;
@@ -164898,13 +165164,13 @@ function substituteConfigVariables(input) {
164898
165164
  } else if (!isAbsolute(filePath)) {
164899
165165
  filePath = resolve3(configDir, filePath);
164900
165166
  }
164901
- if (!existsSync9(filePath)) {
165167
+ if (!existsSync10(filePath)) {
164902
165168
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
164903
165169
  continue;
164904
165170
  }
164905
165171
  let contents;
164906
165172
  try {
164907
- contents = readFileSync5(filePath, "utf-8").trim();
165173
+ contents = readFileSync6(filePath, "utf-8").trim();
164908
165174
  } catch (error51) {
164909
165175
  const message = error51 instanceof Error ? error51.message : String(error51);
164910
165176
  warnings.push(`Failed to read file for ${token} (${filePath}): ${message}; using empty string`);
@@ -164920,39 +165186,39 @@ function substituteConfigVariables(input) {
164920
165186
  var import_comment_json2 = __toESM(require_src2(), 1);
164921
165187
  var CONFIG_FILE_NAME = "magic-context";
164922
165188
  function getProjectConfigPaths(cwd) {
164923
- const basePath = join13(cwd, ".pi", CONFIG_FILE_NAME);
165189
+ const basePath = join14(cwd, ".pi", CONFIG_FILE_NAME);
164924
165190
  return [`${basePath}.jsonc`, `${basePath}.json`];
164925
165191
  }
164926
165192
  function getUserConfigPaths() {
164927
165193
  const home = process.env.HOME ?? homedir5();
164928
- const basePath = join13(home, ".pi", "agent", CONFIG_FILE_NAME);
165194
+ const basePath = join14(home, ".pi", "agent", CONFIG_FILE_NAME);
164929
165195
  return [`${basePath}.jsonc`, `${basePath}.json`];
164930
165196
  }
164931
165197
  function resolveFirstExisting(paths) {
164932
- return paths.find((path5) => existsSync10(path5));
165198
+ return paths.find((path6) => existsSync11(path6));
164933
165199
  }
164934
- function loadConfigFile(path5, scope) {
165200
+ function loadConfigFile(path6, scope) {
164935
165201
  try {
164936
- const rawText = readFileSync6(path5, "utf-8");
165202
+ const rawText = readFileSync7(path6, "utf-8");
164937
165203
  const substituted = substituteConfigVariables({
164938
165204
  text: rawText,
164939
- configPath: path5
165205
+ configPath: path6
164940
165206
  });
164941
165207
  return {
164942
- path: path5,
165208
+ path: path6,
164943
165209
  scope,
164944
165210
  config: import_comment_json2.parse(substituted.text),
164945
- warnings: substituted.warnings.map((warning) => `${path5}: ${warning}`),
165211
+ warnings: substituted.warnings.map((warning) => `${path6}: ${warning}`),
164946
165212
  loadOutcome: substituted.warnings.length > 0 ? "substitution-failure" : "ok"
164947
165213
  };
164948
165214
  } catch (error51) {
164949
165215
  const message = error51 instanceof Error ? error51.message : String(error51);
164950
165216
  return {
164951
- path: path5,
165217
+ path: path6,
164952
165218
  scope,
164953
165219
  config: {},
164954
165220
  warnings: [
164955
- `${path5}: failed to load config: ${message}; using defaults for this file.`
165221
+ `${path6}: failed to load config: ${message}; using defaults for this file.`
164956
165222
  ],
164957
165223
  loadOutcome: typeof error51.code === "string" ? "project-file-io-error" : "project-file-parse-error"
164958
165224
  };
@@ -164990,9 +165256,11 @@ function mergeRawConfigs(base, override) {
164990
165256
  return merged;
164991
165257
  }
164992
165258
  function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
164993
- const parsed = MagicContextConfigSchema.safeParse(rawConfig);
165259
+ const preMigrationWarnings = [];
165260
+ const migrated = migrateLegacyAgentEnabledInMemory(rawConfig, preMigrationWarnings);
165261
+ const parsed = MagicContextConfigSchema.safeParse(migrated);
164994
165262
  if (parsed.success) {
164995
- return { config: parsed.data, warnings: [] };
165263
+ return { config: parsed.data, warnings: preMigrationWarnings };
164996
165264
  }
164997
165265
  const defaults = MagicContextConfigSchema.parse({});
164998
165266
  const errorPaths = new Set;
@@ -165002,8 +165270,8 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
165002
165270
  errorPaths.add(String(topKey));
165003
165271
  }
165004
165272
  }
165005
- const patched = { ...rawConfig };
165006
- const warnings = [];
165273
+ const patched = { ...migrated };
165274
+ const warnings = [...preMigrationWarnings];
165007
165275
  for (const key of errorPaths) {
165008
165276
  recoveredTopLevelKeys.push(key);
165009
165277
  const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
@@ -165077,9 +165345,9 @@ function bindSubstitutionFailures(loaded) {
165077
165345
  }
165078
165346
  const emptyPaths = collectEmptyStringPaths(loaded.config);
165079
165347
  return loaded.warnings.map((message) => {
165080
- const matchedPath = emptyPaths.find((path5) => {
165081
- const tail = path5.split(".").at(-1) ?? path5;
165082
- return message.includes(path5) || message.toLowerCase().includes(tail.toLowerCase());
165348
+ const matchedPath = emptyPaths.find((path6) => {
165349
+ const tail = path6.split(".").at(-1) ?? path6;
165350
+ return message.includes(path6) || message.toLowerCase().includes(tail.toLowerCase());
165083
165351
  });
165084
165352
  return {
165085
165353
  keyPath: matchedPath ?? "<unknown>",
@@ -165175,7 +165443,7 @@ var sessionCounter = 0;
165175
165443
  var piSubagentRunnerFactory = () => new PiSubagentRunner;
165176
165444
  var startDreamScheduleTimerFn = startDreamScheduleTimer;
165177
165445
  function registerPiDreamerProject(opts) {
165178
- if (!opts.config.enabled) {
165446
+ if (opts.config.disable === true) {
165179
165447
  return;
165180
165448
  }
165181
165449
  const existing = registeredProjects2.get(opts.projectIdentity);
@@ -165410,7 +165678,7 @@ function registerCtxDreamCommand(pi, deps) {
165410
165678
  if (deps.dreamerEnabled === false) {
165411
165679
  sendCtxStatusMessage(pi, {
165412
165680
  title: "/ctx-dream",
165413
- text: "## /ctx-dream\n\nDreamer is not configured for this project (`dreamer.enabled=false`).",
165681
+ text: "## /ctx-dream\n\nDreamer is disabled for this project (`dreamer.disable=true`).",
165414
165682
  level: "info"
165415
165683
  }, {
165416
165684
  projectDir: project.projectDir,
@@ -165456,6 +165724,8 @@ function registerCtxDreamCommand(pi, deps) {
165456
165724
  let summary;
165457
165725
  if (!result) {
165458
165726
  summary = "Dream queued, but another worker is already processing the queue.";
165727
+ } else if (result.tasks.length === 0) {
165728
+ summary = "Dreamer is configured, but no dream tasks are enabled for this project.";
165459
165729
  } else {
165460
165730
  const taskLines = result.tasks.map((task) => {
165461
165731
  const status = task.error ? `error: ${task.error}` : "ok";
@@ -167787,7 +168057,7 @@ function stripNestedSystemReminders(text) {
167787
168057
  return result;
167788
168058
  }
167789
168059
  function extractUserPromptText(message) {
167790
- return stripNestedSystemReminders(collectUserPromptParts(message)).replace(/§\d+§\s*/g, "").replace(/<!--\s*\+[\d\s.hmdw]+\s*-->/g, "").replace(/<!--\s*OMO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<!--\s*ALFONSO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<ctx-search-hint>[\s\S]*?<\/ctx-search-hint>/g, "").replace(/<ctx-search-auto>[\s\S]*?<\/ctx-search-auto>/g, "").replace(/<instruction[^>]*>[\s\S]*?<\/instruction>/g, "").replace(/<sidekick-augmentation>[\s\S]*?<\/sidekick-augmentation>/g, "").replace(/[ \t]+\n/g, `
168060
+ return stripNestedSystemReminders(collectUserPromptParts(message)).replace(/<!--[\s\S]*?-->/g, "").replace(/<ctx-search-hint>[\s\S]*?<\/ctx-search-hint>/g, "").replace(/<ctx-search-auto>[\s\S]*?<\/ctx-search-auto>/g, "").replace(/<instruction[^>]*>[\s\S]*?<\/instruction>/g, "").replace(/<sidekick-augmentation>[\s\S]*?<\/sidekick-augmentation>/g, "").replace(/<\/?[a-zA-Z][^<>]*>/g, "").replace(/§\d+§\s*/g, "").replace(/[ \t]+\n/g, `
167791
168061
  `).replace(/\n{3,}/g, `
167792
168062
 
167793
168063
  `).trim();
@@ -167958,18 +168228,28 @@ function applyDeferredPiCompactionMarker(deps, sessionId, pending) {
167958
168228
  return { kind: "stale-skip", reason: "target-superseded" };
167959
168229
  }
167960
168230
  const branchEntries = deps.readBranchEntries();
167961
- if (!branchEntries.some((entry) => getEntryId(entry) === pending.firstKeptEntryId)) {
168231
+ const pendingFirstKeptIndex = findEntryIndex(branchEntries, pending.firstKeptEntryId);
168232
+ if (pendingFirstKeptIndex < 0) {
167962
168233
  return { kind: "stale-skip", reason: "entry-removed" };
167963
168234
  }
167964
168235
  const latestFirstKept = findLatestCompactionFirstKept(branchEntries);
167965
- if (latestFirstKept === pending.firstKeptEntryId) {
167966
- return { kind: "already-current" };
168236
+ if (latestFirstKept !== null) {
168237
+ const latestFirstKeptIndex = findEntryIndex(branchEntries, latestFirstKept);
168238
+ if (latestFirstKeptIndex >= pendingFirstKeptIndex) {
168239
+ return { kind: "already-current" };
168240
+ }
167967
168241
  }
167968
- deps.appendCompaction(pending.summary, pending.firstKeptEntryId, pending.tokensBefore, {
168242
+ const compactionId = deps.appendCompaction(pending.summary, pending.firstKeptEntryId, pending.tokensBefore, {
167969
168243
  source: "magic-context",
167970
168244
  lastCompactedOrdinal: pending.ordinal
167971
168245
  }, true);
167972
- sessionLog(sessionId, `Pi compaction-marker drain: applied firstKept=${pending.firstKeptEntryId} tokensBefore=${pending.tokensBefore}`);
168246
+ if (typeof compactionId !== "string" || compactionId.length === 0) {
168247
+ return {
168248
+ kind: "retryable-failure",
168249
+ error: new Error("Pi appendCompaction returned no compaction id")
168250
+ };
168251
+ }
168252
+ sessionLog(sessionId, `Pi compaction-marker drain: applied compactionId=${compactionId} firstKept=${pending.firstKeptEntryId} endMessageId=${pending.endMessageId} ordinal=${pending.ordinal} tokensBefore=${pending.tokensBefore}`);
167973
168253
  return { kind: "applied", firstKeptEntryId: pending.firstKeptEntryId };
167974
168254
  } catch (err) {
167975
168255
  const error51 = err instanceof Error ? err : new Error(String(err));
@@ -167995,6 +168275,9 @@ function getEntryId(entry) {
167995
168275
  const id = entry.id;
167996
168276
  return typeof id === "string" ? id : null;
167997
168277
  }
168278
+ function findEntryIndex(branchEntries, entryId) {
168279
+ return branchEntries.findIndex((entry) => getEntryId(entry) === entryId);
168280
+ }
167998
168281
 
167999
168282
  // src/detect-recent-commit.ts
168000
168283
  var COMMIT_LOOKBACK = 5;
@@ -168347,36 +168630,70 @@ function trimPiMessagesToBoundary(piMessages, entryIds, cutoffMessageId) {
168347
168630
  }
168348
168631
  if (cutoffIndex < 0)
168349
168632
  return 0;
168350
- const removedCallIds = new Set;
168351
- for (let i = 0;i <= cutoffIndex; i++) {
168352
- const msg = piMessages[i];
168353
- if (!msg || msg.role !== "assistant")
168354
- continue;
168355
- const assistantMsg = msg;
168356
- if (!Array.isArray(assistantMsg.content))
168357
- continue;
168358
- for (const part of assistantMsg.content) {
168359
- if (part && typeof part === "object" && part.type === "toolCall" && typeof part.id === "string") {
168360
- removedCallIds.add(part.id);
168633
+ const remove = new Set;
168634
+ for (let i = 0;i <= cutoffIndex; i++)
168635
+ remove.add(i);
168636
+ let changed = true;
168637
+ while (changed) {
168638
+ changed = false;
168639
+ const removedCallIds = new Set;
168640
+ const removedResultIds = new Set;
168641
+ for (const index of remove) {
168642
+ const msg = piMessages[index];
168643
+ if (!msg)
168644
+ continue;
168645
+ if (msg.role === "assistant") {
168646
+ for (const callId of getPiToolCallIds(msg))
168647
+ removedCallIds.add(callId);
168648
+ } else if (msg.role === "toolResult") {
168649
+ const callId = getPiToolResultCallId(msg);
168650
+ if (callId)
168651
+ removedResultIds.add(callId);
168361
168652
  }
168362
168653
  }
168363
- }
168364
- if (removedCallIds.size > 0) {
168365
- let i = cutoffIndex + 1;
168366
- while (i < piMessages.length) {
168654
+ for (let i = 0;i < piMessages.length; i++) {
168655
+ if (remove.has(i))
168656
+ continue;
168367
168657
  const msg = piMessages[i];
168368
- if (msg && msg.role === "toolResult" && typeof msg.toolCallId === "string" && removedCallIds.has(msg.toolCallId)) {
168369
- cutoffIndex = i;
168370
- i++;
168371
- } else {
168372
- break;
168658
+ if (!msg)
168659
+ continue;
168660
+ if (msg.role === "toolResult") {
168661
+ const callId = getPiToolResultCallId(msg);
168662
+ if (callId && removedCallIds.has(callId)) {
168663
+ remove.add(i);
168664
+ changed = true;
168665
+ }
168666
+ continue;
168667
+ }
168668
+ if (msg.role === "assistant") {
168669
+ const callIds = getPiToolCallIds(msg);
168670
+ if (callIds.some((callId) => removedResultIds.has(callId))) {
168671
+ remove.add(i);
168672
+ changed = true;
168673
+ }
168373
168674
  }
168374
168675
  }
168375
168676
  }
168376
- const removed = cutoffIndex + 1;
168377
- piMessages.splice(0, removed);
168677
+ const kept = piMessages.filter((_, index) => !remove.has(index));
168678
+ const removed = piMessages.length - kept.length;
168679
+ piMessages.splice(0, piMessages.length, ...kept);
168378
168680
  return removed;
168379
168681
  }
168682
+ function getPiToolCallIds(message) {
168683
+ if (!Array.isArray(message.content))
168684
+ return [];
168685
+ const ids = [];
168686
+ for (const part of message.content) {
168687
+ if (part && typeof part === "object" && part.type === "toolCall" && typeof part.id === "string") {
168688
+ ids.push(part.id);
168689
+ }
168690
+ }
168691
+ return ids;
168692
+ }
168693
+ function getPiToolResultCallId(message) {
168694
+ const callId = message.toolCallId;
168695
+ return typeof callId === "string" && callId.length > 0 ? callId : null;
168696
+ }
168380
168697
  function injectHistoryBlockIntoFirstUserMessage(piMessages, historyBlock) {
168381
168698
  for (let i = 0;i < piMessages.length; i++) {
168382
168699
  const msg = piMessages[i];
@@ -168468,6 +168785,7 @@ ${prepared.block}
168468
168785
  // src/note-visibility-pi.ts
168469
168786
  var NOTE_TOOL_NAME = "ctx_note";
168470
168787
  var READ_ACTION = "read";
168788
+ var WHOLE_MESSAGE_PLACEHOLDER_TEXT = "[dropped]";
168471
168789
  function hasVisibleNoteReadCallPi(messages) {
168472
168790
  for (let i = messages.length - 1;i >= 0; i--) {
168473
168791
  const raw = messages[i];
@@ -168479,6 +168797,8 @@ function hasVisibleNoteReadCallPi(messages) {
168479
168797
  if (!Array.isArray(msg.content))
168480
168798
  continue;
168481
168799
  for (const part of msg.content) {
168800
+ if (isSentinelPart(part))
168801
+ continue;
168482
168802
  if (!part || typeof part !== "object")
168483
168803
  continue;
168484
168804
  const p = part;
@@ -168496,6 +168816,12 @@ function hasVisibleNoteReadCallPi(messages) {
168496
168816
  }
168497
168817
  return false;
168498
168818
  }
168819
+ function isSentinelPart(part) {
168820
+ if (!part || typeof part !== "object")
168821
+ return false;
168822
+ const p = part;
168823
+ return p.type === "text" && typeof p.text === "string" && (p.text === "" || p.text === WHOLE_MESSAGE_PLACEHOLDER_TEXT);
168824
+ }
168499
168825
 
168500
168826
  // src/nudge-injector.ts
168501
168827
  var INSTRUCTION_NAME_PATTERN = /<instruction\s+name="([^"]+)">/;
@@ -169060,6 +169386,8 @@ async function runPiCompressionPassIfNeeded(deps) {
169060
169386
  sessionLog(sessionId, `compressor: history block ~${totalTokens} tokens within budget ${historyBudgetTokens}, skipping`);
169061
169387
  return false;
169062
169388
  }
169389
+ const overage = totalTokens - historyBudgetTokens;
169390
+ sessionLog(sessionId, `compressor: history block ~${totalTokens} tokens exceeds budget ${historyBudgetTokens} by ~${overage} tokens`);
169063
169391
  const lastEndMessage = compartments[compartments.length - 1]?.endMessage ?? 0;
169064
169392
  const floor = Math.max(1, Math.ceil(lastEndMessage / minCompartmentRatio));
169065
169393
  const floorHeadroom = compartments.length - floor;
@@ -169068,6 +169396,17 @@ async function runPiCompressionPassIfNeeded(deps) {
169068
169396
  return false;
169069
169397
  }
169070
169398
  const scored = scoreCompartments(db, sessionId, compartments);
169399
+ const depthHistogram = new Map;
169400
+ for (const scoredCompartment of scored) {
169401
+ const bucket = Math.round(scoredCompartment.averageDepth);
169402
+ depthHistogram.set(bucket, (depthHistogram.get(bucket) ?? 0) + 1);
169403
+ }
169404
+ const histText = [...depthHistogram.entries()].sort((a, b) => a[0] - b[0]).map(([depth, count]) => `d${depth}=${count}`).join(" ");
169405
+ const histKey = `${scored.length}|${histText}`;
169406
+ if (lastDepthHistogramBySession.get(sessionId) !== histKey) {
169407
+ lastDepthHistogramBySession.set(sessionId, histKey);
169408
+ sessionLog(sessionId, `compressor: depth histogram (${scored.length} total) ${histText}`);
169409
+ }
169071
169410
  const band = selectPiCompressionBand(scored, {
169072
169411
  maxPickable: maxCompartmentsPerPass,
169073
169412
  maxMergeDepth,
@@ -169086,7 +169425,7 @@ async function runPiCompressionPassIfNeeded(deps) {
169086
169425
  const outputDepth = Math.min(5, Math.max(1, Math.round(avgDepth) + 1));
169087
169426
  const mergeRatio = COMPRESSOR_MERGE_RATIO_BY_DEPTH[outputDepth] ?? 2;
169088
169427
  const outputCount = mergeRatio > 0 ? Math.max(1, Math.ceil(band.length / mergeRatio)) : 1;
169089
- sessionLog(sessionId, `compressor: picked ${band.length} compartments (${selectedCompartments[0]?.startMessage}-${selectedCompartments[selectedCompartments.length - 1]?.endMessage}, ~${selectedTokens} tokens), avg_depth=${avgDepth.toFixed(1)} → output_depth=${outputDepth}`);
169428
+ sessionLog(sessionId, `compressor: scored ${compartments.length}, picked ${band.length} contiguous (${selectedCompartments[0]?.startMessage}-${selectedCompartments[selectedCompartments.length - 1]?.endMessage}, ~${selectedTokens} tokens), avg_depth=${avgDepth.toFixed(1)} → output_depth=${outputDepth} (ratio=${mergeRatio}, target=${outputCount} compartments)`);
169090
169429
  if (outputDepth === 5) {
169091
169430
  const compressed = selectedCompartments.map((c) => ({
169092
169431
  startMessage: c.startMessage,
@@ -169106,7 +169445,8 @@ async function runPiCompressionPassIfNeeded(deps) {
169106
169445
  originalStart: selectedCompartments[0]?.startMessage ?? 0,
169107
169446
  originalEnd: selectedCompartments[selectedCompartments.length - 1]?.endMessage ?? 0,
169108
169447
  facts,
169109
- logLabel: `depth-5 title-only collapse (${selectedCompartments.length} → ${compressed.length})`
169448
+ logLabel: `depth-5 title-only collapse (${selectedCompartments.length} → ${compressed.length})`,
169449
+ holderId: deps.compartmentLeaseHolderId
169110
169450
  });
169111
169451
  if (ok2)
169112
169452
  onPublished?.();
@@ -169137,7 +169477,8 @@ async function runPiCompressionPassIfNeeded(deps) {
169137
169477
  originalStart: selectedCompartments[0]?.startMessage ?? 0,
169138
169478
  originalEnd: selectedCompartments[selectedCompartments.length - 1]?.endMessage ?? 0,
169139
169479
  facts,
169140
- logLabel: `depth-${outputDepth} (${selectedCompartments.length} → ${finalCompressed.length})`
169480
+ logLabel: `depth-${outputDepth} (${selectedCompartments.length} → ${finalCompressed.length})`,
169481
+ holderId: deps.compartmentLeaseHolderId
169141
169482
  });
169142
169483
  if (ok)
169143
169484
  onPublished?.();
@@ -169246,12 +169587,20 @@ function finalizeCompression(args) {
169246
169587
  sequence: leading.length + compressed.length + i
169247
169588
  }))
169248
169589
  ];
169249
- replaceAllCompartmentState(db, sessionId, allCompartments, args.facts);
169250
- incrementCompressionDepth(db, sessionId, originalStart, originalEnd);
169590
+ const published = args.holderId ? replaceAllCompartmentStateAndBumpDepth(db, args.holderId, sessionId, allCompartments, args.facts, originalStart, originalEnd) : (() => {
169591
+ replaceAllCompartmentState(db, sessionId, allCompartments, args.facts);
169592
+ incrementCompressionDepth(db, sessionId, originalStart, originalEnd);
169593
+ return true;
169594
+ })();
169595
+ if (!published) {
169596
+ sessionLog(sessionId, "compressor: publish skipped because compartment lease is no longer held");
169597
+ return false;
169598
+ }
169251
169599
  sessionLog(sessionId, `compressor: completed ${args.logLabel}`);
169252
169600
  return true;
169253
169601
  }
169254
169602
  var lastCompressorRunBySession = new Map;
169603
+ var lastDepthHistogramBySession = new Map;
169255
169604
  function isPiCompressorOnCooldown(sessionId, cooldownMs) {
169256
169605
  const last = lastCompressorRunBySession.get(sessionId);
169257
169606
  return last !== undefined && Date.now() - last < cooldownMs;
@@ -169261,6 +169610,7 @@ function markPiCompressorRun(sessionId) {
169261
169610
  }
169262
169611
  function clearPiCompressorState(sessionId) {
169263
169612
  lastCompressorRunBySession.delete(sessionId);
169613
+ lastDepthHistogramBySession.delete(sessionId);
169264
169614
  }
169265
169615
 
169266
169616
  // ../plugin/src/hooks/magic-context/compartment-runner-drop-queue.ts
@@ -169491,27 +169841,27 @@ function getReducedRecompTokenBudget(currentBudget) {
169491
169841
 
169492
169842
  // ../plugin/src/hooks/magic-context/historian-state-file.ts
169493
169843
  init_data_path();
169494
- import { mkdirSync as mkdirSync5, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
169495
- import { join as join14 } from "node:path";
169844
+ import { mkdirSync as mkdirSync6, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
169845
+ import { join as join15 } from "node:path";
169496
169846
  var HISTORIAN_STATE_INLINE_THRESHOLD = 30000;
169497
169847
  function maybeWriteHistorianStateFile(sessionId, existingState, directory) {
169498
169848
  if (existingState.length <= HISTORIAN_STATE_INLINE_THRESHOLD)
169499
169849
  return;
169500
169850
  try {
169501
169851
  const dir = getProjectMagicContextHistorianDir(directory);
169502
- mkdirSync5(dir, { recursive: true });
169503
- const path5 = join14(dir, `state-${sessionId}-${Date.now()}.xml`);
169504
- writeFileSync2(path5, existingState, "utf8");
169505
- return path5;
169852
+ mkdirSync6(dir, { recursive: true });
169853
+ const path6 = join15(dir, `state-${sessionId}-${Date.now()}.xml`);
169854
+ writeFileSync3(path6, existingState, "utf8");
169855
+ return path6;
169506
169856
  } catch {
169507
169857
  return;
169508
169858
  }
169509
169859
  }
169510
- function cleanupHistorianStateFile(path5) {
169511
- if (!path5)
169860
+ function cleanupHistorianStateFile(path6) {
169861
+ if (!path6)
169512
169862
  return;
169513
169863
  try {
169514
- unlinkSync(path5);
169864
+ unlinkSync(path6);
169515
169865
  } catch {}
169516
169866
  }
169517
169867
 
@@ -169628,7 +169978,7 @@ function convertEntriesToRawMessages(entries) {
169628
169978
  if (pendingToolParts.length > 0) {
169629
169979
  result.push({
169630
169980
  ordinal: nextOrdinal++,
169631
- id: pendingFirstRealId,
169981
+ id: `synth-user-${pendingFirstRealId}`,
169632
169982
  role: "user",
169633
169983
  parts: pendingToolParts
169634
169984
  });
@@ -169653,7 +170003,7 @@ function convertEntriesToRawMessages(entries) {
169653
170003
  if (pendingToolParts.length > 0) {
169654
170004
  result.push({
169655
170005
  ordinal: nextOrdinal,
169656
- id: pendingFirstRealId,
170006
+ id: `synth-user-${pendingFirstRealId}`,
169657
170007
  role: "user",
169658
170008
  parts: pendingToolParts
169659
170009
  });
@@ -169776,6 +170126,7 @@ async function runPiHistorian(deps) {
169776
170126
  memoryEnabled,
169777
170127
  autoPromote,
169778
170128
  onPublished,
170129
+ compartmentLeaseHolderId,
169779
170130
  readBranchEntries,
169780
170131
  notifyIssue
169781
170132
  } = deps;
@@ -169802,23 +170153,27 @@ async function runPiHistorian(deps) {
169802
170153
  const existingValidationError = validateStoredCompartments(priorCompartments);
169803
170154
  if (existingValidationError) {
169804
170155
  sessionLog(sessionId, `historian failure: source=existing-validation reason="${existingValidationError}"`);
170156
+ incrementHistorianFailure(db, sessionId, existingValidationError);
169805
170157
  await notify(`Historian skipped: existing stored compartments are invalid: ${existingValidationError}`);
169806
170158
  return;
169807
170159
  }
169808
170160
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
169809
170161
  const protectedTailStart = getProtectedTailStartOrdinal(sessionId);
169810
170162
  if (protectedTailStart <= offset) {
169811
- sessionLog(sessionId, `historian skip: protected tail covers all eligible history (offset=${offset}, protectedStart=${protectedTailStart})`);
170163
+ sessionLog(sessionId, `historian no-op: protectedTailStart=${protectedTailStart} <= offset=${offset} — nothing to compact`);
170164
+ clearEmergencyRecovery(db, sessionId);
169812
170165
  return;
169813
170166
  }
169814
170167
  const chunk = readSessionChunk(sessionId, historianChunkTokens, offset, protectedTailStart);
169815
170168
  if (!chunk.text || chunk.messageCount === 0) {
169816
- sessionLog(sessionId, `historian skip: empty chunk (offset=${offset}, protectedStart=${protectedTailStart})`);
170169
+ sessionLog(sessionId, `historian no-op: chunk empty after filtering (messageCount=${chunk.messageCount}, textLen=${chunk.text?.length ?? 0}) range=${offset}-${protectedTailStart - 1}`);
170170
+ clearEmergencyRecovery(db, sessionId);
169817
170171
  return;
169818
170172
  }
169819
170173
  const chunkCoverageError = validateChunkCoverage(chunk);
169820
170174
  if (chunkCoverageError) {
169821
170175
  sessionLog(sessionId, `historian failure: source=chunk-coverage reason="${chunkCoverageError}" chunkRange=${chunk.startIndex}-${chunk.endIndex}`);
170176
+ incrementHistorianFailure(db, sessionId, chunkCoverageError);
169822
170177
  await notify(`Historian skipped: raw chunk could not be safely chunked: ${chunkCoverageError}`);
169823
170178
  return;
169824
170179
  }
@@ -169923,7 +170278,6 @@ ${chunk.text}`, { stateFilePath });
169923
170278
  systemPrompt: HISTORIAN_EDITOR_SYSTEM_PROMPT,
169924
170279
  userMessage: buildHistorianEditorPrompt(draftAssistantText),
169925
170280
  model: historianModel,
169926
- fallbackModels,
169927
170281
  timeoutMs: historianTimeoutMs,
169928
170282
  cwd: directory,
169929
170283
  thinkingLevel,
@@ -169961,10 +170315,30 @@ ${chunk.text}`, { stateFilePath });
169961
170315
  sessionLog(sessionId, `historian: native compaction queue lookup failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
169962
170316
  }
169963
170317
  }
169964
- db.transaction(() => {
170318
+ if (!compartmentLeaseHolderId) {
170319
+ sessionLog(sessionId, "historian publish skipped: missing compartment lease holder");
170320
+ return;
170321
+ }
170322
+ let published = false;
170323
+ db.exec("BEGIN IMMEDIATE");
170324
+ try {
170325
+ if (!isCompartmentLeaseHeld(db, sessionId, compartmentLeaseHolderId)) {
170326
+ db.exec("ROLLBACK");
170327
+ sessionLog(sessionId, "historian publish skipped: compartment lease no longer held");
170328
+ return;
170329
+ }
169965
170330
  appendCompartments(db, sessionId, newCompartments);
169966
170331
  replaceSessionFacts(db, sessionId, validatedPass.facts ?? []);
169967
170332
  clearHistorianFailureState(db, sessionId);
170333
+ clearEmergencyRecovery(db, sessionId);
170334
+ if (validatedPass.userObservations?.length) {
170335
+ insertUserMemoryCandidates(db, validatedPass.userObservations.map((obs) => ({
170336
+ content: obs,
170337
+ sessionId,
170338
+ sourceCompartmentStart: newCompartments[0]?.startMessage,
170339
+ sourceCompartmentEnd: lastNewEnd
170340
+ })));
170341
+ }
169968
170342
  if (firstKeptEntryId && lastNewEndMessageId) {
169969
170343
  setPendingPiCompactionMarkerState(db, sessionId, {
169970
170344
  firstKeptEntryId,
@@ -169975,13 +170349,24 @@ ${chunk.text}`, { stateFilePath });
169975
170349
  publishedAt: Date.now()
169976
170350
  });
169977
170351
  }
169978
- })();
170352
+ db.exec("COMMIT");
170353
+ published = true;
170354
+ } finally {
170355
+ if (!published) {
170356
+ try {
170357
+ db.exec("ROLLBACK");
170358
+ } catch {}
170359
+ }
170360
+ }
169979
170361
  onNoteTrigger(db, sessionId, "historian_complete");
169980
170362
  onPublished?.();
169981
170363
  if (memoryEnabled !== false && autoPromote !== false) {
169982
170364
  promoteSessionFactsToMemory(db, sessionId, projectPath, validatedPass.facts ?? []);
169983
170365
  }
169984
170366
  queueDropsForCompartmentalizedMessages(db, sessionId, lastNewEnd);
170367
+ if (validatedPass.userObservations?.length) {
170368
+ sessionLog(sessionId, `stored ${validatedPass.userObservations.length} user memory candidate(s)`);
170369
+ }
169985
170370
  sessionLog(sessionId, `historian: published ${newCompartments.length} compartment(s), ${validatedPass.facts?.length ?? 0} fact(s) covering messages ${chunk.startIndex}-${lastNewEnd}`);
169986
170371
  completedSuccessfully = true;
169987
170372
  });
@@ -170453,8 +170838,8 @@ function stripPiDroppedPlaceholderMessages(args) {
170453
170838
 
170454
170839
  // src/system-prompt.ts
170455
170840
  import { createHash as createHash8 } from "node:crypto";
170456
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
170457
- import { join as join16 } from "node:path";
170841
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
170842
+ import { join as join17 } from "node:path";
170458
170843
 
170459
170844
  // ../plugin/src/agents/magic-context-prompt.ts
170460
170845
  function getToolHistoryGuidance(dropToolStructure) {
@@ -170544,16 +170929,16 @@ Prefer many small targeted operations over one large blanket operation. Compress
170544
170929
  }
170545
170930
 
170546
170931
  // ../plugin/src/hooks/magic-context/key-files-block.ts
170547
- import { readFileSync as readFileSync7, realpathSync as realpathSync3 } from "node:fs";
170548
- import { join as join15, sep as sep2 } from "node:path";
170932
+ import { readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
170933
+ import { join as join16, sep as sep2 } from "node:path";
170549
170934
  init_logger();
170550
170935
  var cachedKeyFilesBySession = new Map;
170551
170936
  var staleUpdates = new Map;
170552
170937
  function staleKey(update) {
170553
170938
  return `${update.projectPath}\x00${update.path}\x00${update.generatedAtWitness}\x00${update.staleReason}`;
170554
170939
  }
170555
- function queueStaleUpdate(projectPath, path5, generatedAtWitness, staleReason) {
170556
- const update = { projectPath, path: path5, generatedAtWitness, staleReason };
170940
+ function queueStaleUpdate(projectPath, path6, generatedAtWitness, staleReason) {
170941
+ const update = { projectPath, path: path6, generatedAtWitness, staleReason };
170557
170942
  staleUpdates.set(staleKey(update), update);
170558
170943
  }
170559
170944
  function flushStaleUpdates(db) {
@@ -170598,13 +170983,13 @@ function buildKeyFilesBlock(db, projectPath, config2 = { enabled: true, tokenBud
170598
170983
  let nextStale = null;
170599
170984
  let observed = false;
170600
170985
  try {
170601
- const absPath = join15(projectPath, row.path);
170986
+ const absPath = join16(projectPath, row.path);
170602
170987
  const real = realpathSync3(absPath);
170603
170988
  if (!isUnderProject(projectPath, real)) {
170604
170989
  nextStale = "missing";
170605
170990
  observed = true;
170606
170991
  } else {
170607
- const diskHash = sha256(readFileSync7(real));
170992
+ const diskHash = sha256(readFileSync8(real));
170608
170993
  if (diskHash !== row.contentHash)
170609
170994
  nextStale = "content_drift";
170610
170995
  observed = true;
@@ -170681,10 +171066,10 @@ var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
170681
171066
  function readProjectDocs(directory) {
170682
171067
  const sections = [];
170683
171068
  for (const filename of DOC_FILES) {
170684
- const filePath = join16(directory, filename);
171069
+ const filePath = join17(directory, filename);
170685
171070
  try {
170686
- if (existsSync11(filePath)) {
170687
- const content = readFileSync8(filePath, "utf-8").trim();
171071
+ if (existsSync12(filePath)) {
171072
+ const content = readFileSync9(filePath, "utf-8").trim();
170688
171073
  if (content.length > 0) {
170689
171074
  sections.push(`<${filename}>
170690
171075
  ${escapeXmlContent(content)}
@@ -171438,6 +171823,16 @@ var recentReduceBySession = new Map;
171438
171823
  var liveModelBySession = new Map;
171439
171824
  var toolUsageSinceUserTurn = new Map;
171440
171825
  var latestUserMessageBySession = new Map;
171826
+ var RECENT_REDUCE_TTL_MS = 5 * 60 * 1000;
171827
+ function hasRecentPiCtxReduceExecution(sessionId) {
171828
+ const recordedAt = recentReduceBySession.get(sessionId);
171829
+ if (recordedAt === undefined)
171830
+ return false;
171831
+ if (Date.now() - recordedAt <= RECENT_REDUCE_TTL_MS)
171832
+ return true;
171833
+ recentReduceBySession.delete(sessionId);
171834
+ return false;
171835
+ }
171441
171836
  function logTransformTiming(sessionId, stage, start, extra) {
171442
171837
  const elapsed = (performance.now() - start).toFixed(1);
171443
171838
  const suffix = extra ? ` ${extra}` : "";
@@ -171527,7 +171922,7 @@ function recordPiToolExecution(sessionId) {
171527
171922
  function onPiNewUserMessage(args) {
171528
171923
  const sessionMeta = getOrCreateSessionMeta(args.db, args.sessionId);
171529
171924
  const turnUsage = toolUsageSinceUserTurn.get(args.sessionId);
171530
- const agentAlreadyReduced = recentReduceBySession.has(args.sessionId);
171925
+ const agentAlreadyReduced = hasRecentPiCtxReduceExecution(args.sessionId);
171531
171926
  if (!sessionMeta.isSubagent && !agentAlreadyReduced && getPersistedStickyTurnReminder(args.db, args.sessionId) === null && turnUsage !== undefined && turnUsage >= TOOL_HEAVY_TURN_REMINDER_THRESHOLD) {
171532
171927
  setPersistedStickyTurnReminder(args.db, args.sessionId, TOOL_HEAVY_TURN_REMINDER_TEXT);
171533
171928
  }
@@ -171648,7 +172043,8 @@ function collectMessageEntryIdsByRef(ctx, messages, sessionId, preloadedBranchEn
171648
172043
  continue;
171649
172044
  }
171650
172045
  const fingerprint = piMessageEntryFingerprint(msg);
171651
- const fingerprintId = fingerprint ? entryIdsByFingerprint.get(fingerprint)?.find((candidate) => !consumedFingerprintIds.has(candidate)) : undefined;
172046
+ const fingerprintBucket = fingerprint ? entryIdsByFingerprint.get(fingerprint) : undefined;
172047
+ const fingerprintId = fingerprintBucket?.length === 1 && !consumedFingerprintIds.has(fingerprintBucket[0]) ? fingerprintBucket[0] : undefined;
171652
172048
  if (typeof fingerprintId === "string") {
171653
172049
  consumedFingerprintIds.add(fingerprintId);
171654
172050
  result[i] = fingerprintId;
@@ -171784,8 +172180,10 @@ function registerPiContextHandler(pi, options) {
171784
172180
  });
171785
172181
  clearHistorianFailureState(options.db, sessionId);
171786
172182
  clearPersistedReasoningWatermark(options.db, sessionId);
171787
- clearDetectedContextLimit(options.db, sessionId);
171788
- clearEmergencyRecovery(options.db, sessionId);
172183
+ if (modelChanged) {
172184
+ clearDetectedContextLimit(options.db, sessionId);
172185
+ clearEmergencyRecovery(options.db, sessionId);
172186
+ }
171789
172187
  sessionMetaForUsage.lastContextPercentage = 0;
171790
172188
  sessionMetaForUsage.lastInputTokens = 0;
171791
172189
  sessionMetaForUsage.observedSafeInputTokens = 0;
@@ -171855,6 +172253,10 @@ function registerPiContextHandler(pi, options) {
171855
172253
  setDeferredExecutePendingIfAbsent(options.db, sessionId, flagPayload);
171856
172254
  }
171857
172255
  schedulerDecision = midTurnAdjustedSchedulerDecision;
172256
+ const deferredExecutePending = peekDeferredExecutePending(options.db, sessionId);
172257
+ if (deferredExecutePending !== null && schedulerDecision === "defer" && !midTurn) {
172258
+ schedulerDecision = "execute";
172259
+ }
171858
172260
  sessionLog(sessionId, `[boundary-exec] base=${schedulerDecisionEarly} bypass=${bypassReason} midTurn=${midTurn} effective=${midTurnAdjustedSchedulerDecision} sideEffect=${sideEffect}`);
171859
172261
  const forceMaterialization = usagePercentage >= FORCE_MATERIALIZATION_PERCENTAGE;
171860
172262
  const isEmergency = usagePercentage >= EMERGENCY_BLOCK_PERCENTAGE;
@@ -171933,7 +172335,7 @@ function registerPiContextHandler(pi, options) {
171933
172335
  db: options.db,
171934
172336
  messages: outputMessages,
171935
172337
  entryIds: entryIds ?? null,
171936
- hasRecentReduceCall: recentReduceBySession.has(sessionId),
172338
+ hasRecentReduceCall: hasRecentPiCtxReduceExecution(sessionId),
171937
172339
  isCacheBustingPass: cacheBustingPass
171938
172340
  });
171939
172341
  } catch (err) {
@@ -171960,8 +172362,7 @@ function registerPiContextHandler(pi, options) {
171960
172362
  db: options.db,
171961
172363
  messages: outputMessages,
171962
172364
  projectIdentity,
171963
- entryIds: strictEntryIds,
171964
- isCacheBusting
172365
+ entryIds: strictEntryIds
171965
172366
  });
171966
172367
  } catch (err) {
171967
172368
  sessionLog(sessionId, `note nudges failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -172084,6 +172485,13 @@ function resolveHistoryBudgetTokensForPi(args) {
172084
172485
  contextLimit: derivedLimit
172085
172486
  }) / 100) * historyBudgetPercentage);
172086
172487
  }
172488
+ function startPiCompartmentLeaseRenewal(db, sessionId, holderId) {
172489
+ return setInterval(() => {
172490
+ if (!renewCompartmentLease(db, sessionId, holderId)) {
172491
+ sessionLog(sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
172492
+ }
172493
+ }, COMPARTMENT_LEASE_RENEWAL_MS);
172494
+ }
172087
172495
  function maybeFireCompressor(args) {
172088
172496
  const { ctx, sessionId, db, historian } = args;
172089
172497
  const compressor = historian.compressor;
@@ -172109,30 +172517,46 @@ function maybeFireCompressor(args) {
172109
172517
  });
172110
172518
  if (!historyBudgetTokens || historyBudgetTokens <= 0)
172111
172519
  return;
172112
- const runPromise = runPiCompressionPassIfNeeded({
172113
- db,
172114
- sessionId,
172115
- directory: ctx.cwd,
172116
- runner: historian.runner,
172117
- historianModel: historian.model,
172118
- fallbackModels: historian.fallbackModels,
172119
- historyBudgetTokens,
172120
- historianTimeoutMs: historian.timeoutMs,
172121
- thinkingLevel: historian.thinkingLevel,
172122
- minCompartmentRatio: compressor.minCompartmentRatio,
172123
- maxMergeDepth: compressor.maxMergeDepth,
172124
- maxCompartmentsPerPass: compressor.maxCompartmentsPerPass,
172125
- graceCompartments: compressor.graceCompartments,
172126
- onPublished: () => {
172127
- if (!isContextHandlerSessionActive(sessionId)) {
172128
- sessionLog(sessionId, "compressor publication ignored: session was cleared");
172129
- return;
172130
- }
172131
- signalPiDeferredHistoryRefresh(sessionId);
172132
- signalPiDeferredMaterialization(sessionId);
172133
- historian.onStatusChange?.(ctx, sessionId);
172520
+ const holderId = crypto2.randomUUID();
172521
+ const runPromise = (async () => {
172522
+ const lease2 = acquireCompartmentLease(db, sessionId, holderId);
172523
+ if (!lease2) {
172524
+ sessionLog(sessionId, "compressor skipped: compartment lease held by another process");
172525
+ return false;
172134
172526
  }
172135
- }).then((didPublish) => {
172527
+ const renewal = startPiCompartmentLeaseRenewal(db, sessionId, holderId);
172528
+ try {
172529
+ return await runPiCompressionPassIfNeeded({
172530
+ db,
172531
+ sessionId,
172532
+ directory: ctx.cwd,
172533
+ runner: historian.runner,
172534
+ historianModel: historian.model,
172535
+ fallbackModels: historian.fallbackModels,
172536
+ historyBudgetTokens,
172537
+ historianTimeoutMs: historian.timeoutMs,
172538
+ thinkingLevel: historian.thinkingLevel,
172539
+ minCompartmentRatio: compressor.minCompartmentRatio,
172540
+ maxMergeDepth: compressor.maxMergeDepth,
172541
+ maxCompartmentsPerPass: compressor.maxCompartmentsPerPass,
172542
+ graceCompartments: compressor.graceCompartments,
172543
+ compartmentLeaseHolderId: holderId,
172544
+ onPublished: () => {
172545
+ const sessionStillActive = isContextHandlerSessionActive(sessionId);
172546
+ signalPiDeferredHistoryRefresh(sessionId);
172547
+ signalPiDeferredMaterialization(sessionId);
172548
+ if (sessionStillActive) {
172549
+ historian.onStatusChange?.(ctx, sessionId);
172550
+ } else {
172551
+ sessionLog(sessionId, "compressor publication recorded after session clear; status callback skipped");
172552
+ }
172553
+ }
172554
+ });
172555
+ } finally {
172556
+ clearInterval(renewal);
172557
+ releaseCompartmentLease(db, sessionId, holderId);
172558
+ }
172559
+ })().then((didPublish) => {
172136
172560
  if (didPublish === true) {
172137
172561
  markPiCompressorRun(sessionId);
172138
172562
  }
@@ -172170,37 +172594,53 @@ function sendPiIgnoredNotification(ctx, message) {
172170
172594
  }
172171
172595
  function spawnPiHistorianRun(args) {
172172
172596
  const { ctx, sessionId, db, historian, provider: provider2, unregister } = args;
172173
- const runPromise = runPiHistorian({
172174
- db,
172175
- sessionId,
172176
- directory: ctx.cwd,
172177
- provider: provider2,
172178
- appendCompaction: resolvePiAppendCompaction(ctx),
172179
- readBranchEntries: resolvePiReadBranchEntries(ctx),
172180
- runner: historian.runner,
172181
- historianModel: historian.model,
172182
- fallbackModels: historian.fallbackModels,
172183
- historianChunkTokens: historian.historianChunkTokens,
172184
- historianTimeoutMs: historian.timeoutMs,
172185
- twoPass: historian.twoPass,
172186
- thinkingLevel: historian.thinkingLevel,
172187
- memoryEnabled: historian.memoryEnabled,
172188
- autoPromote: historian.autoPromote,
172189
- onPublished: () => {
172190
- if (!isContextHandlerSessionActive(sessionId)) {
172191
- sessionLog(sessionId, "historian publication ignored: session was cleared");
172192
- return;
172193
- }
172194
- try {
172195
- clearEmergencyRecovery(db, sessionId);
172196
- } catch (err) {
172197
- sessionLog(sessionId, `historian: clearEmergencyRecovery failed: ${err instanceof Error ? err.message : String(err)}`);
172198
- }
172199
- signalPiDeferredHistoryRefresh(sessionId);
172200
- signalPiDeferredMaterialization(sessionId);
172201
- historian.onStatusChange?.(ctx, sessionId);
172597
+ const holderId = crypto2.randomUUID();
172598
+ const runPromise = (async () => {
172599
+ const lease2 = acquireCompartmentLease(db, sessionId, holderId);
172600
+ if (!lease2) {
172601
+ sessionLog(sessionId, "historian skipped: compartment lease held by another process");
172602
+ return;
172202
172603
  }
172203
- }).finally(() => {
172604
+ const renewal = startPiCompartmentLeaseRenewal(db, sessionId, holderId);
172605
+ try {
172606
+ await runPiHistorian({
172607
+ db,
172608
+ sessionId,
172609
+ directory: ctx.cwd,
172610
+ provider: provider2,
172611
+ appendCompaction: resolvePiAppendCompaction(ctx),
172612
+ readBranchEntries: resolvePiReadBranchEntries(ctx),
172613
+ runner: historian.runner,
172614
+ historianModel: historian.model,
172615
+ fallbackModels: historian.fallbackModels,
172616
+ historianChunkTokens: historian.historianChunkTokens,
172617
+ historianTimeoutMs: historian.timeoutMs,
172618
+ twoPass: historian.twoPass,
172619
+ thinkingLevel: historian.thinkingLevel,
172620
+ memoryEnabled: historian.memoryEnabled,
172621
+ autoPromote: historian.autoPromote,
172622
+ compartmentLeaseHolderId: holderId,
172623
+ onPublished: () => {
172624
+ const sessionStillActive = isContextHandlerSessionActive(sessionId);
172625
+ try {
172626
+ clearEmergencyRecovery(db, sessionId);
172627
+ } catch (err) {
172628
+ sessionLog(sessionId, `historian: clearEmergencyRecovery failed: ${err instanceof Error ? err.message : String(err)}`);
172629
+ }
172630
+ signalPiDeferredHistoryRefresh(sessionId);
172631
+ signalPiDeferredMaterialization(sessionId);
172632
+ if (sessionStillActive) {
172633
+ historian.onStatusChange?.(ctx, sessionId);
172634
+ } else {
172635
+ sessionLog(sessionId, "historian publication recorded after session clear; status callback skipped");
172636
+ }
172637
+ }
172638
+ });
172639
+ } finally {
172640
+ clearInterval(renewal);
172641
+ releaseCompartmentLease(db, sessionId, holderId);
172642
+ }
172643
+ })().finally(() => {
172204
172644
  inFlightHistorian.delete(sessionId);
172205
172645
  unregister();
172206
172646
  if (isContextHandlerSessionActive(sessionId)) {
@@ -172221,12 +172661,11 @@ function resolvePiReadBranchEntries(ctx) {
172221
172661
  if (typeof sm?.getBranch !== "function")
172222
172662
  return;
172223
172663
  return () => {
172224
- try {
172225
- const entries = sm.getBranch?.call(sm);
172226
- return Array.isArray(entries) ? entries : [];
172227
- } catch {
172228
- return [];
172664
+ const entries = sm.getBranch?.call(sm);
172665
+ if (!Array.isArray(entries)) {
172666
+ throw new Error("Pi sessionManager.getBranch() did not return an array");
172229
172667
  }
172668
+ return entries;
172230
172669
  };
172231
172670
  }
172232
172671
  function maybeFireHistorian(args) {
@@ -172328,6 +172767,7 @@ Historian previously failed ${failureState.failureCount} time(s), so magic-conte
172328
172767
  async function runPipeline(args) {
172329
172768
  let executedWorkThisPass = false;
172330
172769
  let historyWasConsumedThisPass = false;
172770
+ let materializationSatisfiedThisPass = false;
172331
172771
  let suppressDeferredHistoryDrain = false;
172332
172772
  let casLost = false;
172333
172773
  const deferredHistoryWasPendingAtPassStart = deferredHistoryRefreshSessions.has(args.sessionId);
@@ -172349,7 +172789,7 @@ async function runPipeline(args) {
172349
172789
  return findLatestUserMessageIdPi(args.messages, ids)?.messageId ?? null;
172350
172790
  })();
172351
172791
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
172352
- const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
172792
+ const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || hasPendingMaterialization(args.sessionId) || deferredMaterializationSessions.has(args.sessionId) || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
172353
172793
  const tTag = performance.now();
172354
172794
  const { targets } = tagTranscript(args.sessionId, transcript, args.tagger, args.db, {
172355
172795
  skipPrefixInjection: !args.ctxReduceEnabled
@@ -172372,22 +172812,26 @@ async function runPipeline(args) {
172372
172812
  const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
172373
172813
  const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
172374
172814
  const deferredHistoryRefreshWasPending = deferredHistoryWasPendingAtPassStart;
172815
+ const deferredExecuteWasPending = peekDeferredExecutePending(args.db, args.sessionId) !== null;
172375
172816
  const pendingOps = getPendingOps(args.db, args.sessionId);
172376
- const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal;
172817
+ const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal || deferredExecuteWasPending;
172377
172818
  const canConsumeDeferredLate = baseShouldApplyPendingOps || shouldRunHeuristics;
172378
172819
  const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
172379
172820
  const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
172380
172821
  const shouldApplyPendingOps = baseShouldApplyPendingOps || deferredMaterialize;
172381
172822
  if (shouldApplyPendingOps) {
172382
- const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
172823
+ const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredExecuteWasPending ? "deferred_boundary_execute" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
172383
172824
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
172384
172825
  try {
172385
172826
  const tApplyPending = performance.now();
172386
172827
  applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags, undefined, pendingOps);
172387
172828
  logTransformTiming(args.sessionId, "applyPendingOperations", tApplyPending);
172388
172829
  executedWorkThisPass = true;
172830
+ materializationSatisfiedThisPass = true;
172389
172831
  if (hasPendingMaterializeSignal) {
172390
- consumePendingMaterialization(args.sessionId);
172832
+ if (args.heuristics === undefined) {
172833
+ consumePendingMaterialization(args.sessionId);
172834
+ }
172391
172835
  }
172392
172836
  if (deferredMaterialize) {
172393
172837
  consumeDeferredMaterialization(args.sessionId);
@@ -172467,6 +172911,9 @@ async function runPipeline(args) {
172467
172911
  }, activeTags);
172468
172912
  heuristicsExecuted = true;
172469
172913
  executedWorkThisPass = true;
172914
+ if (hasPendingMaterializeSignal) {
172915
+ consumePendingMaterialization(args.sessionId);
172916
+ }
172470
172917
  if (currentTurnId !== null) {
172471
172918
  lastHeuristicsTurnIdBySession.set(args.sessionId, currentTurnId);
172472
172919
  }
@@ -172501,7 +172948,9 @@ async function runPipeline(args) {
172501
172948
  }
172502
172949
  logTransformTiming(args.sessionId, "clearOldReasoning", tClearReasoning);
172503
172950
  logTransformTiming(args.sessionId, "watermarkCleanup", tClearReasoning);
172504
- executedWorkThisPass = true;
172951
+ if (combinedWatermark > prevWatermark || clearOutcome.cleared > 0 || stripOutcome.stripped > 0) {
172952
+ executedWorkThisPass = true;
172953
+ }
172505
172954
  } catch (err) {
172506
172955
  sessionLog(args.sessionId, `reasoning clearing failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
172507
172956
  }
@@ -172531,7 +172980,7 @@ async function runPipeline(args) {
172531
172980
  messages: args.messages,
172532
172981
  isCacheBusting: args.isCacheBusting
172533
172982
  });
172534
- const deferredHistoryDrainEligible = historyWasConsumedThisPass && deferredHistoryWasPendingAtPassStart && !suppressDeferredHistoryDrain && !casLost;
172983
+ const deferredHistoryDrainEligible = historyWasConsumedThisPass && materializationSatisfiedThisPass && (deferredHistoryWasPendingAtPassStart || hasPendingMaterializeSignal) && !suppressDeferredHistoryDrain && !casLost;
172535
172984
  if (deferredHistoryDrainEligible) {
172536
172985
  try {
172537
172986
  const pending = getPendingPiCompactionMarkerState(args.db, args.sessionId);
@@ -172621,7 +173070,7 @@ function applyStickyTurnReminder(args) {
172621
173070
  const messageIdByIndex = buildPiMessageIdByIndex(args.messages, args.entryIds);
172622
173071
  if (reminder.messageId) {
172623
173072
  const reinjected = appendReminderToUserMessageByIdPi(args.messages, messageIdByIndex, reminder.messageId, reminder.text);
172624
- if (!reinjected && args.isCacheBustingPass && args.entryIds !== null) {
173073
+ if (!reinjected && args.isCacheBustingPass && args.entryIds !== null && !args.entryIds.includes(undefined) && !args.entryIds.includes(reminder.messageId)) {
172625
173074
  clearPersistedStickyTurnReminder(args.db, args.sessionId);
172626
173075
  }
172627
173076
  return args.messages;
@@ -172638,14 +173087,15 @@ function applyStickyTurnReminder(args) {
172638
173087
  return args.messages;
172639
173088
  }
172640
173089
  function applyNoteNudges(args) {
172641
- const { sessionId, db, messages, projectIdentity, entryIds, isCacheBusting } = args;
173090
+ const { sessionId, db, messages, projectIdentity, entryIds } = args;
172642
173091
  const messageIdByIndex = buildPiMessageIdByIndex(messages, entryIds);
173092
+ const replayMessageIdByIndex = buildPiMessageIdByIndex(messages, entryIds, true);
172643
173093
  for (const anchor of getNoteNudgeAnchors(db, sessionId)) {
172644
- appendReminderToUserMessageByIdPi(messages, messageIdByIndex, anchor.messageId, anchor.text);
173094
+ appendReminderToUserMessageByIdPi(messages, replayMessageIdByIndex, anchor.messageId, anchor.text);
172645
173095
  }
172646
173096
  for (const decision of getAutoSearchHintDecisions(db, sessionId)) {
172647
173097
  if (decision.decision === "hint") {
172648
- appendReminderToUserMessageByIdPi(messages, messageIdByIndex, decision.messageId, decision.text);
173098
+ appendReminderToUserMessageByIdPi(messages, replayMessageIdByIndex, decision.messageId, decision.text);
172649
173099
  }
172650
173100
  }
172651
173101
  const latestUser = findLatestUserMessageIdPi(messages, messageIdByIndex);
@@ -172661,14 +173111,18 @@ function applyNoteNudges(args) {
172661
173111
 
172662
173112
  <instruction name="deferred_notes">${deferredNoteText}</instruction>`;
172663
173113
  const anchoredId = latestUser?.messageId ?? null;
173114
+ if (!anchoredId) {
173115
+ sessionLog(sessionId, "Pi note-nudge: latest user message has no resolved SessionEntry id; deferring delivery to next pass");
173116
+ return messages;
173117
+ }
172664
173118
  const outcome = markNoteNudgeDelivered(db, sessionId, noteInstruction, anchoredId);
172665
- if (latestUser && anchoredId && outcome.ok) {
173119
+ if (latestUser && outcome.ok) {
172666
173120
  appendReminderToPiUserMessage(messages[latestUser.index], noteInstruction);
172667
- } else if (anchoredId && !outcome.ok) {
173121
+ } else if (!outcome.ok) {
172668
173122
  sessionLog(sessionId, `Pi note-nudge delivery skipped wire append: ${outcome.kind}`);
172669
173123
  }
172670
173124
  }
172671
- if (isCacheBusting && entryIds !== null) {
173125
+ if (entryIds !== null && !entryIds.includes(undefined)) {
172672
173126
  const visibleIds = new Set(entryIds.filter((id) => typeof id === "string"));
172673
173127
  pruneNoteNudgeAnchors(db, sessionId, visibleIds);
172674
173128
  pruneAutoSearchHintDecisions(db, sessionId, visibleIds);
@@ -172700,7 +173154,7 @@ function hasMeaningfulUserTextPi(message) {
172700
173154
  }
172701
173155
  return false;
172702
173156
  }
172703
- function buildPiMessageIdByIndex(messages, entryIds) {
173157
+ function buildPiMessageIdByIndex(messages, entryIds, includeMessageIdFallback = false) {
172704
173158
  const ids = new Map;
172705
173159
  for (let index = 0;index < messages.length; index += 1) {
172706
173160
  const entryId = entryIds?.[index];
@@ -172708,9 +173162,11 @@ function buildPiMessageIdByIndex(messages, entryIds) {
172708
173162
  ids.set(index, entryId);
172709
173163
  continue;
172710
173164
  }
172711
- const messageId = messages[index]?.id;
172712
- if (typeof messageId === "string") {
172713
- ids.set(index, messageId);
173165
+ if (includeMessageIdFallback) {
173166
+ const messageId = messages[index]?.id;
173167
+ if (typeof messageId === "string") {
173168
+ ids.set(index, messageId);
173169
+ }
172714
173170
  }
172715
173171
  }
172716
173172
  return ids;
@@ -172841,7 +173297,7 @@ init_logger();
172841
173297
  init_data_path();
172842
173298
  init_logger();
172843
173299
  await init_sqlite();
172844
- import { join as join17 } from "node:path";
173300
+ import { join as join18 } from "node:path";
172845
173301
  var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
172846
173302
  function randomBase62(length) {
172847
173303
  const chars = [];
@@ -172862,7 +173318,7 @@ function generatePartId(timestampMs, counter = 0n) {
172862
173318
  return generateId("prt", timestampMs, counter);
172863
173319
  }
172864
173320
  function getOpenCodeDbPath3() {
172865
- return join17(getDataDir(), "opencode", "opencode.db");
173321
+ return join18(getDataDir(), "opencode", "opencode.db");
172866
173322
  }
172867
173323
  var cachedWriteDb = null;
172868
173324
  var REQUIRED_MESSAGE_COLUMNS = ["id", "session_id", "time_created", "time_updated", "data"];
@@ -173031,7 +173487,7 @@ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEn
173031
173487
  // ../plugin/src/hooks/magic-context/compartment-runner-compressor.ts
173032
173488
  init_logger();
173033
173489
  var HISTORIAN_AGENT2 = "historian";
173034
- var lastDepthHistogramBySession = new Map;
173490
+ var lastDepthHistogramBySession2 = new Map;
173035
173491
  function cavemanLevelForDepth2(outputDepth) {
173036
173492
  if (outputDepth <= 1)
173037
173493
  return null;
@@ -173045,6 +173501,10 @@ function cavemanLevelForDepth2(outputDepth) {
173045
173501
  }
173046
173502
  async function runCompressionPassIfNeeded(deps) {
173047
173503
  const { db, sessionId, historyBudgetTokens } = deps;
173504
+ if (deps.historianRunnable === false) {
173505
+ sessionLog(sessionId, "compressor: skipped because historian.disable=true");
173506
+ return false;
173507
+ }
173048
173508
  const minCompartmentRatio = deps.minCompartmentRatio ?? DEFAULT_COMPRESSOR_MIN_COMPARTMENT_RATIO;
173049
173509
  const maxMergeDepth = deps.maxMergeDepth ?? DEFAULT_COMPRESSOR_MAX_MERGE_DEPTH;
173050
173510
  const compartments = getCompartments(db, sessionId);
@@ -173084,8 +173544,8 @@ ${c.content}
173084
173544
  }
173085
173545
  const histText = [...depthHistogram.entries()].sort((a, b) => a[0] - b[0]).map(([d, n]) => `d${d}=${n}`).join(" ");
173086
173546
  const histKey = `${scored.length}|${histText}`;
173087
- if (lastDepthHistogramBySession.get(sessionId) !== histKey) {
173088
- lastDepthHistogramBySession.set(sessionId, histKey);
173547
+ if (lastDepthHistogramBySession2.get(sessionId) !== histKey) {
173548
+ lastDepthHistogramBySession2.set(sessionId, histKey);
173089
173549
  sessionLog(sessionId, `compressor: depth histogram (${scored.length} total) ${histText}`);
173090
173550
  }
173091
173551
  const floorHeadroom = compartments.length - floor;
@@ -173131,7 +173591,8 @@ ${c.content}
173131
173591
  originalStart: selectedCompartments[0].startMessage,
173132
173592
  originalEnd: selectedCompartments[selectedCompartments.length - 1].endMessage,
173133
173593
  facts,
173134
- logLabel: `depth-5 title-only collapse (${selectedCompartments.length} → ${selectedCompartments.length})`
173594
+ logLabel: `depth-5 title-only collapse (${selectedCompartments.length} → ${selectedCompartments.length})`,
173595
+ holderId: deps.compartmentLeaseHolderId
173135
173596
  });
173136
173597
  }
173137
173598
  try {
@@ -173161,7 +173622,8 @@ ${c.content}
173161
173622
  originalStart: selectedCompartments[0].startMessage,
173162
173623
  originalEnd: selectedCompartments[selectedCompartments.length - 1].endMessage,
173163
173624
  facts,
173164
- logLabel: `depth-${outputDepth} (${selectedCompartments.length} → ${finalCompressed.length})`
173625
+ logLabel: `depth-${outputDepth} (${selectedCompartments.length} → ${finalCompressed.length})`,
173626
+ holderId: deps.compartmentLeaseHolderId
173165
173627
  });
173166
173628
  } catch (error51) {
173167
173629
  sessionLog(sessionId, "compressor: unexpected error:", getErrorMessage(error51));
@@ -173286,7 +173748,8 @@ function finalizeCompression2(args) {
173286
173748
  originalStart,
173287
173749
  originalEnd,
173288
173750
  facts,
173289
- logLabel
173751
+ logLabel,
173752
+ holderId
173290
173753
  } = args;
173291
173754
  const compressedStart = compressed[0].startMessage;
173292
173755
  const compressedEnd = compressed[compressed.length - 1].endMessage;
@@ -173337,8 +173800,16 @@ function finalizeCompression2(args) {
173337
173800
  content: c.content
173338
173801
  }))
173339
173802
  ];
173340
- replaceAllCompartmentState(db, sessionId, allCompartments, facts.map((f) => ({ category: f.category, content: f.content })));
173341
- incrementCompressionDepth(db, sessionId, originalStart, originalEnd);
173803
+ const factInputs = facts.map((f) => ({ category: f.category, content: f.content }));
173804
+ const published = holderId ? replaceAllCompartmentStateAndBumpDepth(db, holderId, sessionId, allCompartments, factInputs, originalStart, originalEnd) : (() => {
173805
+ replaceAllCompartmentState(db, sessionId, allCompartments, factInputs);
173806
+ incrementCompressionDepth(db, sessionId, originalStart, originalEnd);
173807
+ return true;
173808
+ })();
173809
+ if (!published) {
173810
+ sessionLog(sessionId, "compressor: publish skipped because compartment lease is no longer held");
173811
+ return false;
173812
+ }
173342
173813
  sessionLog(sessionId, `compressor: completed ${logLabel}`);
173343
173814
  return true;
173344
173815
  }
@@ -173419,8 +173890,8 @@ async function runCompressorPass2(args) {
173419
173890
  }
173420
173891
 
173421
173892
  // ../plugin/src/hooks/magic-context/compartment-runner-historian.ts
173422
- import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
173423
- import { join as join18 } from "node:path";
173893
+ import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
173894
+ import { join as join19 } from "node:path";
173424
173895
  init_data_path();
173425
173896
  function historianResponseDumpDir(directory) {
173426
173897
  return getProjectMagicContextHistorianDir(directory);
@@ -173671,11 +174142,11 @@ function cleanupHistorianDump(sessionId, dumpPath) {
173671
174142
  function dumpHistorianResponse(sessionId, directory, label, text) {
173672
174143
  try {
173673
174144
  const dumpDir = historianResponseDumpDir(directory);
173674
- mkdirSync6(dumpDir, { recursive: true });
174145
+ mkdirSync7(dumpDir, { recursive: true });
173675
174146
  const safeSessionId = sanitizeDumpName(sessionId);
173676
174147
  const safeLabel = sanitizeDumpName(label);
173677
- const dumpPath = join18(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
173678
- writeFileSync3(dumpPath, text, "utf8");
174148
+ const dumpPath = join19(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
174149
+ writeFileSync4(dumpPath, text, "utf8");
173679
174150
  sessionLog(sessionId, "compartment agent: historian response dumped", {
173680
174151
  label,
173681
174152
  dumpPath
@@ -173856,6 +174327,12 @@ async function executePartialRecompInternal(deps, range) {
173856
174327
  getNotificationParams
173857
174328
  } = deps;
173858
174329
  const notifParams = () => getNotificationParams?.() ?? {};
174330
+ const holderId = deps.compartmentLeaseHolderId;
174331
+ if (!holderId) {
174332
+ return `## Magic Recomp — Failed
174333
+
174334
+ Could not acquire the compartment-state lease for this session.`;
174335
+ }
173859
174336
  updateSessionMeta(db, sessionId, { compartmentInProgress: true });
173860
174337
  try {
173861
174338
  let promoteFinal = function() {
@@ -173892,7 +174369,7 @@ async function executePartialRecompInternal(deps, range) {
173892
174369
  return null;
173893
174370
  }
173894
174371
  saveRecompStagingPass(db, sessionId, passCount + 1, merged, currentFacts);
173895
- const promoted = promoteRecompStaging(db, sessionId);
174372
+ const promoted = promoteRecompStaging(db, sessionId, holderId);
173896
174373
  if (!promoted) {
173897
174374
  log("[magic-context] partial recomp promote returned null");
173898
174375
  return null;
@@ -174100,6 +174577,7 @@ Staging preserved for resume on next attempt.`;
174100
174577
  }
174101
174578
 
174102
174579
  // ../plugin/src/hooks/magic-context/compartment-runner-recomp.ts
174580
+ init_logger();
174103
174581
  async function executeContextRecompInternal(deps) {
174104
174582
  const {
174105
174583
  client,
@@ -174111,6 +174589,12 @@ async function executeContextRecompInternal(deps) {
174111
174589
  getNotificationParams
174112
174590
  } = deps;
174113
174591
  const notifParams = () => getNotificationParams?.() ?? {};
174592
+ const holderId = deps.compartmentLeaseHolderId;
174593
+ if (!holderId) {
174594
+ return `## Magic Recomp — Skipped
174595
+
174596
+ Could not acquire the compartment-state lease for this session.`;
174597
+ }
174114
174598
  let currentStateFilePath;
174115
174599
  updateSessionMeta(db, sessionId, { compartmentInProgress: true });
174116
174600
  try {
@@ -174147,7 +174631,7 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
174147
174631
  if (mergedError)
174148
174632
  return null;
174149
174633
  saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, candidateFacts);
174150
- const promoted2 = promoteRecompStaging(db, sessionId);
174634
+ const promoted2 = promoteRecompStaging(db, sessionId, holderId);
174151
174635
  if (!promoted2)
174152
174636
  return null;
174153
174637
  clearCompressionDepth(db, sessionId);
@@ -174298,10 +174782,12 @@ Recomp completed ${passCount} pass${passCount === 1 ? "" : "es"} but produced an
174298
174782
  Nothing was written.`;
174299
174783
  }
174300
174784
  saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, candidateFacts);
174301
- const promoted = promoteRecompStaging(db, sessionId);
174785
+ const promoted = promoteRecompStaging(db, sessionId, holderId);
174302
174786
  if (!promoted) {
174303
- replaceAllCompartmentState(db, sessionId, candidateCompartments, candidateFacts);
174304
- clearRecompStaging(db, sessionId);
174787
+ sessionLog(sessionId, "recomp publish skipped: compartment lease no longer held");
174788
+ return `## Magic Recomp — Skipped
174789
+
174790
+ Another process acquired the compartment-state lease before recomp could publish. No state was written.`;
174305
174791
  }
174306
174792
  clearCompressionDepth(db, sessionId);
174307
174793
  if (deps.preserveInjectionCacheUntilConsumed !== true) {
@@ -174370,12 +174856,32 @@ function withPublishedCallback(deps) {
174370
174856
  }
174371
174857
  };
174372
174858
  }
174373
- async function executeContextRecomp(deps, options = {}) {
174859
+ function startLeaseRenewal(deps, holderId) {
174860
+ return setInterval(() => {
174861
+ if (!renewCompartmentLease(deps.db, deps.sessionId, holderId)) {
174862
+ sessionLog(deps.sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
174863
+ }
174864
+ }, COMPARTMENT_LEASE_RENEWAL_MS);
174865
+ }
174866
+ async function executeContextRecompWithResult(deps, options = {}) {
174374
174867
  const { sessionId } = deps;
174375
174868
  if (activeRuns.has(sessionId)) {
174376
- return "## Magic Recomp\n\nHistorian is already running for this session. Wait for it to finish, then try `/ctx-recomp` again.";
174869
+ return {
174870
+ message: "## Magic Recomp\n\nHistorian is already running for this session. Wait for it to finish, then try `/ctx-recomp` again.",
174871
+ published: false
174872
+ };
174873
+ }
174874
+ const holderId = crypto.randomUUID();
174875
+ const lease2 = acquireCompartmentLease(deps.db, sessionId, holderId);
174876
+ if (!lease2) {
174877
+ sessionLog(sessionId, "recomp skipped: compartment lease held by another process");
174878
+ return {
174879
+ message: "## Magic Recomp\n\nAnother process is already mutating compartment state for this session. Wait for it to finish, then try `/ctx-recomp` again.",
174880
+ published: false
174881
+ };
174377
174882
  }
174378
- const runnerDeps = withPublishedCallback(deps);
174883
+ const renewal = startLeaseRenewal(deps, holderId);
174884
+ const runnerDeps = withPublishedCallback({ ...deps, compartmentLeaseHolderId: holderId });
174379
174885
  const promise2 = options.range ? executePartialRecompInternal(runnerDeps, options.range) : executeContextRecompInternal(runnerDeps);
174380
174886
  const wrappedPromise = promise2.then(() => {
174381
174887
  return;
@@ -174384,8 +174890,14 @@ async function executeContextRecomp(deps, options = {}) {
174384
174890
  });
174385
174891
  activeRuns.set(sessionId, { promise: wrappedPromise, published: false });
174386
174892
  try {
174387
- return await promise2;
174893
+ const message = await promise2;
174894
+ return {
174895
+ message,
174896
+ published: activeRuns.get(sessionId)?.published === true
174897
+ };
174388
174898
  } finally {
174899
+ clearInterval(renewal);
174900
+ releaseCompartmentLease(deps.db, sessionId, holderId);
174389
174901
  if (activeRuns.get(sessionId)?.promise === wrappedPromise) {
174390
174902
  activeRuns.delete(sessionId);
174391
174903
  }
@@ -174558,7 +175070,7 @@ Historian recomp started. Rebuilding compartments and facts from raw Pi session
174558
175070
  projectIdentity: ctx.cwd
174559
175071
  });
174560
175072
  try {
174561
- const result = await executeContextRecomp({
175073
+ const result = await executeContextRecompWithResult({
174562
175074
  client: createPiRecompClient({
174563
175075
  runner: deps.runner,
174564
175076
  model: deps.historianModel,
@@ -174584,9 +175096,12 @@ Historian recomp started. Rebuilding compartments and facts from raw Pi session
174584
175096
  }, parsed.kind === "partial" ? { range: parsed.range } : {});
174585
175097
  sendCtxStatusMessage(pi, {
174586
175098
  title: "/ctx-recomp",
174587
- text: result,
174588
- level: inferLevel(result)
175099
+ text: result.message,
175100
+ level: inferLevel(result.message)
174589
175101
  });
175102
+ if (!result.published) {
175103
+ return;
175104
+ }
174590
175105
  queueAndApplyPiRecompMarker({ db: deps.db, sessionId, ctx });
174591
175106
  signalPiHistoryRefresh(sessionId);
174592
175107
  signalPiPendingMaterialization(sessionId);
@@ -174780,8 +175295,8 @@ function createPiRecompClient(args) {
174780
175295
  function readPathId(input) {
174781
175296
  if (typeof input !== "object" || input === null)
174782
175297
  return "";
174783
- const path5 = input.path;
174784
- return typeof path5?.id === "string" ? path5.id : "";
175298
+ const path6 = input.path;
175299
+ return typeof path6?.id === "string" ? path6.id : "";
174785
175300
  }
174786
175301
  function readBody(input) {
174787
175302
  if (typeof input !== "object" || input === null)
@@ -174932,10 +175447,20 @@ import {
174932
175447
  truncateToWidth,
174933
175448
  visibleWidth
174934
175449
  } from "@earendil-works/pi-tui";
175450
+
175451
+ // ../plugin/src/shared/format-threshold.ts
175452
+ function formatThresholdPercent(value) {
175453
+ if (typeof value !== "number" || !Number.isFinite(value))
175454
+ return "—";
175455
+ const rounded = Math.round(value);
175456
+ if (Math.abs(value - rounded) < 0.05)
175457
+ return String(rounded);
175458
+ return value.toFixed(1);
175459
+ }
174935
175460
  // package.json
174936
175461
  var package_default = {
174937
175462
  name: "@cortexkit/pi-magic-context",
174938
- version: "0.21.5",
175463
+ version: "0.21.7",
174939
175464
  type: "module",
174940
175465
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
174941
175466
  main: "dist/index.js",
@@ -175099,7 +175624,7 @@ function renderInner(s, theme, innerWidth) {
175099
175624
  lines.push(theme.fg("muted", "Tags"));
175100
175625
  lines.push(`Active ${s.activeTags} (~${formatBytes(s.activeBytes)}) · Dropped ${s.droppedTags} · Total ${s.totalTags}`);
175101
175626
  lines.push(theme.fg("muted", "Rolling Nudges / Context"));
175102
- lines.push(`Execute threshold ${s.executeThreshold}% · Anchor ${fmt2(s.lastNudgeTokens)} tok · Interval ${fmt2(s.nudgeInterval)} tok · Next ${fmt2(s.nextNudgeAfter)} tok`);
175627
+ lines.push(`Execute threshold ${formatThresholdPercent(s.executeThreshold)}% · Anchor ${fmt2(s.lastNudgeTokens)} tok · Interval ${fmt2(s.nudgeInterval)} tok · Next ${fmt2(s.nextNudgeAfter)} tok`);
175103
175628
  lines.push(`Protected tags ${s.protectedTagCount} · Subagent ${s.isSubagent ? "yes" : "no"} · History block ~${fmt2(s.historyBlockTokens)} tok${s.compressionBudget ? ` · Budget ~${fmt2(s.compressionBudget)} tok (${s.compressionUsage} used)` : ""}`);
175104
175629
  if (s.lastTransformError)
175105
175630
  lines.push(theme.fg("error", `⚠ ${s.lastTransformError}`));
@@ -175447,7 +175972,7 @@ function buildStatusDetails(deps, sessionId) {
175447
175972
  status: ["pending", "ready"]
175448
175973
  }).length,
175449
175974
  dreamer: {
175450
- enabled: deps.dreamer?.enabled === true,
175975
+ enabled: deps.dreamer?.runnable === true,
175451
175976
  schedule: deps.dreamer?.schedule ?? null,
175452
175977
  lastRunAt: readNumberState(deps.db, `last_dream_at:${deps.projectIdentity}`)
175453
175978
  },
@@ -180661,7 +181186,7 @@ var PLUGIN_VERSION = (() => {
180661
181186
  setHarness("pi");
180662
181187
  function resolveSidekickFromConfig(config2) {
180663
181188
  const sidekick = config2.sidekick;
180664
- if (!sidekick?.enabled)
181189
+ if (!sidekick || sidekick.disable === true)
180665
181190
  return;
180666
181191
  const model = sidekick.model?.trim();
180667
181192
  if (!model || model.length === 0)
@@ -180670,18 +181195,20 @@ function resolveSidekickFromConfig(config2) {
180670
181195
  model,
180671
181196
  systemPrompt: sidekick.system_prompt,
180672
181197
  timeoutMs: sidekick.timeout_ms,
180673
- thinking_level: sidekick.thinking_level
181198
+ thinking_level: sidekick.thinking_level,
181199
+ fallbackModels: resolveFallbackChain("sidekick", sidekick.fallback_models)
180674
181200
  };
180675
181201
  }
180676
181202
  function resolveHistorianFromConfig(config2) {
180677
181203
  const historian = config2.historian;
181204
+ if (historian?.disable === true)
181205
+ return;
180678
181206
  const model = historian?.model?.trim();
180679
181207
  if (!model || model.length === 0)
180680
181208
  return;
180681
181209
  const historianContextLimit = resolveHistorianContextLimit(model);
180682
181210
  const historianChunkTokens = deriveHistorianChunkTokens(historianContextLimit);
180683
- const fbRaw = historian?.fallback_models;
180684
- const fallbackModels = typeof fbRaw === "string" ? [fbRaw] : fbRaw;
181211
+ const fallbackModels = resolveFallbackChain("historian", historian?.fallback_models);
180685
181212
  return {
180686
181213
  runner: new PiSubagentRunner,
180687
181214
  model,
@@ -180730,11 +181257,11 @@ function resolveAutoSearchFromConfig(config2) {
180730
181257
  };
180731
181258
  }
180732
181259
  function resolveDreamerFromConfig(config2) {
180733
- return config2.dreamer;
181260
+ return config2.dreamer?.disable === true ? undefined : config2.dreamer;
180734
181261
  }
180735
181262
  async function src_default2(pi) {
180736
181263
  const storageDir = getMagicContextStorageDir();
180737
- const dbPath = join19(storageDir, "context.db");
181264
+ const dbPath = join20(storageDir, "context.db");
180738
181265
  let db;
180739
181266
  try {
180740
181267
  db = openDatabase();
@@ -180750,6 +181277,7 @@ async function src_default2(pi) {
180750
181277
  const pendingPiMarkerSessions = getSessionsWithPendingPiMarker(db);
180751
181278
  for (const sid of pendingPiMarkerSessions) {
180752
181279
  signalPiDeferredHistoryRefresh(sid);
181280
+ signalPiPendingMaterialization(sid);
180753
181281
  }
180754
181282
  if (pendingPiMarkerSessions.length > 0) {
180755
181283
  log(`${PREFIX2} rehydrated ${pendingPiMarkerSessions.length} Pi deferred compaction marker session(s)`);
@@ -180781,7 +181309,7 @@ async function src_default2(pi) {
180781
181309
  allowDreamerActions: false,
180782
181310
  ctxReduceEnabled: config2.ctx_reduce_enabled === true,
180783
181311
  protectedTags: config2.protected_tags ?? 20,
180784
- dreamerEnabled: config2.dreamer?.enabled === true
181312
+ dreamerEnabled: isDreamerRunnable(config2)
180785
181313
  });
180786
181314
  info(`registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand${config2.ctx_reduce_enabled === true ? ", ctx_reduce" : ""}`);
180787
181315
  const historianConfig = resolveHistorianFromConfig(config2);
@@ -180825,7 +181353,7 @@ async function src_default2(pi) {
180825
181353
  info(autoSearchConfig.enabled ? `registered auto-search hint (threshold=${autoSearchConfig.scoreThreshold}, minChars=${autoSearchConfig.minPromptChars})` : "registered auto-search hint: DISABLED (experimental.auto_search.enabled=false)");
180826
181354
  const sidekickConfig = resolveSidekickFromConfig(config2);
180827
181355
  registerCtxAugCommand(pi, sidekickConfig);
180828
- info(sidekickConfig ? `registered /ctx-aug (sidekick model=${sidekickConfig.model})` : "registered /ctx-aug (sidekick disabled — set sidekick.enabled=true and sidekick.model in config)");
181356
+ info(sidekickConfig ? `registered /ctx-aug (sidekick model=${sidekickConfig.model})` : "registered /ctx-aug (sidekick disabled — set sidekick.disable=false and sidekick.model in config)");
180829
181357
  registerCtxStatusCommand(pi, {
180830
181358
  db,
180831
181359
  projectIdentity,
@@ -180837,7 +181365,7 @@ async function src_default2(pi) {
180837
181365
  commitClusterTrigger: config2.commit_cluster_trigger,
180838
181366
  executeThresholdTokens: config2.execute_threshold_tokens,
180839
181367
  dreamer: {
180840
- enabled: config2.dreamer?.enabled ?? false,
181368
+ runnable: isDreamerRunnable(config2),
180841
181369
  schedule: config2.dreamer?.schedule
180842
181370
  }
180843
181371
  });
@@ -180863,7 +181391,7 @@ async function src_default2(pi) {
180863
181391
  projectDir,
180864
181392
  projectIdentity,
180865
181393
  resolveProject: resolveCurrentProject,
180866
- dreamerEnabled: config2.dreamer?.enabled ?? false,
181394
+ dreamerEnabled: isDreamerRunnable(config2),
180867
181395
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
180868
181396
  });
180869
181397
  info("registered /ctx-dream");
@@ -180879,11 +181407,28 @@ async function src_default2(pi) {
180879
181407
  gitCommitIndexing: config2.experimental.git_commit_indexing,
180880
181408
  onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject
180881
181409
  });
180882
- info(dreamerConfig.enabled ? `registered dreamer (schedule=${dreamerConfig.schedule}, tasks=[${dreamerConfig.tasks.join(",")}])` : "registered dreamer: DISABLED (dreamer.enabled=false)");
181410
+ info(`registered dreamer (schedule=${dreamerConfig.schedule || "manual-only"}, tasks=[${dreamerConfig.tasks.join(",")}])`);
180883
181411
  } else {
180884
- info("registered dreamer: DISABLED (no dreamer config)");
181412
+ info(isDreamerRunnable(config2) ? "registered dreamer: DISABLED (no dreamer config)" : "registered dreamer: DISABLED (dreamer.disable=true or no dreamer config)");
180885
181413
  }
180886
181414
  pi.on("before_agent_start", async (event, ctx) => {
181415
+ try {
181416
+ if (ctx.hasUI && shouldShowAnnouncement()) {
181417
+ const featureText = ANNOUNCEMENT_FEATURES.map((line) => ` • ${line}`).join(`
181418
+ `);
181419
+ const sections = [
181420
+ `✨ Magic Context v${ANNOUNCEMENT_VERSION} — what's new:`,
181421
+ "",
181422
+ featureText
181423
+ ];
181424
+ if (ANNOUNCEMENT_FOOTER && ANNOUNCEMENT_FOOTER.trim().length > 0) {
181425
+ sections.push("", ANNOUNCEMENT_FOOTER);
181426
+ }
181427
+ ctx.ui.notify(sections.join(`
181428
+ `), "info");
181429
+ markAnnouncementSeen(ANNOUNCEMENT_VERSION);
181430
+ }
181431
+ } catch {}
180887
181432
  try {
180888
181433
  const currentProject = resolveCurrentProject(ctx);
180889
181434
  seenDreamerProjectIdentities.add(currentProject.projectIdentity);
@@ -180915,16 +181460,16 @@ async function src_default2(pi) {
180915
181460
  cwd: currentProject.projectDir,
180916
181461
  sessionId,
180917
181462
  memoryEnabled: config2.memory.enabled,
180918
- injectDocs: config2.dreamer?.inject_docs ?? true,
181463
+ injectDocs: isDreamerRunnable(config2) && (config2.dreamer?.inject_docs ?? true),
180919
181464
  includeGuidance: true,
180920
181465
  protectedTags: config2.protected_tags,
180921
181466
  ctxReduceEnabled: config2.ctx_reduce_enabled,
180922
- dreamerEnabled: config2.dreamer?.enabled ?? false,
181467
+ dreamerEnabled: isDreamerRunnable(config2),
180923
181468
  dropToolStructure: config2.drop_tool_structure,
180924
181469
  temporalAwarenessEnabled: config2.experimental?.temporal_awareness ?? false,
180925
181470
  cavemanTextCompressionEnabled: config2.ctx_reduce_enabled === false && config2.experimental?.caveman_text_compression?.enabled === true,
180926
- userMemoriesEnabled: (config2.dreamer?.enabled ?? false) && (config2.dreamer?.user_memories?.enabled ?? false),
180927
- pinKeyFilesEnabled: (config2.dreamer?.enabled ?? false) && (config2.dreamer?.pin_key_files?.enabled ?? false),
181471
+ userMemoriesEnabled: config2.dreamer?.user_memories?.enabled ?? false,
181472
+ pinKeyFilesEnabled: config2.dreamer?.pin_key_files?.enabled ?? false,
180928
181473
  pinKeyFilesTokenBudget: config2.dreamer?.pin_key_files?.token_budget ?? 1e4,
180929
181474
  isCacheBusting,
180930
181475
  existingSystemPrompt: event.systemPrompt
@@ -181063,7 +181608,7 @@ ${block}` : event.systemPrompt;
181063
181608
  continue;
181064
181609
  if (typeof b.name !== "string")
181065
181610
  continue;
181066
- if (!/^todo.*write$|^write.*todo$|^todowrite$/i.test(b.name)) {
181611
+ if (b.name !== "todowrite") {
181067
181612
  continue;
181068
181613
  }
181069
181614
  const args = b.arguments;
@@ -181149,6 +181694,9 @@ ${block}` : event.systemPrompt;
181149
181694
  });
181150
181695
  }
181151
181696
  export {
181697
+ resolveSidekickFromConfig,
181698
+ resolveHistorianFromConfig,
181699
+ resolveDreamerFromConfig,
181152
181700
  persistPiPressureFromMessageEnd,
181153
181701
  persistPiMessageEndModelMeta,
181154
181702
  src_default2 as default