@cortexkit/pi-magic-context 0.40.1 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -52,6 +52,7 @@ import {
52
52
  bumpProjectUserProfileVersion,
53
53
  byteSize,
54
54
  canonicalizeInMemoryChunkTextForEmbedding,
55
+ captureChannel1PostReduceGraceBaseline,
55
56
  casChannel2NudgeClaim,
56
57
  casChannel2NudgeState,
57
58
  chunkCanonicalText,
@@ -100,6 +101,7 @@ import {
100
101
  deriveTagLoadFloor,
101
102
  deriveWindowGeometry,
102
103
  describeError,
104
+ describeProtectedTailDrainBudgetSkip,
103
105
  dismissUserMemory,
104
106
  drainCommitBacklogForProject,
105
107
  effectiveTailHygiene,
@@ -124,6 +126,7 @@ import {
124
126
  extractM0Block,
125
127
  extractTexts,
126
128
  extractToolCallSummaries,
129
+ fenceBoundaryForCompletedToolArcs,
127
130
  fenceBoundaryForToolArcs,
128
131
  findAdoptableFallbackTags,
129
132
  findLastModelKeyFromBranch,
@@ -230,7 +233,6 @@ import {
230
233
  hasMuralCueColumns,
231
234
  hasPiFallbackMessageTags,
232
235
  hasPiFallbackToolOwnerTags,
233
- hasShareabilitySensitiveText,
234
236
  incrementHistorianFailure,
235
237
  indexCommitsForProject,
236
238
  indexMessagesAfterOrdinal,
@@ -256,6 +258,8 @@ import {
256
258
  isWrapupInProgress,
257
259
  loadPiConfig,
258
260
  loadProtectedTailMeta,
261
+ logSlowWriteTransaction,
262
+ markChannel1PostReduceGracePending,
259
263
  markGitSweepSuccessAndRelease,
260
264
  markNoteChecked,
261
265
  markNoteReady,
@@ -298,9 +302,11 @@ import {
298
302
  readGitFileChangeTimesSince,
299
303
  readGitHead,
300
304
  readPiSessionMessages,
305
+ readPiSessionSnapshot,
301
306
  readProjectDocsCanonical,
302
307
  readRawSessionMessages,
303
308
  readSessionChunk,
309
+ reclaimableToolOutputCount,
304
310
  recordHistorianDrainFailure,
305
311
  recordMemoryMapping,
306
312
  recordMemoryVerifications,
@@ -327,8 +333,6 @@ import {
327
333
  replaceCompartmentChunkEmbeddings,
328
334
  replaceSourceContent,
329
335
  reserveProtectedTailDrainTokens,
330
- resetLastNudgeCycle,
331
- resetLastNudgeCycleIfTailShrank,
332
336
  resolveCompactionModeRecord,
333
337
  resolveGitTopLevel,
334
338
  resolveModelConfigOrDefault,
@@ -368,7 +372,6 @@ import {
368
372
  setStoragePrivatePermissionEnforcement,
369
373
  setTodoSnapshot,
370
374
  shouldBypassFailClosedBlock,
371
- shouldUseStickyChannel1Reminder,
372
375
  sourceNameForMemory,
373
376
  stripChannel1ReminderSpans,
374
377
  stripMemoryMuralBlock,
@@ -403,7 +406,7 @@ import {
403
406
  withMigrationLanguageDirective,
404
407
  withRawMessageProvider,
405
408
  withRawSessionMessageCache
406
- } from "./index-fre7qcqg.js";
409
+ } from "./index-ts6nabp0.js";
407
410
  import {
408
411
  pushNotification
409
412
  } from "./index-hw34xmzk.js";
@@ -411,19 +414,22 @@ import {
411
414
  ensureCortexKitArtifactGitignore,
412
415
  getHarness,
413
416
  getMagicContextStorageDir,
417
+ getMagicContextStorageResolution,
414
418
  getProjectMagicContextHistorianDir,
419
+ hasShareabilitySensitiveText,
415
420
  log,
416
421
  sessionLog,
417
422
  setHarness
418
- } from "./index-hk6sj7dn.js";
423
+ } from "./index-3k71e196.js";
419
424
  import {
420
425
  __require,
421
426
  __toESM
422
427
  } from "./index-1yh8g550.js";
423
428
 
424
429
  // src/index.ts
425
- import { createRequire as createRequire3 } from "node:module";
426
- import { join as join5, resolve as resolve2 } from "node:path";
430
+ import { AsyncLocalStorage } from "node:async_hooks";
431
+ import { createRequire as createRequire2 } from "node:module";
432
+ import { join as join6, resolve as resolve3 } from "node:path";
427
433
 
428
434
  // ../plugin/src/shared/model-resolution.ts
429
435
  function asRecord(value) {
@@ -1432,7 +1438,7 @@ function resolveHistorianContextLimit(historianModelOverride) {
1432
1438
  }
1433
1439
 
1434
1440
  // ../plugin/src/hooks/magic-context/event-resolvers.ts
1435
- var DEFAULT_CONTEXT_LIMIT = 128000;
1441
+ var DEFAULT_CONTEXT_LIMIT = 200000;
1436
1442
  function resolveCacheTtl(cacheTtl, modelKey) {
1437
1443
  if (typeof cacheTtl === "string") {
1438
1444
  return cacheTtl;
@@ -2235,16 +2241,21 @@ function resolveBundledPiCli() {
2235
2241
  return null;
2236
2242
  }
2237
2243
  }
2244
+ function isPiCliScript(scriptPath) {
2245
+ const normalized = scriptPath.replaceAll("\\", "/");
2246
+ return /\/@(?:earendil-works|oh-my-pi)\/pi-coding-agent\/dist\/cli\.js$/.test(normalized);
2247
+ }
2248
+ function isGenericRuntimeExecutable(execPath) {
2249
+ return /^(node(?:js)?\d*|bun)(\.exe)?$/.test(basename(execPath).toLowerCase());
2250
+ }
2238
2251
  function resolvePiInvocation() {
2239
2252
  const execPath = process.execPath;
2240
2253
  const currentScript = process.argv[1];
2241
2254
  const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/") ?? false;
2242
- if (currentScript && !isBunVirtualScript && existsSync3(currentScript)) {
2255
+ if (currentScript && !isBunVirtualScript && existsSync3(currentScript) && isPiCliScript(currentScript)) {
2243
2256
  return { command: execPath, prefixArgs: [currentScript] };
2244
2257
  }
2245
- const execName = basename(execPath).toLowerCase();
2246
- const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
2247
- if (!isGenericRuntime) {
2258
+ if (!isGenericRuntimeExecutable(execPath)) {
2248
2259
  return { command: execPath, prefixArgs: [] };
2249
2260
  }
2250
2261
  const bundled = resolveBundledPiCli();
@@ -2253,18 +2264,17 @@ function resolvePiInvocation() {
2253
2264
  }
2254
2265
  return { command: "pi", prefixArgs: [] };
2255
2266
  }
2256
- function resolveSubagentEntryPath() {
2267
+ function resolveSiblingEntryPath(fileName) {
2257
2268
  try {
2258
2269
  const here = dirname(fileURLToPath(import.meta.url));
2259
- const candidate = resolvePath(here, "subagent-entry.js");
2260
- if (existsSync3(candidate))
2261
- return candidate;
2262
- return;
2270
+ const candidate = resolvePath(here, fileName);
2271
+ return existsSync3(candidate) ? candidate : undefined;
2263
2272
  } catch {
2264
2273
  return;
2265
2274
  }
2266
2275
  }
2267
- var SUBAGENT_ENTRY_PATH = resolveSubagentEntryPath();
2276
+ var SUBAGENT_ENTRY_PATH = resolveSiblingEntryPath("subagent-entry.js");
2277
+ var HISTORIAN_CALIBRATION_ENTRY_PATH = resolveSiblingEntryPath("historian-calibration-extension.js");
2268
2278
  var TERMINAL_DRAIN_GRACE_MS = 2000;
2269
2279
  var MAGIC_CONTEXT_PI_SUBAGENT_ENV = "MAGIC_CONTEXT_PI_SUBAGENT";
2270
2280
  function packageRootIsOmp(packageRoot) {
@@ -2338,6 +2348,12 @@ var DREAMER_ACTION_AGENTS = new Set([
2338
2348
  "dreamer",
2339
2349
  "magic-context-dreamer"
2340
2350
  ]);
2351
+ var HISTORIAN_AGENTS = new Set([
2352
+ "magic-context-historian",
2353
+ "historian",
2354
+ "historian-recomp",
2355
+ "historian-editor"
2356
+ ]);
2341
2357
  var SEARCH_ONLY_SUBAGENT_TOOL_AGENTS = new Set([
2342
2358
  "sidekick",
2343
2359
  "dreamer-retrospective",
@@ -2644,7 +2660,13 @@ class PiSubagentRunner {
2644
2660
  cwd: options.cwd,
2645
2661
  env: {
2646
2662
  ...process.env,
2647
- [MAGIC_CONTEXT_PI_SUBAGENT_ENV]: "1"
2663
+ [MAGIC_CONTEXT_PI_SUBAGENT_ENV]: "1",
2664
+ ...options.temperature !== undefined ? {
2665
+ MAGIC_CONTEXT_HISTORIAN_TEMPERATURE: String(options.temperature)
2666
+ } : {},
2667
+ ...options.maxOutputTokens !== undefined ? {
2668
+ MAGIC_CONTEXT_HISTORIAN_MAX_OUTPUT_TOKENS: String(options.maxOutputTokens)
2669
+ } : {}
2648
2670
  },
2649
2671
  stdio: [deliverViaStdin ? "pipe" : "ignore", "pipe", "pipe"]
2650
2672
  });
@@ -3036,6 +3058,10 @@ function buildArgs(options, opts) {
3036
3058
  args.push("--magic-context-dreamer-actions");
3037
3059
  }
3038
3060
  }
3061
+ const historianCalibrationEntryPath = opts?.historianCalibrationEntryPath === undefined ? HISTORIAN_CALIBRATION_ENTRY_PATH : opts.historianCalibrationEntryPath;
3062
+ if (HISTORIAN_AGENTS.has(options.agent) && historianCalibrationEntryPath) {
3063
+ args.push("--extension", historianCalibrationEntryPath);
3064
+ }
3039
3065
  const strictTools = STRICT_TOOL_ALLOWLIST.get(options.agent);
3040
3066
  if (strictTools === undefined) {
3041
3067
  sessionLog(options.accountingSessionId ?? "pi-subagent", `Pi subagent agent "${options.agent}" has no strict tool allow-list; forcing --no-tools`);
@@ -3772,7 +3798,7 @@ async function sendIgnoredMessageNow(client, sessionId, text, params, forcePersi
3772
3798
  queueIgnoredNotification({ client, sessionId, text, params, forcePersist });
3773
3799
  return "queued";
3774
3800
  }
3775
- const { waitForSafeNotificationTarget } = await import("./safe-notification-target-q00qgjm6.js");
3801
+ const { waitForSafeNotificationTarget } = await import("./safe-notification-target-g8fx80va.js");
3776
3802
  if (await waitForSafeNotificationTarget(client, sessionId) === "skip") {
3777
3803
  sessionLog(sessionId, "notification skipped (session not titled yet)");
3778
3804
  return "skipped";
@@ -4380,13 +4406,17 @@ function peekLeaseHolderAndExpiry(db, expectedHolder, leaseKey = DREAMING_LEASE_
4380
4406
  function leaseOwnershipMatches(db, expectedHolder, expectedGeneration, leaseKey = DREAMING_LEASE_KEY) {
4381
4407
  return getLeaseGeneration(db, leaseKey) === expectedGeneration && peekLeaseHolderAndExpiry(db, expectedHolder, leaseKey);
4382
4408
  }
4383
- function runImmediate(db, body) {
4409
+ function runImmediate(db, body, site, slowWriteThresholdMs) {
4410
+ const transactionStartedAt = site === undefined ? undefined : performance.now();
4384
4411
  db.exec("BEGIN IMMEDIATE");
4385
4412
  let committed = false;
4386
4413
  try {
4387
4414
  const result = body();
4388
4415
  db.exec("COMMIT");
4389
4416
  committed = true;
4417
+ if (site !== undefined && transactionStartedAt !== undefined) {
4418
+ logSlowWriteTransaction(site, transactionStartedAt, slowWriteThresholdMs);
4419
+ }
4390
4420
  return result;
4391
4421
  } finally {
4392
4422
  if (!committed) {
@@ -4428,13 +4458,13 @@ function renewLease(db, holderId, leaseKey = DREAMING_LEASE_KEY, expectedGenerat
4428
4458
  return true;
4429
4459
  });
4430
4460
  }
4431
- function runLeaseGuardedWrite(db, holderId, leaseKey, fn) {
4461
+ function runLeaseGuardedWrite(db, holderId, leaseKey, fn, slowWriteThresholdMs) {
4432
4462
  return runImmediate(db, () => {
4433
4463
  if (!peekLeaseHolderAndExpiry(db, holderId, leaseKey)) {
4434
4464
  throw new Error("Dream lease lost before guarded write");
4435
4465
  }
4436
4466
  return fn();
4437
- });
4467
+ }, `lease-guarded-write:${leaseKey}`, slowWriteThresholdMs);
4438
4468
  }
4439
4469
  var LEASE_HEARTBEAT_INTERVAL_MS = 60 * 1000;
4440
4470
  function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisition = LEASE_HEARTBEAT_INTERVAL_MS) {
@@ -5966,7 +5996,7 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
5966
5996
  query: { directory: args.sessionDirectory },
5967
5997
  body: {
5968
5998
  agent: DREAMER_CLASSIFIER_AGENT,
5969
- system: CLASSIFY_SYSTEM_PROMPT,
5999
+ system: withContentLanguageDirective(CLASSIFY_SYSTEM_PROMPT, args.language),
5970
6000
  ...modelBodyField(args.model),
5971
6001
  parts: [{ type: "text", text: prompt, synthetic: true }]
5972
6002
  }
@@ -6029,6 +6059,8 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
6029
6059
  memories: chunk.map(toPromptMemory2),
6030
6060
  anchors
6031
6061
  });
6062
+ const modelChain = [args.model, ...args.fallbackModels ?? []].map(toModelEntry).filter((entry) => entry !== undefined).map((entry) => entry.model);
6063
+ const resolvedModelChain = [...new Set(modelChain)];
6032
6064
  const response = await args.moduleClient?.call({
6033
6065
  sessionId: args.moduleSessionId,
6034
6066
  projectRoot: args.moduleProjectRoot,
@@ -6040,6 +6072,7 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
6040
6072
  task: "classify",
6041
6073
  command_id: `classify:${args.moduleCommandId ?? Date.now()}:${createHash4("sha256").update(chunk.map((candidate) => candidate.id).join(",")).digest("hex").slice(0, 24)}`,
6042
6074
  authority_generation: args.moduleAuthorityGeneration,
6075
+ ...resolvedModelChain.length > 0 ? { model_chain: resolvedModelChain } : {},
6043
6076
  payload: {
6044
6077
  prompt_body: prompt,
6045
6078
  items: chunk.map((candidate) => ({
@@ -6713,7 +6746,7 @@ function getAsyncModule() {
6713
6746
  asyncModulePromise ??= (async () => {
6714
6747
  const [{ default: singlefileAsyncifyVariant }, { newQuickJSAsyncWASMModuleFromVariant }] = await Promise.all([
6715
6748
  import("./index-rgdqj8nj.js").then((m)=>__toESM(m.default,1)),
6716
- import("./index-8sseka65.js")
6749
+ import("./index-wnrygn5h.js")
6717
6750
  ]);
6718
6751
  return newQuickJSAsyncWASMModuleFromVariant(singlefileAsyncifyVariant);
6719
6752
  })();
@@ -7853,7 +7886,7 @@ Tools (read-only): read, grep, glob, aft_search, aft_outline, aft_zoom. Each mem
7853
7886
 
7854
7887
  For each memory decide ONE of:
7855
7888
  - Backing files found → the COMPLETE set of repo-relative paths whose code the memory is about.
7856
- - File-independent → the memory describes EXTERNAL behavior (a provider / API / platform / protocol limit, e.g. "Anthropic returns 400 on empty content"), or a pure process / workflow / philosophy rule, with NO specific local file that backs it.
7889
+ - File-independent → the memory describes EXTERNAL behavior (a provider / API / platform / protocol limit, e.g. "Anthropic returns 400 on empty content"), or a pure process / workflow / philosophy rule, with NO specific local file that backs it. A BEHAVIORAL claim (when to act, how to work, who decides, or tool-usage discipline) is file-independent even when it cites file paths or commands as examples: a named file is not the file backing the rule.
7857
7890
 
7858
7891
  Output ONE XML manifest at the very end and NOTHING else — no narration, no per-memory commentary, no reasoning:
7859
7892
  <mappings>
@@ -7864,7 +7897,7 @@ Output ONE XML manifest at the very end and NOTHING else — no narration, no pe
7864
7897
  Rules:
7865
7898
  - Every input memory id MUST appear exactly once.
7866
7899
  - files: repo-relative, comma-separated, no spaces inside a path. Only files that actually exist and genuinely back the memory.
7867
- - A BACKING FILE is CODE that implements or handles the claim — not a file that merely mentions it. A markdown doc (.md), a PARITY/notes file, or a test that only DESCRIBES an external fact is NOT a backing file. If the only place a memory's fact appears is prose/docs/a test (no code implements or handles it), mark it independent="true".
7900
+ - A BACKING FILE is CODE that implements or handles the claim — not a file that merely mentions it. A path named inside a process directive is an action target or example, not evidence that the file backs the directive. A markdown doc (.md), a PARITY/notes file, or a test that only DESCRIBES an external fact is NOT a backing file. If the only place a memory's fact appears is prose/docs/a test (no code implements or handles it), mark it independent="true".
7868
7901
  - Many CONSTRAINTS are HYBRID: "external system does X, and OUR code handles it here." Map those to the HANDLING code (you can verify the handling, even though you can't verify the external behavior). Only mark independent when there is NO local code that implements or handles the fact.
7869
7902
  - Prefer the most specific file(s); do not pad with tangential files. Most memories map to one file; some to a few.
7870
7903
  - When you genuinely cannot find any local backing and it is not clearly external, still emit the memory with independent="true" (do not drop it).`;
@@ -7902,7 +7935,7 @@ ${m.content}${seed}`;
7902
7935
 
7903
7936
  Project: ${projectPath}
7904
7937
 
7905
- For each memory below, find the repo file(s) it makes a claim about, or mark it file-independent. When "Likely files" are listed, those paths are named in the memory and confirmed to exist — START there: confirm each actually backs the claim (a quick read/outline), drop any that don't, add others only if genuinely needed. Search from scratch only when no likely files are given. Then output ONE <mappings> manifest covering every id.
7938
+ For each memory below, find the repo file(s) it makes a claim about, or mark it file-independent. Behavioral process/workflow directives stay file-independent even when they name files. When "Likely files" are listed, those paths are named in the memory and confirmed to exist — START there only for code claims: confirm each actually backs the claim (a quick read/outline), drop any that don't, add others only if genuinely needed. Search from scratch only when no likely files are given. Then output ONE <mappings> manifest covering every id.
7906
7939
 
7907
7940
  <memories>
7908
7941
  ${list}
@@ -7967,6 +8000,21 @@ function validateMapMemoriesManifest(text, expectedIds) {
7967
8000
  return parsed;
7968
8001
  }
7969
8002
 
8003
+ // ../plugin/src/features/magic-context/dreamer/memory-claim-safety.ts
8004
+ var POLICY_SENTENCE_START = /(?:^|[.!?]\s+|\n\s*)(?:[-*]\s+|\d+[.)]\s+)?(?:you\s+|we\s+)?(?:must(?:\s+not)?|never|always|do\s+not|don't|shall\s+not)\b/i;
8005
+ var ACTOR_POLICY = /\b(?:you|we|agents?|masons?|workers?|operators?|users?|maintainers?)\s+(?:must(?:\s+not)?|should(?:\s+not)?|need\s+to|shall(?:\s+not)?|cannot|can't|may\s+not)\b/i;
8006
+ var BEHAVIORAL_WHEN_CLAUSE = /\bwhen\s+(?:you(?:'re|\s+are)?|we(?:'re|\s+are)?|told|asked|requested|working|debugging|reviewing|checking|verifying|investigating|using|running|editing|changing)\b/i;
8007
+ var WORKFLOW_IMPERATIVE = /(?:^|[.!?]\s+|\n\s*)(?:[-*]\s+|\d+[.)]\s+)?(?:please\s+)?(?:run|use|check|ask|avoid|prefer|ensure|keep|follow|brief|report|inspect|search|open|read|review|validate|confirm|delegate|stop|start|remember)\b/i;
8008
+ var DECISION_AUTHORITY = /\b(?:the\s+)?(?:user|operator|maintainer|owner)\s+(?:decides|chooses|approves|has\s+(?:the\s+)?final\s+say)\b/i;
8009
+ function isDirectiveShapedProjectRule(category, content) {
8010
+ if (category !== "PROJECT_RULES")
8011
+ return false;
8012
+ const text = content.trim();
8013
+ if (!text)
8014
+ return false;
8015
+ return POLICY_SENTENCE_START.test(text) || ACTOR_POLICY.test(text) || BEHAVIORAL_WHEN_CLAUSE.test(text) || WORKFLOW_IMPERATIVE.test(text) || DECISION_AUTHORITY.test(text);
8016
+ }
8017
+
7970
8018
  // ../plugin/src/features/magic-context/dreamer/map-memories.ts
7971
8019
  var MAP_BATCH_SIZE = 80;
7972
8020
  var MAP_BATCH_FLOOR_MS = 240000;
@@ -7981,6 +8029,8 @@ function isTimeoutClassError2(error) {
7981
8029
  function shouldRequeueIndependentMapping(state2, content, repoDir) {
7982
8030
  if (!state2.hasSentinel || state2.files.length > 0)
7983
8031
  return false;
8032
+ if (state2.mappingOrigin === "host_rejected_fallback")
8033
+ return false;
7984
8034
  return extractMemoryCandidatePaths(content, repoDir).length > 0;
7985
8035
  }
7986
8036
  function toMapInput(memory, repoDir) {
@@ -8187,9 +8237,26 @@ async function applyParsedBatchMappings(args, batch, parsed) {
8187
8237
  if (valid.length === 0)
8188
8238
  return { mapped: 0, independent: 0 };
8189
8239
  const planned = [];
8240
+ const batchById = new Map(batch.map((memory) => [memory.id, memory]));
8190
8241
  for (const p of valid) {
8242
+ const memory = batchById.get(p.id);
8243
+ if (!p.independent && p.files.length > 0 && memory && isDirectiveShapedProjectRule(memory.category, memory.content)) {
8244
+ log(`[dreamer] map-memories safety override: memory_id=${p.id} verdict=file-mapping replacement=independent mapping_origin=host_rejected_fallback reason=directive-shaped-project-rule`);
8245
+ planned.push({
8246
+ id: p.id,
8247
+ files: [],
8248
+ independent: true,
8249
+ mappingOrigin: "host_rejected_fallback"
8250
+ });
8251
+ continue;
8252
+ }
8191
8253
  if (p.independent) {
8192
- planned.push({ id: p.id, files: [], independent: true });
8254
+ planned.push({
8255
+ id: p.id,
8256
+ files: [],
8257
+ independent: true,
8258
+ mappingOrigin: "mapper"
8259
+ });
8193
8260
  continue;
8194
8261
  }
8195
8262
  if (p.files.length === 0) {
@@ -8199,9 +8266,22 @@ async function applyParsedBatchMappings(args, batch, parsed) {
8199
8266
  cwd: args.sessionDirectory,
8200
8267
  files: p.files
8201
8268
  });
8202
- if (normalized.files.length === 0)
8269
+ if (normalized.files.length === 0) {
8270
+ log(`[dreamer] map-memories: all ${p.files.length} path(s) for memory ${p.id} were rejected; recording host_rejected_fallback`);
8271
+ planned.push({
8272
+ id: p.id,
8273
+ files: [],
8274
+ independent: true,
8275
+ mappingOrigin: "host_rejected_fallback"
8276
+ });
8203
8277
  continue;
8204
- planned.push({ id: p.id, files: normalized.files, independent: false });
8278
+ }
8279
+ planned.push({
8280
+ id: p.id,
8281
+ files: normalized.files,
8282
+ independent: false,
8283
+ mappingOrigin: "mapper"
8284
+ });
8205
8285
  }
8206
8286
  if (planned.length === 0)
8207
8287
  return { mapped: 0, independent: 0 };
@@ -8216,7 +8296,8 @@ async function applyParsedBatchMappings(args, batch, parsed) {
8216
8296
  return {
8217
8297
  memory_id: identity.moduleId,
8218
8298
  content_hash_at_prompt: identity.normalizedHash,
8219
- mapped_files: item.independent ? null : item.files
8299
+ mapped_files: item.independent ? null : item.files,
8300
+ mapping_origin: item.mappingOrigin
8220
8301
  };
8221
8302
  });
8222
8303
  let response;
@@ -8253,7 +8334,7 @@ async function applyParsedBatchMappings(args, batch, parsed) {
8253
8334
  const now = Date.now();
8254
8335
  runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
8255
8336
  for (const item of planned) {
8256
- recordMemoryMapping(args.db, item.id, item.files, now);
8337
+ recordMemoryMapping(args.db, item.id, item.files, now, item.mappingOrigin);
8257
8338
  item.independent ? independent += 1 : mapped += 1;
8258
8339
  }
8259
8340
  });
@@ -9414,24 +9495,29 @@ Each memory below comes with its backing file(s) — the code it makes a claim a
9414
9495
 
9415
9496
  Tools (read-only): read, grep, glob, aft_search, aft_outline, aft_zoom. You read code to check claims; you change nothing.
9416
9497
 
9417
- Decide ONE of three outcomes per memory:
9498
+ Decide ONE of four outcomes per memory:
9418
9499
  - VERIFIED — still accurate. Keep it as-is.
9419
- - UPDATE — the underlying fact is still true but a DETAIL drifted (a renamed symbol, moved file, changed number/name). Provide corrected content in terse present tense ("X uses Y", not "X was changed to Y"). Only update for genuine drift, not style.
9420
- - ARCHIVE — the code CLEARLY contradicts the memory, or the thing it describes no longer exists.
9500
+ - UPDATE — a CODE FACT is still true but a file-falsifiable DETAIL drifted (a renamed symbol, moved file, changed number/name). Provide corrected content in terse present tense ("X uses Y", not "X was changed to Y"). Only update for genuine drift, not style. If a legitimate update intentionally consolidates the old claim into much shorter content, set consolidation="true" explicitly.
9501
+ - ARCHIVE — a CODE FACT is positively falsified: the code clearly contradicts it, or the API/symbol/path it describes no longer exists.
9502
+ - SKIP — the claim is not a code fact, or code cannot decide it. Keep it unchanged and do not claim verification.
9421
9503
 
9422
- BE CONSERVATIVE ABOUT ARCHIVING. Wrong archival of a TRUE memory is the worst possible outcome — far worse than leaving a slightly-stale memory. If you cannot find the code, or you are unsure, or it might still be true somewhere you didn't look: mark it VERIFIED, never archived. Archive ONLY when you have positive evidence the code contradicts it.
9504
+ UPDATE and ARCHIVE are ONLY for claims a repository file can falsify, such as an API that no longer exists, a path that moved, or a constant that changed. Behavioral directives (when to act, how to work, who decides, tool-usage discipline) can only be VERIFIED or SKIPPED. A named path inside a directive does not make the directive a code fact, and a file's failure to corroborate a behavioral rule is never grounds to update or archive it.
9505
+
9506
+ BE CONSERVATIVE ABOUT ARCHIVING. Wrong archival of a TRUE memory is the worst possible outcome — far worse than leaving a slightly-stale memory. If you cannot find the code, or you are unsure, or it might still be true somewhere you didn't look: mark it VERIFIED or SKIPPED, never archived. Archive ONLY when you have positive evidence the code contradicts a code fact.
9423
9507
 
9424
9508
  Output ONE XML manifest at the very end and NOTHING else — no narration, no per-memory commentary, no reasoning:
9425
9509
  <verify>
9426
9510
  <verified id="N" files="path/a.ts,path/b.ts"/>
9427
9511
  <update id="M" files="path/c.ts">corrected present-tense content</update>
9512
+ <update id="C" files="path/d.ts" consolidation="true">intentionally consolidated content</update>
9428
9513
  <archive id="K" reason="specific evidence the code contradicts it"/>
9514
+ <skip id="S" reason="behavioral directive; code cannot decide it"/>
9429
9515
  </verify>
9430
9516
 
9431
9517
  Rules:
9432
- - Every input memory id MUST appear exactly once, in exactly one of verified/update/archive.
9518
+ - Every input memory id MUST appear exactly once, in exactly one of verified/update/archive/skip.
9433
9519
  - files = the COMPLETE current backing set (repo-relative, comma-separated). It may differ from the given mapping if a file moved — record what you actually verified against.
9434
- - Default to VERIFIED. update and archive are the exceptions, not the norm.`;
9520
+ - Default to VERIFIED. skip when code cannot decide the claim. update and archive are the exceptions, not the norm.`;
9435
9521
  function buildVerifyPrompt(projectPath, memories) {
9436
9522
  const list = memories.map((m) => `[${m.id}] ${m.category}
9437
9523
  Content: ${m.content}
@@ -9442,7 +9528,7 @@ Backing files: ${m.mappedFiles.join(", ")}`).join(`
9442
9528
 
9443
9529
  Project: ${projectPath}
9444
9530
 
9445
- Read each memory's backing files, decide verified / update / archive (default verified; be conservative about archiving), then output ONE <verify> manifest covering every id.
9531
+ Read each memory's backing files, decide verified / update / archive / skip (default verified; behavioral directives only permit verified or skip), then output ONE <verify> manifest covering every id.
9446
9532
 
9447
9533
  <memories>
9448
9534
  ${list}
@@ -9456,7 +9542,7 @@ function filesOf(s) {
9456
9542
  return (attrOf(s, "files") ?? "").split(",").map((f) => f.trim()).filter(Boolean);
9457
9543
  }
9458
9544
  function verifyIds(parsed) {
9459
- return [...parsed.verified, ...parsed.updated, ...parsed.archived].map((entry) => entry.id);
9545
+ return [...parsed.verified, ...parsed.updated, ...parsed.archived, ...parsed.skipped].map((entry) => entry.id);
9460
9546
  }
9461
9547
  function verifyBody(text) {
9462
9548
  try {
@@ -9469,7 +9555,7 @@ function verifyBody(text) {
9469
9555
  }
9470
9556
  }
9471
9557
  function parseVerifyManifest(text) {
9472
- const out = { verified: [], updated: [], archived: [] };
9558
+ const out = { verified: [], updated: [], archived: [], skipped: [] };
9473
9559
  const body = verifyBody(text);
9474
9560
  for (const m of body.matchAll(/<verified\b([^>]*)\/?>/g)) {
9475
9561
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
@@ -9481,7 +9567,12 @@ function parseVerifyManifest(text) {
9481
9567
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
9482
9568
  if (!Number.isInteger(id))
9483
9569
  throw new Error("verify manifest entry missing numeric id");
9484
- out.updated.push({ id, files: filesOf(m[1]), content: (m[2] ?? "").trim() });
9570
+ out.updated.push({
9571
+ id,
9572
+ files: filesOf(m[1]),
9573
+ content: (m[2] ?? "").trim(),
9574
+ consolidation: attrOf(m[1], "consolidation")?.toLowerCase() === "true"
9575
+ });
9485
9576
  }
9486
9577
  for (const m of body.matchAll(/<archive\b([^>]*)\/?>/g)) {
9487
9578
  const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
@@ -9489,6 +9580,12 @@ function parseVerifyManifest(text) {
9489
9580
  throw new Error("verify manifest entry missing numeric id");
9490
9581
  out.archived.push({ id, reason: attrOf(m[1], "reason") ?? "" });
9491
9582
  }
9583
+ for (const m of body.matchAll(/<skip\b([^>]*)\/?>/g)) {
9584
+ const id = Number.parseInt(attrOf(m[1], "id") ?? "", 10);
9585
+ if (!Number.isInteger(id))
9586
+ throw new Error("verify manifest entry missing numeric id");
9587
+ out.skipped.push({ id, reason: attrOf(m[1], "reason") ?? "" });
9588
+ }
9492
9589
  if (verifyIds(out).length === 0 && body.trim().length > 0) {
9493
9590
  throw new Error(describeUnrecognizedManifestShape(text, "verify", "verified"));
9494
9591
  }
@@ -9528,6 +9625,8 @@ async function runVerify(args) {
9528
9625
  verified: 0,
9529
9626
  updated: 0,
9530
9627
  archived: 0,
9628
+ skipped: 0,
9629
+ refused: 0,
9531
9630
  batches: 0,
9532
9631
  inScope: 0,
9533
9632
  remaining: 0,
@@ -9572,9 +9671,12 @@ async function runVerify(args) {
9572
9671
  result.verified += counts.verified;
9573
9672
  result.updated += counts.updated;
9574
9673
  result.archived += counts.archived;
9575
- result.remaining -= counts.verified + counts.updated + counts.archived;
9674
+ result.skipped += counts.skipped;
9675
+ result.refused += counts.refused;
9676
+ const batchProcessed = counts.verified + counts.updated + counts.archived + counts.skipped + counts.refused;
9677
+ result.remaining -= batchProcessed;
9576
9678
  result.batches += 1;
9577
- args.onProgress?.(result.verified + result.updated + result.archived);
9679
+ args.onProgress?.(result.verified + result.updated + result.archived + result.skipped + result.refused, result.refused);
9578
9680
  if (counts.providerFailure) {
9579
9681
  lastProviderFailure = counts.providerFailure;
9580
9682
  if (counts.providerFailure.fingerprint === priorProviderFailureFingerprint) {
@@ -9597,7 +9699,7 @@ async function runVerify(args) {
9597
9699
  result.complete = result.remaining === 0;
9598
9700
  if (result.complete)
9599
9701
  closeBroadCycle(args, gate.broadCycleStartAt);
9600
- log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"}: verified=${result.verified} updated=${result.updated} archived=${result.archived} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}`);
9702
+ log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"}: verified=${result.verified} updated=${result.updated} archived=${result.archived} skipped=${result.skipped} refused=${result.refused} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}`);
9601
9703
  return result;
9602
9704
  } finally {
9603
9705
  heartbeat.stop();
@@ -9666,7 +9768,14 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
9666
9768
  recordInvocation4(args, startedAt, { status: "failed", error });
9667
9769
  if (error instanceof DreamerModuleFailureError || signal.aborted)
9668
9770
  throw error;
9669
- return { verified: 0, updated: 0, archived: 0, providerFailure };
9771
+ return {
9772
+ verified: 0,
9773
+ updated: 0,
9774
+ archived: 0,
9775
+ skipped: 0,
9776
+ refused: 0,
9777
+ providerFailure
9778
+ };
9670
9779
  } finally {
9671
9780
  if (agentSessionId && !shouldKeepSubagents()) {
9672
9781
  await args.client.session.delete({
@@ -9680,35 +9789,59 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
9680
9789
  }
9681
9790
  async function applyParsedVerifyManifest(args, batch, parsed) {
9682
9791
  const batchIds = new Set(batch.map((m) => m.id));
9792
+ const batchById = new Map(batch.map((memory) => [memory.id, memory]));
9683
9793
  const valid = {
9684
9794
  verified: parsed.verified.filter((entry) => batchIds.has(entry.id)),
9685
9795
  updated: parsed.updated.filter((entry) => batchIds.has(entry.id)),
9686
- archived: parsed.archived.filter((entry) => batchIds.has(entry.id))
9796
+ archived: parsed.archived.filter((entry) => batchIds.has(entry.id)),
9797
+ skipped: parsed.skipped.filter((entry) => batchIds.has(entry.id))
9687
9798
  };
9688
- const unknown = [...parsed.verified, ...parsed.updated, ...parsed.archived].filter((entry) => !batchIds.has(entry.id));
9799
+ const unknown = [
9800
+ ...parsed.verified,
9801
+ ...parsed.updated,
9802
+ ...parsed.archived,
9803
+ ...parsed.skipped
9804
+ ].filter((entry) => !batchIds.has(entry.id));
9689
9805
  if (unknown.length > 0) {
9690
9806
  log(`[dreamer] verify warning: dropping ${unknown.length} unknown verification entr${unknown.length === 1 ? "y" : "ies"} outside the current batch (${unknown.map((entry) => entry.id).join(", ")})`);
9691
9807
  }
9692
- const validIds = [...valid.verified, ...valid.updated, ...valid.archived].map((entry) => entry.id);
9808
+ const validIds = [...valid.verified, ...valid.updated, ...valid.archived, ...valid.skipped].map((entry) => entry.id);
9693
9809
  assertNoDuplicateManifestIds(validIds, "verify");
9694
9810
  if (validIds.length * 2 < batch.length) {
9695
9811
  throw new Error(`verify manifest covers ${validIds.length}/${batch.length} batch ids after filtering unknown entries; rejecting mostly-wrong manifest`);
9696
9812
  }
9697
- if (validIds.length === 0)
9698
- return { verified: 0, updated: 0, archived: 0 };
9813
+ if (validIds.length === 0) {
9814
+ return { verified: 0, updated: 0, archived: 0, skipped: 0, refused: 0 };
9815
+ }
9699
9816
  const now = Date.now();
9700
9817
  const writes = [];
9818
+ let refused = 0;
9819
+ const skipped = valid.skipped.length;
9701
9820
  for (const v of valid.verified) {
9702
9821
  const files = await normalizeFiles(args, v.files);
9703
9822
  writes.push({ kind: "verify", id: v.id, files });
9704
9823
  }
9705
9824
  for (const u of valid.updated) {
9825
+ const original = batchById.get(u.id);
9826
+ if (!original)
9827
+ continue;
9828
+ if (isDirectiveShapedProjectRule(original.category, original.content)) {
9829
+ log(`[dreamer] verify safety refusal: memory_id=${u.id} verdict=update reason=directive-shaped-project-rule`);
9830
+ refused += 1;
9831
+ continue;
9832
+ }
9706
9833
  const content = u.content.trim();
9707
9834
  if (!content || content.length > 20000) {
9708
9835
  const files2 = await normalizeFiles(args, u.files);
9709
9836
  writes.push({ kind: "verify", id: u.id, files: files2 });
9710
9837
  continue;
9711
9838
  }
9839
+ const originalLength = original.content.trim().length;
9840
+ if (!u.consolidation && content.length * 2 < originalLength) {
9841
+ log(`[dreamer] verify safety refusal: memory_id=${u.id} verdict=update reason=content-loss original_chars=${originalLength} replacement_chars=${content.length}`);
9842
+ refused += 1;
9843
+ continue;
9844
+ }
9712
9845
  const files = await normalizeFiles(args, u.files);
9713
9846
  writes.push({
9714
9847
  kind: "update",
@@ -9719,13 +9852,21 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
9719
9852
  });
9720
9853
  }
9721
9854
  for (const a of valid.archived) {
9855
+ const original = batchById.get(a.id);
9856
+ if (!original)
9857
+ continue;
9858
+ if (isDirectiveShapedProjectRule(original.category, original.content)) {
9859
+ log(`[dreamer] verify safety refusal: memory_id=${a.id} verdict=archive reason=directive-shaped-project-rule`);
9860
+ refused += 1;
9861
+ continue;
9862
+ }
9722
9863
  writes.push({ kind: "archive", id: a.id, reason: a.reason });
9723
9864
  }
9724
- if (writes.length === 0)
9725
- return { verified: 0, updated: 0, archived: 0 };
9726
9865
  let verified = 0;
9727
9866
  let updated = 0;
9728
9867
  let archived = 0;
9868
+ if (writes.length === 0)
9869
+ return { verified, updated, archived, skipped, refused };
9729
9870
  if (args.moduleRoute) {
9730
9871
  const identities = getModuleMemoryIdentities(args.db, args.projectIdentity, writes.map((write) => write.id));
9731
9872
  const rows = writes.map((write) => {
@@ -9775,7 +9916,7 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
9775
9916
  else
9776
9917
  archived += 1;
9777
9918
  }
9778
- return { verified, updated, archived };
9919
+ return { verified, updated, archived, skipped, refused };
9779
9920
  }
9780
9921
  runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
9781
9922
  for (const w of writes) {
@@ -9806,7 +9947,7 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
9806
9947
  }
9807
9948
  }
9808
9949
  });
9809
- return { verified, updated, archived };
9950
+ return { verified, updated, archived, skipped, refused };
9810
9951
  }
9811
9952
  async function normalizeFiles(args, rawFiles) {
9812
9953
  if (rawFiles.length === 0)
@@ -9919,12 +10060,13 @@ function createDreamTaskExecutor(deps) {
9919
10060
  })();
9920
10061
  const deadline = startedAt + config.timeoutMinutes * 60 * 1000;
9921
10062
  const backlogAtStart = getDreamTaskBacklog(db, projectIdentity, config.task);
9922
- const reportProgress = (processed) => {
10063
+ const reportProgress = (processed, refused) => {
9923
10064
  deps.onProgress?.({
9924
10065
  task: config.task,
9925
10066
  processed: Math.max(0, processed),
9926
10067
  total: backlogAtStart.pending,
9927
- startedAt
10068
+ startedAt,
10069
+ ...refused === undefined ? {} : { refused: Math.max(0, refused) }
9928
10070
  });
9929
10071
  };
9930
10072
  reportProgress(0);
@@ -10111,10 +10253,11 @@ function createDreamTaskExecutor(deps) {
10111
10253
  fallbackModels: config.fallbackModels,
10112
10254
  language: config.language ?? deps.language,
10113
10255
  moduleRoute,
10114
- onProgress: (processed2) => reportProgress(processed2)
10256
+ onProgress: (processed2, refused) => reportProgress(processed2, refused)
10115
10257
  });
10116
- const processed = result.verified + result.updated + result.archived;
10117
- const broadProgress = config.task === "verify-broad" ? `verify-broad cycle ${result.broadCycleStartAt ?? "open"}: verified ${processed}, ${result.remaining} remain` : null;
10258
+ const processed = result.verified + result.updated + result.archived + result.skipped + result.refused;
10259
+ const verificationProgress = `${config.task}${config.task === "verify-broad" ? ` cycle ${result.broadCycleStartAt ?? "open"}` : ""}: processed ${processed} (verified ${result.verified}, updated ${result.updated}, archived ${result.archived}, skipped ${result.skipped}, refused ${result.refused}); ${result.remaining} remain`;
10260
+ const broadProgress = config.task === "verify-broad" ? verificationProgress : null;
10118
10261
  const backlogAfter = config.task === "verify-broad" ? { pending: result.remaining, total: backlogAtStart.total } : undefined;
10119
10262
  if (!result.complete) {
10120
10263
  if (broadProgress && processed > 0) {
@@ -10127,13 +10270,14 @@ function createDreamTaskExecutor(deps) {
10127
10270
  }
10128
10271
  const error = incompleteMessage(result.remaining);
10129
10272
  recordRun("failed", error, {
10273
+ progress: verificationProgress,
10130
10274
  memoryChanges: computeMemoryDelta(memoryBefore),
10131
10275
  backlogAfter
10132
10276
  });
10133
10277
  return { status: "failed", transient: true, error };
10134
10278
  }
10135
10279
  recordRun("completed", null, {
10136
- progress: broadProgress,
10280
+ progress: verificationProgress,
10137
10281
  memoryChanges: computeMemoryDelta(memoryBefore),
10138
10282
  backlogAfter
10139
10283
  });
@@ -10163,6 +10307,7 @@ function createDreamTaskExecutor(deps) {
10163
10307
  leaseAcquisition,
10164
10308
  model: config.model,
10165
10309
  fallbackModels: config.fallbackModels,
10310
+ language: config.language ?? deps.language,
10166
10311
  ...moduleArgs,
10167
10312
  onProgress: (processed) => reportProgress(processed)
10168
10313
  });
@@ -11014,6 +11159,7 @@ var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
11014
11159
  var GIT_COMMIT_BACKLOG_DRAIN_MAX_MS = 5 * 60 * 1000;
11015
11160
  var BOOT_PROJECT_JITTER_SLOT_MS = 1000;
11016
11161
  var activeTimer = null;
11162
+ var startupTickTimer = null;
11017
11163
  var startupTimers = new Map;
11018
11164
  var startupJitters = new Map;
11019
11165
  var nextStartupJitterSlot = 0;
@@ -11039,6 +11185,23 @@ function openTimerDatabaseOrNull(context) {
11039
11185
  return db;
11040
11186
  }
11041
11187
  var registeredProjects = new Map;
11188
+ function stopDreamScheduleTimerIfIdle() {
11189
+ if (registeredProjects.size !== 0)
11190
+ return;
11191
+ const stopped = activeTimer !== null || startupTickTimer !== null;
11192
+ if (activeTimer) {
11193
+ clearInterval(activeTimer);
11194
+ activeTimer = null;
11195
+ }
11196
+ if (startupTickTimer) {
11197
+ clearTimeout(startupTickTimer);
11198
+ startupTickTimer = null;
11199
+ }
11200
+ startupJitters.clear();
11201
+ nextStartupJitterSlot = 0;
11202
+ if (stopped)
11203
+ log("[dreamer] stopped dream schedule timer (no projects left)");
11204
+ }
11042
11205
  async function startDreamScheduleTimer(args) {
11043
11206
  beginBootQuietPeriod();
11044
11207
  const db = openTimerDatabaseOrNull("schedule timer registration");
@@ -11062,7 +11225,10 @@ async function startDreamScheduleTimer(args) {
11062
11225
  }
11063
11226
  if (!activeTimer) {
11064
11227
  log(`[dreamer] started independent schedule timer (every ${DREAM_TIMER_INTERVAL_MS / 60000}m)`);
11065
- scheduleAfterBootQuiet(() => runTick("startup"));
11228
+ startupTickTimer = scheduleAfterBootQuiet(() => {
11229
+ startupTickTimer = null;
11230
+ runTick("startup");
11231
+ });
11066
11232
  const timer = setInterval(() => runTick("interval"), DREAM_TIMER_INTERVAL_MS);
11067
11233
  if (typeof timer === "object" && "unref" in timer) {
11068
11234
  timer.unref();
@@ -11072,6 +11238,10 @@ async function startDreamScheduleTimer(args) {
11072
11238
  scheduleInitialProjectRun(args, db);
11073
11239
  }
11074
11240
  return () => {
11241
+ if (registeredProjects.get(args.directory) !== args) {
11242
+ stopDreamScheduleTimerIfIdle();
11243
+ return;
11244
+ }
11075
11245
  registeredProjects.delete(args.directory);
11076
11246
  const startupTimer = startupTimers.get(args.directory);
11077
11247
  if (startupTimer) {
@@ -11079,13 +11249,7 @@ async function startDreamScheduleTimer(args) {
11079
11249
  startupTimers.delete(args.directory);
11080
11250
  }
11081
11251
  log(`[dreamer] unregistered project ${args.projectIdentity} (remaining=${registeredProjects.size})`);
11082
- if (registeredProjects.size === 0 && activeTimer) {
11083
- clearInterval(activeTimer);
11084
- activeTimer = null;
11085
- startupJitters.clear();
11086
- nextStartupJitterSlot = 0;
11087
- log("[dreamer] stopped dream schedule timer (no projects left)");
11088
- }
11252
+ stopDreamScheduleTimerIfIdle();
11089
11253
  };
11090
11254
  }
11091
11255
  function runTick(origin) {
@@ -11170,7 +11334,15 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
11170
11334
  log(`[dreamer] orphan schedule GC failed for ${reg.projectIdentity}:`, error);
11171
11335
  }
11172
11336
  }
11173
- registeredProjects.delete(reg.directory);
11337
+ if (registeredProjects.get(reg.directory) === reg) {
11338
+ registeredProjects.delete(reg.directory);
11339
+ const startupTimer = startupTimers.get(reg.directory);
11340
+ if (startupTimer) {
11341
+ clearTimeout(startupTimer);
11342
+ startupTimers.delete(reg.directory);
11343
+ }
11344
+ }
11345
+ stopDreamScheduleTimerIfIdle();
11174
11346
  return;
11175
11347
  }
11176
11348
  await reg.ensureRegistered(reg.directory, db);
@@ -11350,25 +11522,145 @@ async function sweepGitCommits(args) {
11350
11522
  }
11351
11523
 
11352
11524
  // src/dreamer/pi-session-api.ts
11353
- import { readFileSync as readFileSync4 } from "node:fs";
11354
- import { createRequire as createRequire2 } from "node:module";
11525
+ import { existsSync as existsSync8, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync3 } from "node:fs";
11526
+ import {
11527
+ basename as basename2,
11528
+ dirname as dirname2,
11529
+ extname,
11530
+ join as join4,
11531
+ relative,
11532
+ resolve,
11533
+ sep
11534
+ } from "node:path";
11355
11535
  import { pathToFileURL } from "node:url";
11356
11536
  var PI_CODING_AGENT_MODULE = "@earendil-works/pi-coding-agent";
11537
+ var SCRIPT_ENTRY_PATTERN = /\.(mjs|cjs|mts|cts|js|ts|tsx|jsx)$/i;
11538
+ var TS_ENTRY_PATTERN = /\.(mts|cts|ts|tsx)$/i;
11539
+ function isScriptEntry(filePath) {
11540
+ return SCRIPT_ENTRY_PATTERN.test(filePath) || extname(filePath) === "";
11541
+ }
11542
+ function readManifest(pkgJsonPath) {
11543
+ try {
11544
+ const pkg = JSON.parse(readFileSync4(pkgJsonPath, "utf8"));
11545
+ return pkg && typeof pkg === "object" ? pkg : null;
11546
+ } catch {
11547
+ return null;
11548
+ }
11549
+ }
11550
+ function isCompiledBunBinary() {
11551
+ if (!process.versions.bun) {
11552
+ return false;
11553
+ }
11554
+ const exe = basename2(process.execPath).toLowerCase();
11555
+ return exe !== "bun" && exe !== "bun.exe";
11556
+ }
11557
+ function resolveRunningEntry() {
11558
+ const argv1 = process.argv[1];
11559
+ if (argv1 && existsSync8(argv1) && statSync3(argv1).isFile()) {
11560
+ const real = realpathSync2(argv1);
11561
+ if (isScriptEntry(real)) {
11562
+ return real;
11563
+ }
11564
+ }
11565
+ return null;
11566
+ }
11567
+ function findPackageRoot(startDir) {
11568
+ let dir = startDir;
11569
+ while (dir !== dirname2(dir)) {
11570
+ const pkg = readManifest(join4(dir, "package.json"));
11571
+ if (pkg?.name === PI_CODING_AGENT_MODULE) {
11572
+ if (basename2(dir) === "dist") {
11573
+ const parentDir = dirname2(dir);
11574
+ const parentPkg = readManifest(join4(parentDir, "package.json"));
11575
+ if (parentPkg?.name === PI_CODING_AGENT_MODULE) {
11576
+ return { dir: parentDir, pkg: parentPkg };
11577
+ }
11578
+ }
11579
+ return { dir, pkg };
11580
+ }
11581
+ dir = dirname2(dir);
11582
+ }
11583
+ return null;
11584
+ }
11585
+ function resolveExportsTarget(target) {
11586
+ if (typeof target === "string") {
11587
+ return target;
11588
+ }
11589
+ if (Array.isArray(target)) {
11590
+ for (const item of target) {
11591
+ const resolved = resolveExportsTarget(item);
11592
+ if (resolved !== undefined) {
11593
+ return resolved;
11594
+ }
11595
+ }
11596
+ return;
11597
+ }
11598
+ if (target && typeof target === "object") {
11599
+ const conditions = target;
11600
+ for (const condition of ["import", "node", "default"]) {
11601
+ if (condition in conditions) {
11602
+ const resolved = resolveExportsTarget(conditions[condition]);
11603
+ if (resolved !== undefined) {
11604
+ return resolved;
11605
+ }
11606
+ }
11607
+ }
11608
+ }
11609
+ return;
11610
+ }
11611
+ function resolveManifestEntry(found) {
11612
+ const pkg = found.pkg;
11613
+ const entryRel = resolveExportsTarget(pkg.exports?.["."]) ?? (typeof pkg.module === "string" ? pkg.module : undefined) ?? (typeof pkg.main === "string" ? pkg.main : undefined);
11614
+ if (!entryRel) {
11615
+ throw new Error(`No ESM entry found in ${found.dir}/package.json`);
11616
+ }
11617
+ if (!entryRel.startsWith("./")) {
11618
+ throw new Error(`Invalid entry "${entryRel}" in ${found.dir}/package.json: targets must start with "./"`);
11619
+ }
11620
+ const root = resolve(found.dir);
11621
+ const resolved = resolve(root, entryRel);
11622
+ if (resolved !== root && !resolved.startsWith(root + sep)) {
11623
+ throw new Error(`Invalid entry "${entryRel}" in ${found.dir}/package.json: target escapes the package root`);
11624
+ }
11625
+ return resolved;
11626
+ }
11627
+ function toSourceEntry(entryPath, pkgDir) {
11628
+ const rel = relative(pkgDir, entryPath);
11629
+ const srcRel = rel.replace(/^dist[/\\]/, "src/").replace(/\.mjs$/, ".mts").replace(/\.cjs$/, ".cts").replace(/\.jsx$/, ".tsx").replace(/\.js$/, ".ts");
11630
+ if (srcRel === rel) {
11631
+ return null;
11632
+ }
11633
+ return join4(pkgDir, srcRel);
11634
+ }
11357
11635
  var defaultLoaders = [
11358
- {
11359
- name: "Bare import",
11360
- load: async () => await import(PI_CODING_AGENT_MODULE)
11361
- },
11362
11636
  {
11363
11637
  name: "Resolve from running Pi binary entry",
11364
11638
  load: async () => {
11365
- if (!process.argv[1]) {
11366
- throw new Error("process.argv[1] is undefined");
11639
+ if (isCompiledBunBinary()) {
11640
+ throw new Error("Running inside a compiled Bun binary: pi-coding-agent is embedded in the executable and argv[1] is a user CLI argument, so there is no on-disk package entry to resolve");
11641
+ }
11642
+ const entry = resolveRunningEntry() ?? process.execPath;
11643
+ if (!entry) {
11644
+ throw new Error("Neither process.argv[1] nor process.execPath is available");
11645
+ }
11646
+ const found = findPackageRoot(dirname2(entry));
11647
+ if (!found) {
11648
+ throw new Error(`Could not locate ${PI_CODING_AGENT_MODULE} package.json from ${entry}`);
11649
+ }
11650
+ const entryPath = resolveManifestEntry(found);
11651
+ if (TS_ENTRY_PATTERN.test(entry)) {
11652
+ const srcEntry = toSourceEntry(entryPath, found.dir);
11653
+ if (srcEntry && existsSync8(srcEntry)) {
11654
+ return await import(pathToFileURL(srcEntry).href);
11655
+ }
11656
+ throw new Error(`Pi is running from TypeScript source (${entry}) but no source counterpart of ${entryPath} exists; refusing to load possibly stale build output`);
11367
11657
  }
11368
- const require2 = createRequire2(process.argv[1]);
11369
- const resolved = require2.resolve(PI_CODING_AGENT_MODULE);
11370
- return await import(pathToFileURL(resolved).href);
11658
+ return await import(pathToFileURL(entryPath).href);
11371
11659
  }
11660
+ },
11661
+ {
11662
+ name: "Bare import",
11663
+ load: async () => await import(PI_CODING_AGENT_MODULE)
11372
11664
  }
11373
11665
  ];
11374
11666
  var cachedModulePromise = null;
@@ -11454,7 +11746,7 @@ function createPiPrimerRawProviderFactory(deps = {}) {
11454
11746
  }
11455
11747
 
11456
11748
  // src/dreamer/retrospective-raw-provider-pi.ts
11457
- import { resolve } from "node:path";
11749
+ import { resolve as resolve2 } from "node:path";
11458
11750
  class PiRetrospectiveRawProvider {
11459
11751
  deps;
11460
11752
  sessionPathById = new Map;
@@ -11465,7 +11757,7 @@ class PiRetrospectiveRawProvider {
11465
11757
  async listProjectSessions(_projectIdentity) {
11466
11758
  const deps = await this.resolveDeps();
11467
11759
  const sessions = await deps.listSessions(this.deps.sessionDir);
11468
- const projectCwd = resolve(this.deps.projectCwd);
11760
+ const projectCwd = resolve2(this.deps.projectCwd);
11469
11761
  const result = [];
11470
11762
  this.sessionPathById.clear();
11471
11763
  for (const raw of sessions) {
@@ -11474,7 +11766,7 @@ class PiRetrospectiveRawProvider {
11474
11766
  continue;
11475
11767
  if (typeof info.id !== "string" || typeof info.path !== "string")
11476
11768
  continue;
11477
- if (typeof info.cwd !== "string" || resolve(info.cwd) !== projectCwd)
11769
+ if (typeof info.cwd !== "string" || resolve2(info.cwd) !== projectCwd)
11478
11770
  continue;
11479
11771
  this.sessionPathById.set(info.id, info.path);
11480
11772
  result.push({
@@ -11568,9 +11860,30 @@ function extractPiTextContent(content) {
11568
11860
  }
11569
11861
 
11570
11862
  // src/dreamer/index.ts
11571
- var registeredProjects2 = new Map;
11863
+ var PI_DREAMER_PROJECTS = Symbol.for("magic-context.pi.dreamer-registered-projects");
11864
+ function getRegisteredProjects() {
11865
+ const globals = globalThis;
11866
+ const existing = globals[PI_DREAMER_PROJECTS];
11867
+ if (existing instanceof Map) {
11868
+ return existing;
11869
+ }
11870
+ const projects = new Map;
11871
+ globals[PI_DREAMER_PROJECTS] = projects;
11872
+ return projects;
11873
+ }
11874
+ var registeredProjects2 = getRegisteredProjects();
11572
11875
  var sessionsById = new Map;
11573
- var inFlightDreams = new Set;
11876
+ var PI_DREAMER_IN_FLIGHT = Symbol.for("magic-context.pi.dreamer-in-flight");
11877
+ var inFlightDreams = (() => {
11878
+ const globals = globalThis;
11879
+ const existing = globals[PI_DREAMER_IN_FLIGHT];
11880
+ if (existing instanceof Map) {
11881
+ return existing;
11882
+ }
11883
+ const dreams = new Map;
11884
+ globals[PI_DREAMER_IN_FLIGHT] = dreams;
11885
+ return dreams;
11886
+ })();
11574
11887
  var sessionCounter = 0;
11575
11888
  var piSubagentRunnerFactory = () => new PiSubagentRunner;
11576
11889
  var startDreamScheduleTimerFn = startDreamScheduleTimer;
@@ -11579,14 +11892,26 @@ function registerPiDreamerProject(opts) {
11579
11892
  return;
11580
11893
  }
11581
11894
  const existing = registeredProjects2.get(opts.projectIdentity);
11895
+ const owners = existing?.owners ?? new Map;
11896
+ owners.delete(opts.registrationOwner);
11897
+ owners.set(opts.registrationOwner, opts);
11898
+ const notifyOwnersOfAdjunctRefresh = (projectIdentity) => {
11899
+ const callbacks = new Set([...owners.values()].map((owner) => owner.onAdjunctsRefreshNeeded).filter((callback) => callback !== undefined));
11900
+ for (const callback of callbacks)
11901
+ callback(projectIdentity);
11902
+ };
11582
11903
  if (existing) {
11583
- if (existing.projectDir === opts.projectDir) {
11904
+ if (existing.generation && existing.projectDir === opts.projectDir) {
11584
11905
  return;
11585
11906
  }
11586
11907
  existing.cleanup();
11587
11908
  registeredProjects2.delete(opts.projectIdentity);
11588
11909
  }
11589
- const client = createPiDreamerClient(opts);
11910
+ const generation = {};
11911
+ const client = createPiDreamerClient(opts, notifyOwnersOfAdjunctRefresh, () => {
11912
+ const current = registeredProjects2.get(opts.projectIdentity);
11913
+ return current?.generation === generation && current.owners.get(opts.registrationOwner)?.projectDir === opts.projectDir;
11914
+ });
11590
11915
  let cleanup;
11591
11916
  let cancelled = false;
11592
11917
  startDreamScheduleTimerFn({
@@ -11604,32 +11929,50 @@ function registerPiDreamerProject(opts) {
11604
11929
  primerRawProviderFactory: createPiPrimerRawProviderFactory()
11605
11930
  }).then((timerCleanup) => {
11606
11931
  if (cancelled) {
11607
- timerCleanup?.();
11932
+ if (registeredProjects2.get(opts.projectIdentity)?.generation !== generation) {
11933
+ timerCleanup?.();
11934
+ }
11608
11935
  return;
11609
11936
  }
11610
11937
  cleanup = timerCleanup;
11611
11938
  });
11612
- const runManual = async (task) => runManualDream({
11613
- db: opts.db,
11614
- projectIdentity: opts.projectIdentity,
11615
- tasks: buildDreamTaskRuntimeConfigs(opts.config, "pi", opts.language, opts.mural?.model),
11616
- executor: createDreamTaskExecutor({
11617
- client,
11618
- sessionDirectory: opts.projectDir,
11619
- openOpenCodeDb,
11620
- retrospectiveRawProvider: new PiRetrospectiveRawProvider({
11621
- projectCwd: opts.projectDir
11939
+ const runManual = async (task, registrationOwner) => {
11940
+ const manualOpts = owners.get(registrationOwner);
11941
+ if (!manualOpts) {
11942
+ throw new Error(`Pi dreamer registration owner is no longer active for project ${opts.projectIdentity}`);
11943
+ }
11944
+ const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir);
11945
+ const manualRun = runManualDream({
11946
+ db: manualOpts.db,
11947
+ projectIdentity: manualOpts.projectIdentity,
11948
+ tasks: buildDreamTaskRuntimeConfigs(manualOpts.config, "pi", manualOpts.language, manualOpts.mural?.model),
11949
+ executor: createDreamTaskExecutor({
11950
+ client: manualClient,
11951
+ sessionDirectory: manualOpts.projectDir,
11952
+ openOpenCodeDb,
11953
+ retrospectiveRawProvider: new PiRetrospectiveRawProvider({
11954
+ projectCwd: manualOpts.projectDir
11955
+ }),
11956
+ primerRawProviderFactory: createPiPrimerRawProviderFactory(),
11957
+ userMemoryCollectionEnabled: userMemoryCollectionEnabled(manualOpts.config),
11958
+ ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
11959
+ language: manualOpts.language,
11960
+ retinaHandoff: manualOpts.retinaHandoff,
11961
+ mural: manualOpts.mural
11622
11962
  }),
11623
- primerRawProviderFactory: createPiPrimerRawProviderFactory(),
11624
- userMemoryCollectionEnabled: userMemoryCollectionEnabled(opts.config),
11625
- ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
11626
- language: opts.language,
11627
- retinaHandoff: opts.retinaHandoff,
11628
- mural: opts.mural
11629
- }),
11630
- task
11631
- });
11963
+ task
11964
+ });
11965
+ inFlightDreams.set(manualRun, manualOpts.registrationOwner);
11966
+ try {
11967
+ return await manualRun;
11968
+ } finally {
11969
+ inFlightDreams.delete(manualRun);
11970
+ }
11971
+ };
11632
11972
  registeredProjects2.set(opts.projectIdentity, {
11973
+ generation,
11974
+ activeOwner: opts.registrationOwner,
11975
+ owners,
11633
11976
  cleanup: () => {
11634
11977
  cancelled = true;
11635
11978
  cleanup?.();
@@ -11638,31 +11981,55 @@ function registerPiDreamerProject(opts) {
11638
11981
  projectDir: opts.projectDir
11639
11982
  });
11640
11983
  }
11641
- async function runPiDreamForProject(projectIdentity, task) {
11984
+ async function runPiDreamForProject(projectIdentity, task, registrationOwner) {
11642
11985
  const registration = registeredProjects2.get(projectIdentity);
11643
11986
  if (!registration) {
11644
11987
  throw new Error(`Pi dreamer not registered for project ${projectIdentity}; call registerPiDreamerProject() first`);
11645
11988
  }
11646
- return registration.runManual(task);
11989
+ return registration.runManual(task, registrationOwner);
11647
11990
  }
11648
11991
  function unregisterPiDreamerProject(opts) {
11649
11992
  const registration = registeredProjects2.get(opts.projectIdentity);
11650
- if (!registration) {
11993
+ if (!registration?.owners.delete(opts.registrationOwner)) {
11994
+ return;
11995
+ }
11996
+ if (registration.owners.size > 0) {
11997
+ if (registration.activeOwner !== opts.registrationOwner)
11998
+ return;
11999
+ const remaining = [...registration.owners.values()];
12000
+ const replacementOptions = remaining[remaining.length - 1];
12001
+ if (!replacementOptions)
12002
+ return;
12003
+ registration.cleanup();
12004
+ registeredProjects2.delete(opts.projectIdentity);
12005
+ registerPiDreamerProject(replacementOptions);
12006
+ const replacement = registeredProjects2.get(opts.projectIdentity);
12007
+ if (replacement) {
12008
+ for (const remainingOptions of remaining) {
12009
+ replacement.owners.set(remainingOptions.registrationOwner, remainingOptions);
12010
+ }
12011
+ }
11651
12012
  return;
11652
12013
  }
11653
12014
  registration.cleanup();
11654
12015
  registeredProjects2.delete(opts.projectIdentity);
11655
12016
  }
11656
- async function awaitInFlightDreamers() {
11657
- if (inFlightDreams.size === 0) {
12017
+ async function awaitInFlightDreamers(registrationOwner) {
12018
+ const runs = registrationOwner === undefined ? [...inFlightDreams.keys()] : [...inFlightDreams.entries()].filter(([, owner]) => owner === registrationOwner).map(([run]) => run);
12019
+ if (runs.length === 0)
11658
12020
  return;
11659
- }
11660
- await Promise.allSettled(Array.from(inFlightDreams));
12021
+ await Promise.allSettled(runs);
11661
12022
  }
11662
- function createPiDreamerClient(opts) {
12023
+ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true) {
11663
12024
  const runner = piSubagentRunnerFactory();
12025
+ const assertRegistrationOwnerActive = () => {
12026
+ if (!isRegistrationOwnerActive()) {
12027
+ throw new Error(`Pi dreamer registration is no longer active for project ${opts.projectIdentity}`);
12028
+ }
12029
+ };
11664
12030
  const session = {
11665
12031
  create: async (args) => {
12032
+ assertRegistrationOwnerActive();
11666
12033
  const sessionId = `magic-context-pi-dream-${++sessionCounter}`;
11667
12034
  sessionsById.set(sessionId, {
11668
12035
  id: sessionId,
@@ -11679,6 +12046,7 @@ function createPiDreamerClient(opts) {
11679
12046
  if (!dreamSession) {
11680
12047
  throw new Error(`Pi dreamer session not found: ${sessionId}`);
11681
12048
  }
12049
+ assertRegistrationOwnerActive();
11682
12050
  const userMessage = extractUserMessage(args);
11683
12051
  const systemPrompt = extractSystemPrompt(args);
11684
12052
  const perTaskModel = extractBodyModel(args);
@@ -11694,9 +12062,10 @@ function createPiDreamerClient(opts) {
11694
12062
  signal: args.signal ?? undefined,
11695
12063
  thinkingLevel: extractBodyVariant(args)
11696
12064
  });
11697
- inFlightDreams.add(runPromise);
12065
+ inFlightDreams.set(runPromise, opts.registrationOwner);
11698
12066
  try {
11699
12067
  const result = await runPromise;
12068
+ assertRegistrationOwnerActive();
11700
12069
  if (!result.ok) {
11701
12070
  const error = new Error(`Pi dreamer subagent failed (${result.reason}): ${result.error}`);
11702
12071
  if (result.transient) {
@@ -11711,12 +12080,13 @@ function createPiDreamerClient(opts) {
11711
12080
  { type: "text", text: result.assistantText }
11712
12081
  ])
11713
12082
  ];
11714
- opts.onAdjunctsRefreshNeeded?.(opts.projectIdentity);
12083
+ onAdjunctsRefreshNeeded?.(opts.projectIdentity);
11715
12084
  } finally {
11716
12085
  inFlightDreams.delete(runPromise);
11717
12086
  }
11718
12087
  },
11719
12088
  messages: async (args) => {
12089
+ assertRegistrationOwnerActive();
11720
12090
  const dreamSession = sessionsById.get(args.path.id);
11721
12091
  return { data: dreamSession?.messages ?? [] };
11722
12092
  },
@@ -11816,8 +12186,15 @@ function makeMessage(role, parts) {
11816
12186
  }
11817
12187
 
11818
12188
  // src/commands/pi-command-utils.ts
11819
- import { Box, Text } from "@earendil-works/pi-tui";
12189
+ import { Box, matchesKey, Text } from "@earendil-works/pi-tui";
11820
12190
  var CTX_STATUS_CUSTOM_TYPE = "ctx-status";
12191
+ var statusLifecycleSignals = new WeakMap;
12192
+ function registerCtxStatusLifecycleSignal(pi, signal) {
12193
+ statusLifecycleSignals.set(pi, signal);
12194
+ }
12195
+ function shouldShowCtxStatusDialog(content) {
12196
+ return content.rpcDisplay === "dialog" || content.rpcDisplay !== "notification" && content.level !== undefined && content.level !== "info";
12197
+ }
11821
12198
  function resolveSessionId(ctx) {
11822
12199
  const sm = ctx.sessionManager;
11823
12200
  const getSessionId = sm.getSessionId;
@@ -11830,6 +12207,41 @@ function resolveSessionId(ctx) {
11830
12207
  return;
11831
12208
  }
11832
12209
  }
12210
+ async function showCtxStatusDialog(ctx, content) {
12211
+ let factoryInvoked = false;
12212
+ await ctx.ui.custom((_tui, theme, _keybindings, done) => {
12213
+ factoryInvoked = true;
12214
+ return new CtxStatusDialog(content, theme, done);
12215
+ }, { overlay: true, overlayOptions: { anchor: "center", width: 92 } });
12216
+ return factoryInvoked;
12217
+ }
12218
+
12219
+ class CtxStatusDialog {
12220
+ content;
12221
+ theme;
12222
+ done;
12223
+ constructor(content, theme, done) {
12224
+ this.content = content;
12225
+ this.theme = theme;
12226
+ this.done = done;
12227
+ }
12228
+ handleInput(data) {
12229
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c") || matchesKey(data, "return")) {
12230
+ this.done(undefined);
12231
+ }
12232
+ }
12233
+ invalidate() {}
12234
+ render(_width) {
12235
+ return [
12236
+ this.theme.bold(this.theme.fg(statusTitleColor(this.content.level), `[${this.content.title}]`)),
12237
+ "",
12238
+ ...this.content.text.split(`
12239
+ `),
12240
+ "",
12241
+ this.theme.fg("dim", "Press Enter or Escape to close")
12242
+ ];
12243
+ }
12244
+ }
11833
12245
  function statusTitleColor(level) {
11834
12246
  switch (level) {
11835
12247
  case "success":
@@ -11864,7 +12276,37 @@ function registerCtxStatusEntryRenderer(pi) {
11864
12276
  return false;
11865
12277
  }
11866
12278
  }
11867
- function sendCtxStatusMessage(pi, content, details) {
12279
+ async function presentCtxStatusMessage(ctx, content, signal) {
12280
+ if (ctx.mode !== "rpc" || signal?.aborted)
12281
+ return;
12282
+ const type = content.level === "error" || content.level === "warning" ? content.level : "info";
12283
+ if (!shouldShowCtxStatusDialog(content)) {
12284
+ if (!signal?.aborted)
12285
+ ctx.ui.notify(content.text, type);
12286
+ return;
12287
+ }
12288
+ try {
12289
+ const shown = await showCtxStatusDialog(ctx, content);
12290
+ if (!shown && !signal?.aborted)
12291
+ ctx.ui.notify(content.text, type);
12292
+ } catch (err) {
12293
+ if (signal?.aborted)
12294
+ return;
12295
+ sessionLog("pi-status", `ctx status dialog failed: ${err instanceof Error ? err.message : String(err)}`);
12296
+ ctx.ui.notify(content.text, type);
12297
+ }
12298
+ }
12299
+ function createCtxStatusSender(pi, ctx, signal) {
12300
+ const lifecycleSignal = statusLifecycleSignals.get(pi);
12301
+ const effectiveSignal = signal && lifecycleSignal ? AbortSignal.any([signal, lifecycleSignal]) : signal ?? lifecycleSignal;
12302
+ return (content, details) => {
12303
+ if (!effectiveSignal?.aborted)
12304
+ sendCtxStatusMessage(pi, content, details, ctx, effectiveSignal);
12305
+ };
12306
+ }
12307
+ function sendCtxStatusMessage(pi, content, details, ctx, signal) {
12308
+ if (signal?.aborted)
12309
+ return;
11868
12310
  const data = {
11869
12311
  ...content,
11870
12312
  details: details ?? content.details
@@ -11872,6 +12314,8 @@ function sendCtxStatusMessage(pi, content, details) {
11872
12314
  if (typeof pi.appendEntry === "function") {
11873
12315
  pi.appendEntry(CTX_STATUS_CUSTOM_TYPE, data);
11874
12316
  }
12317
+ if (ctx)
12318
+ presentCtxStatusMessage(ctx, data, signal);
11875
12319
  sessionLog("pi-status", `${content.title}: ${content.text}`);
11876
12320
  }
11877
12321
 
@@ -11880,6 +12324,7 @@ function registerCtxDreamCommand(pi, deps) {
11880
12324
  pi.registerCommand("ctx-dream", {
11881
12325
  description: "Run Magic Context dreamer tasks for this project now",
11882
12326
  handler: async (args, ctx) => {
12327
+ const sendStatus = createCtxStatusSender(pi, ctx);
11883
12328
  const project = deps.resolveProject?.(ctx) ?? {
11884
12329
  projectDir: deps.projectDir,
11885
12330
  projectIdentity: deps.projectIdentity
@@ -11890,7 +12335,7 @@ function registerCtxDreamCommand(pi, deps) {
11890
12335
  let task;
11891
12336
  if (requested) {
11892
12337
  if (!isCanonicalDreamTask(requested)) {
11893
- sendCtxStatusMessage(pi, {
12338
+ sendStatus({
11894
12339
  title: "/ctx-dream",
11895
12340
  text: `## /ctx-dream
11896
12341
 
@@ -11905,7 +12350,7 @@ Unknown task "${requested}".`,
11905
12350
  task = requested;
11906
12351
  }
11907
12352
  if (dreamerEnabled === false) {
11908
- sendCtxStatusMessage(pi, {
12353
+ sendStatus({
11909
12354
  title: "/ctx-dream",
11910
12355
  text: "## /ctx-dream\n\nDreamer is disabled for this project (`dreamer.disable=true`).",
11911
12356
  level: "info"
@@ -11917,7 +12362,7 @@ Unknown task "${requested}".`,
11917
12362
  }
11918
12363
  const backlogTasks = task ? [task] : CANONICAL_DREAM_TASKS;
11919
12364
  const backlogBefore = getDreamTaskBacklogs(deps.db, project.projectIdentity, backlogTasks);
11920
- sendCtxStatusMessage(pi, {
12365
+ sendStatus({
11921
12366
  title: "/ctx-dream",
11922
12367
  text: [
11923
12368
  "## /ctx-dream",
@@ -11935,7 +12380,8 @@ Unknown task "${requested}".`,
11935
12380
  projectIdentity: project.projectIdentity
11936
12381
  });
11937
12382
  try {
11938
- const result = await runPiDreamForProject(project.projectIdentity, task);
12383
+ await deps.ensureRegistered?.(ctx);
12384
+ const result = await runPiDreamForProject(project.projectIdentity, task, deps.registrationOwner);
11939
12385
  const lines = [];
11940
12386
  if (result.ran.length > 0)
11941
12387
  lines.push(`Ran: ${result.ran.join(", ")}`);
@@ -11953,11 +12399,12 @@ Unknown task "${requested}".`,
11953
12399
  }
11954
12400
  if (lines.length === 0)
11955
12401
  lines.push("No enabled dream tasks to run.");
11956
- sendCtxStatusMessage(pi, {
12402
+ sendStatus({
11957
12403
  title: "/ctx-dream",
11958
12404
  text: ["## /ctx-dream", "", ...lines].join(`
11959
12405
  `),
11960
- level: result.ran.length > 0 ? "success" : "info"
12406
+ level: result.ran.length > 0 ? "success" : "info",
12407
+ rpcDisplay: "dialog"
11961
12408
  }, {
11962
12409
  projectDir: project.projectDir,
11963
12410
  projectIdentity: project.projectIdentity
@@ -11965,7 +12412,7 @@ Unknown task "${requested}".`,
11965
12412
  } catch (error) {
11966
12413
  const message = error instanceof Error ? error.message : String(error);
11967
12414
  sessionLog(project.projectIdentity, `/ctx-dream failed: ${message}`);
11968
- sendCtxStatusMessage(pi, {
12415
+ sendStatus({
11969
12416
  title: "/ctx-dream",
11970
12417
  text: [
11971
12418
  "## /ctx-dream",
@@ -12176,9 +12623,10 @@ function registerCtxEmbedCommand(pi, deps) {
12176
12623
  pi.registerCommand("ctx-embed", {
12177
12624
  description: "Embedding status, or start/pause history compartment embedding (start | pause)",
12178
12625
  handler: async (args, ctx) => {
12626
+ const sendStatus = createCtxStatusSender(pi, ctx);
12179
12627
  const sessionId = resolveSessionId(ctx);
12180
12628
  if (!sessionId) {
12181
- sendCtxStatusMessage(pi, {
12629
+ sendStatus({
12182
12630
  title: "/ctx-embed",
12183
12631
  text: `## /ctx-embed
12184
12632
 
@@ -12199,7 +12647,7 @@ No active Pi session is available.`,
12199
12647
  if (ctrl)
12200
12648
  ctrl.abort();
12201
12649
  const cov = getEmbeddingCoverageStatus(deps.db, project.projectIdentity, sessionId);
12202
- sendCtxStatusMessage(pi, {
12650
+ sendStatus({
12203
12651
  title: "/ctx-embed",
12204
12652
  text: `## /ctx-embed
12205
12653
 
@@ -12209,7 +12657,7 @@ Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`,
12209
12657
  return;
12210
12658
  }
12211
12659
  if (memoryEnabled === false) {
12212
- sendCtxStatusMessage(pi, {
12660
+ sendStatus({
12213
12661
  title: "/ctx-embed",
12214
12662
  text: `## /ctx-embed
12215
12663
 
@@ -12221,16 +12669,16 @@ Memory is disabled for this project, so there is no semantic embedding to backfi
12221
12669
  await ensureProjectRegisteredFromPiDirectory(project.projectDir, deps.db);
12222
12670
  if (sub === "start") {
12223
12671
  const { text, level } = await runEmbedDrain(deps.db, project.projectIdentity, sessionId, {
12224
- onStatus: (status) => sendCtxStatusMessage(pi, {
12672
+ onStatus: (status) => sendStatus({
12225
12673
  title: "/ctx-embed",
12226
12674
  ...status
12227
12675
  })
12228
12676
  });
12229
- sendCtxStatusMessage(pi, { title: "/ctx-embed", text, level });
12677
+ sendStatus({ title: "/ctx-embed", text, level });
12230
12678
  return;
12231
12679
  }
12232
12680
  if (sub !== "") {
12233
- sendCtxStatusMessage(pi, {
12681
+ sendStatus({
12234
12682
  title: "/ctx-embed",
12235
12683
  text: "## /ctx-embed\n\nUsage: `/ctx-embed` (status), `/ctx-embed start`, or `/ctx-embed pause`.",
12236
12684
  level: "info"
@@ -12239,12 +12687,13 @@ Memory is disabled for this project, so there is no semantic embedding to backfi
12239
12687
  }
12240
12688
  const coverage = getEmbeddingCoverageStatus(deps.db, project.projectIdentity, sessionId);
12241
12689
  const statusText = formatEmbedStatusText(coverage, { status: "idle" });
12242
- sendCtxStatusMessage(pi, {
12690
+ sendStatus({
12243
12691
  title: "/ctx-embed",
12244
12692
  text: `## Embedding Status
12245
12693
 
12246
12694
  ${statusText}`,
12247
- level: "info"
12695
+ level: "info",
12696
+ rpcDisplay: "dialog"
12248
12697
  });
12249
12698
  }
12250
12699
  });
@@ -12260,7 +12709,7 @@ function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity, n
12260
12709
  (async () => {
12261
12710
  let completedDrainWithWork = false;
12262
12711
  try {
12263
- await new Promise((resolve2) => setTimeout(resolve2, 0));
12712
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
12264
12713
  await ensureProjectRegisteredFromPiDirectory(projectDir, deps.db);
12265
12714
  const coverage = getEmbeddingCoverageStatus(deps.db, projectIdentity, sessionId);
12266
12715
  if (!coverage.enabled)
@@ -13649,23 +14098,33 @@ function fenceWrapupBoundaryForToolArcs(args) {
13649
14098
  return boundary;
13650
14099
  }
13651
14100
  function applyHeadCap(args) {
13652
- const { index, protectedTailStart, offset, arcs, capTokens, recentOpenArcCutoff } = args;
14101
+ const {
14102
+ index,
14103
+ protectedTailStart,
14104
+ offset,
14105
+ arcs,
14106
+ lastCompartmentEndOrdinal,
14107
+ capTokens,
14108
+ recentOpenArcCutoff
14109
+ } = args;
13653
14110
  if (offset >= protectedTailStart)
13654
14111
  return { eligibleEndOrdinal: offset, oversizeAtomicUnit: false };
13655
14112
  let end = index.findHeadEndForCap(offset, protectedTailStart, capTokens);
13656
14113
  let oversizeAtomicUnit = end === offset + 1 && index.tokenForOrdinal(offset) > capTokens;
14114
+ const completedFence = fenceBoundaryForCompletedToolArcs(end, arcs, lastCompartmentEndOrdinal + 1);
14115
+ if (completedFence > end) {
14116
+ end = Math.min(protectedTailStart, completedFence);
14117
+ if (index.rangeTokens(offset, end) > capTokens)
14118
+ oversizeAtomicUnit = true;
14119
+ } else {
14120
+ end = completedFence;
14121
+ }
13657
14122
  for (const arc of arcs) {
13658
14123
  const resOrdinal = arc.resOrdinal;
13659
14124
  if (resOrdinal === null) {
13660
14125
  if (arc.invOrdinal >= recentOpenArcCutoff && arc.invOrdinal >= offset && arc.invOrdinal < end) {
13661
14126
  end = Math.min(end, arc.invOrdinal);
13662
14127
  }
13663
- continue;
13664
- }
13665
- if (arc.invOrdinal < end && end <= resOrdinal) {
13666
- end = Math.min(protectedTailStart, resOrdinal + 1);
13667
- if (index.rangeTokens(Math.max(offset, arc.invOrdinal), end) > capTokens)
13668
- oversizeAtomicUnit = true;
13669
14128
  }
13670
14129
  }
13671
14130
  if (end <= offset && offset < protectedTailStart) {
@@ -14267,6 +14726,9 @@ function getUnsummarizedTailInfo(db, sessionId, triggerBudget, usage, executeThr
14267
14726
  }
14268
14727
  });
14269
14728
  }
14729
+ function formatProjectedPostDropPercentage(value) {
14730
+ return value === null ? "none" : `${value.toFixed(1)}%`;
14731
+ }
14270
14732
  function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, clearReasoningAge, commitClusterTrigger, preloadedActiveTags, contextLimit, inMemoryTail, taggerFloorOverride, reasoningProjection) {
14271
14733
  if (sessionMeta.compartmentInProgress) {
14272
14734
  sessionLog(sessionId, `compartment trigger: skipped — historian already in progress (usage=${usage.percentage.toFixed(1)}%)`);
@@ -14330,7 +14792,7 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
14330
14792
  sessionLog(sessionId, `compartment trigger: skipping force band ${forceMaterializationPercentage}% because projected post-drop usage is ${projectedPostDropPercentage.toFixed(1)}% (target ${relativePostDropTarget.toFixed(1)}%)`);
14331
14793
  return { shouldFire: false };
14332
14794
  }
14333
- sessionLog(sessionId, `compartment trigger: force-firing at ${usage.percentage.toFixed(1)}% (projected post-drop ${projectedPostDropPercentage?.toFixed(1) ?? "none"}%)`);
14795
+ sessionLog(sessionId, `compartment trigger: force-firing at ${usage.percentage.toFixed(1)}% (projected post-drop ${formatProjectedPostDropPercentage(projectedPostDropPercentage)})`);
14334
14796
  if (tailInfo.boundarySnapshot && hasRunnableCompartmentWindow(tailInfo.boundarySnapshot)) {
14335
14797
  return {
14336
14798
  shouldFire: true,
@@ -14395,7 +14857,7 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
14395
14857
  sessionLog(sessionId, `compartment trigger: not firing at ${usage.percentage.toFixed(1)}% because unsummarized tail from ${tailInfo.nextStartOrdinal} is too small`);
14396
14858
  return { shouldFire: false };
14397
14859
  }
14398
- sessionLog(sessionId, `compartment trigger: proactive fire at ${usage.percentage.toFixed(1)}% (floor=${proactiveTriggerPercentage}% projected post-drop=${projectedPostDropPercentage?.toFixed(1) ?? "none"}% target=${relativePostDropTarget.toFixed(1)}%)`);
14860
+ sessionLog(sessionId, `compartment trigger: proactive fire at ${usage.percentage.toFixed(1)}% (floor=${proactiveTriggerPercentage}% projected post-drop=${formatProjectedPostDropPercentage(projectedPostDropPercentage)} target=${relativePostDropTarget.toFixed(1)}%)`);
14399
14861
  return {
14400
14862
  shouldFire: true,
14401
14863
  reason: "projected_headroom",
@@ -14436,8 +14898,9 @@ function buildSupersessionReclaimOps(input) {
14436
14898
  const action = input.targets.get(tag.tagNumber)?.readInput?.()?.action;
14437
14899
  isTarget = typeof action === "string" && CTX_NOTE_ZERO_VALUE_ACTIONS.has(action);
14438
14900
  }
14439
- if (isTarget)
14901
+ if (isTarget && (!input.recentMessageIds || tag.toolOwnerMessageId !== null && !input.recentMessageIds.has(tag.toolOwnerMessageId))) {
14440
14902
  dropTagIds.push(tag.tagNumber);
14903
+ }
14441
14904
  }
14442
14905
  const synthetic = [];
14443
14906
  for (const tagId of dropTagIds) {
@@ -14470,6 +14933,9 @@ function buildEditSupersessionReclaim(input) {
14470
14933
  seenFile.add(filePath);
14471
14934
  continue;
14472
14935
  }
14936
+ if (input.recentMessageIds && (tag.toolOwnerMessageId === null || input.recentMessageIds.has(tag.toolOwnerMessageId))) {
14937
+ continue;
14938
+ }
14473
14939
  if (realPendingTagIds.has(tag.tagNumber))
14474
14940
  continue;
14475
14941
  if (input.targets.get(tag.tagNumber)?.canDrop?.() !== true)
@@ -15258,10 +15724,10 @@ var DEFAULT_MIN_PROMPT_CHARS = 20;
15258
15724
  async function unifiedSearchWithTimeout(db, sessionId, projectPath, prompt, options, timeoutMs) {
15259
15725
  const controller = new AbortController;
15260
15726
  let timer;
15261
- const timeoutPromise = new Promise((resolve2) => {
15727
+ const timeoutPromise = new Promise((resolve3) => {
15262
15728
  timer = setTimeout(() => {
15263
15729
  controller.abort();
15264
- resolve2(null);
15730
+ resolve3(null);
15265
15731
  }, timeoutMs);
15266
15732
  });
15267
15733
  try {
@@ -16334,7 +16800,7 @@ function measurePiTailHygiene(input) {
16334
16800
  }
16335
16801
  continue;
16336
16802
  }
16337
- const content = part.text;
16803
+ const content = stripChannel1ReminderSpans(part.text);
16338
16804
  const tag = parseVisibleTag(content, tagsByNumber) ?? messageTags.get(`${messageKey}:p${textOrdinal}`);
16339
16805
  textOrdinal += 1;
16340
16806
  if (!content || isDropSentinel(content)) {
@@ -16437,7 +16903,8 @@ function refreshPiTailHygieneBaseline(input) {
16437
16903
  evaluable: true,
16438
16904
  generationInvalidated: false,
16439
16905
  baselineParts: measured.parts,
16440
- contentSignature: measured.contentSignature
16906
+ contentSignature: measured.contentSignature,
16907
+ channel1PostReduceGrace: input.previous?.channel1PostReduceGrace
16441
16908
  };
16442
16909
  }
16443
16910
  const prefix = sameMeasuredPrefix(input.previous.baselineParts, measured.parts);
@@ -16511,8 +16978,15 @@ function markPiChannel1Reduced(sessionId, db) {
16511
16978
  state2.evaluable = false;
16512
16979
  state2.generationInvalidated = true;
16513
16980
  }
16514
- if (db)
16515
- resetLastNudgeCycle(db, sessionId);
16981
+ if (!db)
16982
+ return;
16983
+ const grace = markChannel1PostReduceGracePending(db, sessionId);
16984
+ if (state2) {
16985
+ state2.channel1PostReduceGrace = {
16986
+ pending: true,
16987
+ preReduceLevel: grace.postReduceGracePreLevel ?? grace.level
16988
+ };
16989
+ }
16516
16990
  }
16517
16991
  function isPiTextContent(c) {
16518
16992
  return c !== null && typeof c === "object" && c.type === "text" && typeof c.text === "string";
@@ -16531,10 +17005,7 @@ function maybeChannel1ReminderForToolResult(args) {
16531
17005
  if (!state2)
16532
17006
  return null;
16533
17007
  if (toolName === "ctx_reduce") {
16534
- state2.reducedSinceRefresh = true;
16535
- state2.evaluable = false;
16536
- state2.generationInvalidated = true;
16537
- resetLastNudgeCycle(db, sessionId);
17008
+ markPiChannel1Reduced(sessionId, db);
16538
17009
  return null;
16539
17010
  }
16540
17011
  const text = toolResultText(args.content);
@@ -16551,28 +17022,32 @@ function maybeChannel1ReminderForToolResult(args) {
16551
17022
  ...state2,
16552
17023
  lastNudgeUndropped: getLastNudgeUndropped(db, sessionId),
16553
17024
  lastNudgeLevel: nudgeState.level,
16554
- hasRecentReduce: state2.reducedSinceRefresh
17025
+ lastFireOrdinal: nudgeState.ordinal,
17026
+ currentRealUserTurnCount: state2.realUserTurnCount,
17027
+ hasRecentReduce: state2.reducedSinceRefresh,
17028
+ postReduceGracePending: nudgeState.postReduceGracePending,
17029
+ postReduceGraceBaselineU: nudgeState.postReduceGraceBaselineU,
17030
+ postReduceGracePreLevel: nudgeState.postReduceGracePreLevel
16555
17031
  });
16556
17032
  setLastNudgeUndropped(db, sessionId, decision.nextLastNudge);
17033
+ const nextNudgeState = {
17034
+ ...nudgeState,
17035
+ level: decision.nextLastNudgeLevel,
17036
+ ordinal: decision.nextLastNudgeLevel === "" ? 0 : nudgeState.ordinal,
17037
+ postReduceGracePending: decision.clearPostReduceGrace ? undefined : nudgeState.postReduceGracePending,
17038
+ postReduceGraceBaselineU: decision.clearPostReduceGrace ? undefined : nudgeState.postReduceGraceBaselineU,
17039
+ postReduceGracePreLevel: decision.clearPostReduceGrace ? undefined : nudgeState.postReduceGracePreLevel
17040
+ };
16557
17041
  if (!decision.fire) {
16558
- setChannel1NudgeState(db, sessionId, {
16559
- level: decision.nextLastNudgeLevel,
16560
- ordinal: decision.nextLastNudgeLevel === "" ? 0 : nudgeState.ordinal
16561
- });
17042
+ setChannel1NudgeState(db, sessionId, nextNudgeState);
16562
17043
  return null;
16563
17044
  }
16564
- const sticky = shouldUseStickyChannel1Reminder({
16565
- lastLevel: nudgeState.level,
16566
- lastOrdinal: nudgeState.ordinal,
16567
- level: decision.level,
16568
- currentRealUserTurnCount: state2.realUserTurnCount
16569
- });
16570
17045
  const block = {
16571
17046
  type: "text",
16572
- text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state2.usableWindow, state2.oldestReclaimableToolTags, sticky)
17047
+ text: buildChannel1Reminder(decision.level, decision.undroppedTokens, reclaimableToolOutputCount(state2.baselineParts), state2.oldestReclaimableToolTags, decision.sticky)
16573
17048
  };
16574
17049
  setChannel1NudgeState(db, sessionId, {
16575
- level: decision.level,
17050
+ ...nextNudgeState,
16576
17051
  ordinal: state2.realUserTurnCount
16577
17052
  });
16578
17053
  return block;
@@ -16606,7 +17081,7 @@ function maybeDeliverChannel2Pi(pi, db, sessionId) {
16606
17081
  try {
16607
17082
  const message = {
16608
17083
  customType: CHANNEL2_NUDGE_CUSTOM_TYPE,
16609
- content: buildChannel2Reminder(undropped, baseline.usableWindow, baseline.oldestReclaimableToolTags),
17084
+ content: buildChannel2Reminder(undropped, reclaimableToolOutputCount(baseline.baselineParts), baseline.oldestReclaimableToolTags),
16610
17085
  display: false,
16611
17086
  details: { kind: "channel-2-ceiling-nudge" }
16612
17087
  };
@@ -16896,6 +17371,8 @@ function piImageFromDataUrl(dataUrl) {
16896
17371
  return { type: "image", mimeType: match[1], data: match[2] };
16897
17372
  }
16898
17373
  function resolveMuralForM0Pi(state2, db, modelKey, budgetTokens) {
17374
+ if (state2.memoryEnabled === false)
17375
+ return;
16899
17376
  if (state2.mural)
16900
17377
  return state2.mural;
16901
17378
  if (!state2.muralEnabled)
@@ -17107,7 +17584,7 @@ function renderM0Pi(state2, db, projectDocs = readProjectDocsForPiM0(state2).ren
17107
17584
  const sections = [];
17108
17585
  if (projectDocs.length > 0)
17109
17586
  sections.push(projectDocs);
17110
- const trimmedProfile = trimUserMemoriesToBudget(userProfileOverride ?? safeGetActiveUserMemoriesPi(db), state2.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS);
17587
+ const trimmedProfile = trimUserMemoriesToBudget(state2.memoryEnabled === false ? [] : userProfileOverride ?? safeGetActiveUserMemoriesPi(db), state2.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS);
17111
17588
  const userProfile = renderUserProfileBlock(db, "user-profile", trimmedProfile);
17112
17589
  if (userProfile.length > 0)
17113
17590
  sections.push(userProfile);
@@ -17167,7 +17644,7 @@ function readFrozenM0InputsPi(state2, db, docs = readProjectDocsForPiM0(state2),
17167
17644
  const workspace = resolveWorkspaceRenderContextPi(state2, db);
17168
17645
  const compartments = getRenderableCompartmentsPi(db, state2);
17169
17646
  const memories = memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], memoryCutoff, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
17170
- const userProfile = safeGetActiveUserMemoriesPi(db);
17647
+ const userProfile = state2.memoryEnabled === false ? [] : safeGetActiveUserMemoriesPi(db);
17171
17648
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
17172
17649
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
17173
17650
  const markers = {
@@ -17187,7 +17664,7 @@ function readFrozenM0InputsPi(state2, db, docs = readProjectDocsForPiM0(state2),
17187
17664
  systemHash: (state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
17188
17665
  modelKey: piModelRefToCanonical((state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey),
17189
17666
  projectIdentity: state2.projectIdentity,
17190
- muralEnabled: state2.muralEnabled === true,
17667
+ muralEnabled: state2.memoryEnabled !== false && state2.muralEnabled === true,
17191
17668
  renderBudgetIdentity: renderBudgetIdentityPi(state2)
17192
17669
  };
17193
17670
  return { docs, markers, compartments, memories, userProfile, workspace };
@@ -17409,7 +17886,7 @@ ${body}
17409
17886
  if (newMemoriesBlock)
17410
17887
  sections.push(newMemoriesBlock);
17411
17888
  const currentUserProfileVersion = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH)?.projectUserProfileVersion ?? 0;
17412
- if (currentUserProfileVersion !== markers.projectUserProfileVersion) {
17889
+ if (state2.memoryEnabled !== false && currentUserProfileVersion !== markers.projectUserProfileVersion) {
17413
17890
  const profileBudget = state2.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS;
17414
17891
  const trimmedProfile = trimUserMemoriesToBudget(safeGetActiveUserMemoriesPi(db), Math.max(1, Math.floor(profileBudget * 0.25)));
17415
17892
  const profileBlock = renderUserProfileBlock(db, "new-user-profile", trimmedProfile);
@@ -20994,7 +21471,7 @@ async function sleepWithAbort(ms, signal) {
20994
21471
  return true;
20995
21472
  if (ms <= 0)
20996
21473
  return signal?.aborted === true;
20997
- return new Promise((resolve2) => {
21474
+ return new Promise((resolve3) => {
20998
21475
  let settled = false;
20999
21476
  let timeout;
21000
21477
  const finish = (aborted) => {
@@ -21004,7 +21481,7 @@ async function sleepWithAbort(ms, signal) {
21004
21481
  if (timeout)
21005
21482
  clearTimeout(timeout);
21006
21483
  signal?.removeEventListener("abort", onAbort);
21007
- resolve2(aborted);
21484
+ resolve3(aborted);
21008
21485
  };
21009
21486
  const onAbort = () => finish(true);
21010
21487
  signal?.addEventListener("abort", onAbort, { once: true });
@@ -21123,6 +21600,8 @@ async function runPiHistorian(deps) {
21123
21600
  refreshBoundarySnapshot,
21124
21601
  currentContextLimit,
21125
21602
  historianTimeoutMs = DEFAULT_HISTORIAN_TIMEOUT_MS2,
21603
+ temperature = 0.1,
21604
+ maxOutputTokens = 32000,
21126
21605
  signal,
21127
21606
  retryBackoffMs,
21128
21607
  twoPass,
@@ -21235,9 +21714,9 @@ async function runPiHistorian(deps) {
21235
21714
  executeThresholdPercentage: boundarySnapshot.executeThresholdPercentage
21236
21715
  });
21237
21716
  if (!reserve.ok) {
21238
- sessionLog(sessionId, `historian rate-limit skip: ${reserve.skippedReason ?? "quota exhausted"}`);
21717
+ sessionLog(sessionId, describeProtectedTailDrainBudgetSkip(reserve));
21239
21718
  telemetry.status = "noop";
21240
- telemetry.failureReason = "protected-tail drain quota exhausted";
21719
+ telemetry.failureReason = "internal protected-tail drain budget spent";
21241
21720
  return;
21242
21721
  }
21243
21722
  drainReservation = reserve.reservation;
@@ -21350,6 +21829,8 @@ ${chunkText}`,
21350
21829
  cwd: directory,
21351
21830
  signal,
21352
21831
  thinkingLevel,
21832
+ temperature,
21833
+ maxOutputTokens,
21353
21834
  onProgress: buildProgressLogger("first"),
21354
21835
  accountingSessionId: sessionId,
21355
21836
  accountingSubagent: "historian"
@@ -21374,6 +21855,8 @@ ${chunkText}`,
21374
21855
  cwd: directory,
21375
21856
  signal,
21376
21857
  thinkingLevel,
21858
+ temperature,
21859
+ maxOutputTokens,
21377
21860
  onProgress: buildProgressLogger("repair"),
21378
21861
  accountingSessionId: sessionId,
21379
21862
  accountingSubagent: "historian"
@@ -21403,6 +21886,8 @@ ${chunkText}`,
21403
21886
  cwd: directory,
21404
21887
  signal,
21405
21888
  thinkingLevel: candidate.entry.qualifier,
21889
+ temperature,
21890
+ maxOutputTokens,
21406
21891
  onProgress: buildProgressLogger("fallback"),
21407
21892
  accountingSessionId: sessionId,
21408
21893
  accountingSubagent: "historian"
@@ -21449,6 +21934,8 @@ ${chunkText}`,
21449
21934
  cwd: directory,
21450
21935
  signal,
21451
21936
  thinkingLevel,
21937
+ temperature,
21938
+ maxOutputTokens,
21452
21939
  onProgress: buildProgressLogger("editor"),
21453
21940
  accountingSessionId: sessionId,
21454
21941
  accountingSubagent: "historian_editor"
@@ -21759,6 +22246,62 @@ function findFirstKeptEntryId(entries, lastCompactedOrdinal) {
21759
22246
  return boundary.id;
21760
22247
  }
21761
22248
 
22249
+ // src/pi-pressure.ts
22250
+ function extractAssistantUsage(message) {
22251
+ if (!message || typeof message !== "object")
22252
+ return null;
22253
+ const m = message;
22254
+ if (m.role !== "assistant")
22255
+ return null;
22256
+ if (!m.usage || typeof m.usage !== "object")
22257
+ return null;
22258
+ const u = m.usage;
22259
+ const result = {};
22260
+ if (typeof u.input === "number")
22261
+ result.input = u.input;
22262
+ if (typeof u.output === "number")
22263
+ result.output = u.output;
22264
+ if (typeof u.cacheRead === "number")
22265
+ result.cacheRead = u.cacheRead;
22266
+ if (typeof u.cacheWrite === "number")
22267
+ result.cacheWrite = u.cacheWrite;
22268
+ if (typeof u.totalTokens === "number")
22269
+ result.totalTokens = u.totalTokens;
22270
+ return result;
22271
+ }
22272
+ function computePiPressure(usage, contextLimit) {
22273
+ if (!usage)
22274
+ return null;
22275
+ const input = usage.input ?? 0;
22276
+ const cacheRead = usage.cacheRead ?? 0;
22277
+ const cacheWrite = usage.cacheWrite ?? 0;
22278
+ const inputTokens = input + cacheRead + cacheWrite;
22279
+ if (inputTokens === 0)
22280
+ return null;
22281
+ const percentage = contextLimit > 0 ? inputTokens / contextLimit * 100 : 0;
22282
+ return { inputTokens, percentage };
22283
+ }
22284
+ function resolvePiPressureSnapshot(args) {
22285
+ const persistedInputTokens = Number.isFinite(args.persistedInputTokens) && args.persistedInputTokens > 0 ? args.persistedInputTokens : 0;
22286
+ const liveInputTokens = typeof args.liveInputTokens === "number" && Number.isFinite(args.liveInputTokens) && args.liveInputTokens > 0 ? args.liveInputTokens : 0;
22287
+ const inputTokens = Math.max(persistedInputTokens, liveInputTokens);
22288
+ const contextLimit = typeof args.usableContextLimit === "number" && Number.isFinite(args.usableContextLimit) && args.usableContextLimit > 0 ? args.usableContextLimit : undefined;
22289
+ if (contextLimit !== undefined) {
22290
+ return {
22291
+ inputTokens,
22292
+ percentage: inputTokens > 0 ? inputTokens / contextLimit * 100 : 0,
22293
+ contextLimit
22294
+ };
22295
+ }
22296
+ return {
22297
+ inputTokens: persistedInputTokens,
22298
+ percentage: Number.isFinite(args.persistedPercentage) && args.persistedPercentage > 0 ? args.persistedPercentage : 0
22299
+ };
22300
+ }
22301
+ function formatPiPressureForLog(snapshot) {
22302
+ return `usage=${snapshot.percentage.toFixed(1)}% (${snapshot.inputTokens} tokens, limit=${snapshot.contextLimit ?? "?"})`;
22303
+ }
22304
+
21762
22305
  // src/pi-todo-inject.ts
21763
22306
  function getMessageId(message) {
21764
22307
  if (typeof message.responseId === "string" && message.responseId.length > 0) {
@@ -22585,8 +23128,8 @@ function injectPiTemporalMarkers(messages) {
22585
23128
  // src/timeout.ts
22586
23129
  function withTimeout(p, ms) {
22587
23130
  let handle;
22588
- const timeout = new Promise((resolve2) => {
22589
- handle = setTimeout(() => resolve2(undefined), ms);
23131
+ const timeout = new Promise((resolve3) => {
23132
+ handle = setTimeout(() => resolve3(undefined), ms);
22590
23133
  handle.unref?.();
22591
23134
  });
22592
23135
  return Promise.race([p, timeout]).finally(() => {
@@ -23300,22 +23843,9 @@ function extractStableId(msg, index, entryIds) {
23300
23843
 
23301
23844
  // src/context-handler.ts
23302
23845
  var EMERGENCY_BLOCK_PERCENTAGE = 95;
23303
- var FORWARD_PRESSURE_LIMIT_FACTOR = 0.85;
23304
23846
  function isPiHardCacheExpired(lastResponseTime, ttlMs, now) {
23305
23847
  return lastResponseTime > 0 && now - lastResponseTime > ttlMs;
23306
23848
  }
23307
- function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUsageTokens, correctedLimit) {
23308
- const forwardTokens = typeof piUsageTokens === "number" && piUsageTokens > 0 ? piUsageTokens : 0;
23309
- if (forwardTokens === 0 || !isSaneLimit(correctedLimit)) {
23310
- return { percentage: trailingPercentage, inputTokens: trailingInputTokens };
23311
- }
23312
- const forwardPressureLimit = correctedLimit * FORWARD_PRESSURE_LIMIT_FACTOR;
23313
- const forwardPercentage = forwardTokens / forwardPressureLimit * 100;
23314
- return forwardPercentage > trailingPercentage ? {
23315
- percentage: forwardPercentage,
23316
- inputTokens: Math.max(trailingInputTokens, forwardTokens)
23317
- } : { percentage: trailingPercentage, inputTokens: trailingInputTokens };
23318
- }
23319
23849
  var injectM0M1PiForRun = injectM0M1Pi;
23320
23850
  var persistReasoningWatermarkForRun = updateSessionMeta;
23321
23851
  var persistStableIdSchemeForRun = updateSessionMeta;
@@ -24255,7 +24785,12 @@ function registerPiContextHandler(pi, baseOptions) {
24255
24785
  if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
24256
24786
  usagePercentage = usageInputTokens / usageContextLimit * 100;
24257
24787
  }
24258
- ({ percentage: usagePercentage, inputTokens: usageInputTokens } = applyForwardPressureFloor(usagePercentage, usageInputTokens, piUsage?.tokens, usageContextLimit));
24788
+ ({ percentage: usagePercentage, inputTokens: usageInputTokens } = resolvePiPressureSnapshot({
24789
+ persistedPercentage: usagePercentage,
24790
+ persistedInputTokens: usageInputTokens,
24791
+ liveInputTokens: piUsage?.tokens,
24792
+ usableContextLimit: usageContextLimit
24793
+ }));
24259
24794
  const realUsagePercentageBeforeEmergencyBump = usagePercentage;
24260
24795
  if (needsEmergencyBump) {
24261
24796
  sessionLog(sessionId, `transform: overflow recovery flag set — bumping percentage to 95% (detectedLimit=${usageContextLimit ?? "unknown"})`);
@@ -24317,6 +24852,9 @@ function registerPiContextHandler(pi, baseOptions) {
24317
24852
  schedulerDecision = midTurnAdjustedSchedulerDecision;
24318
24853
  sessionLog(sessionId, `[boundary-exec] base=${schedulerDecisionEarly} bypass=${bypassReason} midTurn=${midTurn} effective=${midTurnAdjustedSchedulerDecision} sideEffect=${sideEffect}`);
24319
24854
  const forceMaterialization = !options.compactionOff && usagePercentage >= forceMaterializationPercentage;
24855
+ if (!forceMaterialization && getEmergencyInputSample(options.db, sessionId) > 0) {
24856
+ clearEmergencyDropSample(options.db, sessionId);
24857
+ }
24320
24858
  const hardUsagePercentage = needsEmergencyBump ? Math.max(EMERGENCY_BLOCK_PERCENTAGE, usagePercentage) : windowGeometry?.usableHard && usageInputTokens > 0 ? usageInputTokens / windowGeometry.usableHard * 100 : usagePercentage;
24321
24859
  const emergencyPercentage = ctx.model ? hardUsagePercentage : usagePercentage;
24322
24860
  const isEmergency = !options.compactionOff && emergencyPercentage >= EMERGENCY_BLOCK_PERCENTAGE;
@@ -24346,7 +24884,7 @@ function registerPiContextHandler(pi, baseOptions) {
24346
24884
  }
24347
24885
  const isCacheBusting = historyRefreshSessions.has(sessionId);
24348
24886
  logTransformTiming(sessionId, "boundaryTriggerChecks", tBoundaryChecks);
24349
- sessionLog(sessionId, `transform: usage=${usagePercentage.toFixed(1)}% (${usageInputTokens} tokens, limit=${usageContextLimit ?? "?"}) decision=${schedulerDecision}${forceMaterialization ? " force=true" : ""}${isEmergency ? " EMERGENCY=true" : ""}${isCacheBusting ? " busting=true" : ""}`);
24887
+ sessionLog(sessionId, `transform: ${formatPiPressureForLog({ percentage: usagePercentage, inputTokens: usageInputTokens, contextLimit: usageContextLimit })} decision=${schedulerDecision}${forceMaterialization ? " force=true" : ""}${isEmergency ? " EMERGENCY=true" : ""}${isCacheBusting ? " busting=true" : ""}`);
24350
24888
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tEmergencyRecovery);
24351
24889
  const entryIds = strictEntryIds ?? undefined;
24352
24890
  const emergencyCeilingTokens = usageContextLimit && usageContextLimit > 0 ? Math.floor(usageContextLimit * (resolveExecuteThreshold(schedulerConfig.executeThresholdPercentage ?? 65, liveModelBySession.get(sessionId), 65, {
@@ -24524,7 +25062,12 @@ function registerPiContextHandler(pi, baseOptions) {
24524
25062
  previous: getPiChannel1Baseline(sessionId)
24525
25063
  });
24526
25064
  const effective = effectivePiTailHygiene(baseline);
24527
- resetLastNudgeCycleIfTailShrank(options.db, sessionId, effective.u);
25065
+ const durableGrace = baseline.evaluable && !baseline.generationInvalidated ? captureChannel1PostReduceGraceBaseline(options.db, sessionId, effective.u) : getChannel1NudgeState(options.db, sessionId);
25066
+ baseline.channel1PostReduceGrace = durableGrace.postReduceGracePending || durableGrace.postReduceGraceBaselineU !== undefined ? {
25067
+ pending: durableGrace.postReduceGracePending === true,
25068
+ baselineU: durableGrace.postReduceGraceBaselineU,
25069
+ preReduceLevel: durableGrace.postReduceGracePreLevel ?? durableGrace.level
25070
+ } : undefined;
24528
25071
  const oldestReclaimableToolTags = getOldestActiveUnprotectedToolTags(options.db, sessionId, protectedTags);
24529
25072
  const channelState = {
24530
25073
  ...baseline,
@@ -24631,10 +25174,11 @@ function registerPiContextHandler(pi, baseOptions) {
24631
25174
  log("[magic-context][pi] registered context handler (tagging + drops + nudges)");
24632
25175
  }
24633
25176
  var inFlightHistorian = new Map;
24634
- async function awaitInFlightHistorians() {
24635
- if (inFlightHistorian.size === 0)
25177
+ async function awaitInFlightHistorians(sessionId) {
25178
+ const runs = sessionId ? [inFlightHistorian.get(sessionId)].filter((run) => run !== undefined) : [...inFlightHistorian.values()];
25179
+ if (runs.length === 0)
24636
25180
  return;
24637
- await Promise.allSettled(Array.from(inFlightHistorian.values()));
25181
+ await Promise.allSettled(runs);
24638
25182
  }
24639
25183
  function resolvePiHistorianTriggerInputs(args) {
24640
25184
  const contextLimit = typeof args.usageContextLimit === "number" && Number.isFinite(args.usageContextLimit) && args.usageContextLimit > 0 ? args.usageContextLimit : DEFAULT_CONTEXT_LIMIT;
@@ -24771,6 +25315,8 @@ function spawnPiHistorianRun(args) {
24771
25315
  refreshBoundarySnapshot,
24772
25316
  currentContextLimit,
24773
25317
  historianTimeoutMs: historian.timeoutMs,
25318
+ temperature: historian.temperature,
25319
+ maxOutputTokens: historian.maxOutputTokens,
24774
25320
  twoPass: historian.twoPass,
24775
25321
  thinkingLevel: historian.thinkingLevel,
24776
25322
  memoryEnabled: historian.memoryEnabled,
@@ -24889,7 +25435,12 @@ function maybeFireHistorian(args) {
24889
25435
  };
24890
25436
  usageSource = "piUsage fallback";
24891
25437
  }
24892
- usage = applyForwardPressureFloor(usage.percentage, usage.inputTokens, piUsage?.tokens, usageContextLimit);
25438
+ usage = resolvePiPressureSnapshot({
25439
+ persistedPercentage: usage.percentage,
25440
+ persistedInputTokens: usage.inputTokens,
25441
+ liveInputTokens: piUsage?.tokens,
25442
+ usableContextLimit: usageContextLimit
25443
+ });
24893
25444
  sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [${usageSource}], checking trigger...`);
24894
25445
  } catch (err) {
24895
25446
  sessionLog(sessionId, `historian trigger eval: getContextUsage threw: ${err instanceof Error ? err.message : String(err)}`);
@@ -25356,6 +25907,10 @@ async function runPipeline(args) {
25356
25907
  if (shouldRunHeuristics && args.heuristics) {
25357
25908
  try {
25358
25909
  const tHeuristic = performance.now();
25910
+ const independentMutationBeforeHeuristics = pendingOpsDidMutate || foldExecutedThisPass;
25911
+ if (args.forceMaterialization === true && independentMutationBeforeHeuristics && getEmergencyInputSample(args.db, args.sessionId) > 0) {
25912
+ clearEmergencyDropSample(args.db, args.sessionId);
25913
+ }
25359
25914
  heuristicsResult = applyPiHeuristicCleanup(args.sessionId, args.db, targets, args.messages, {
25360
25915
  protectedTags: args.protectedTags,
25361
25916
  staleReduceStripEnabled: args.canUseEmptySentinels,
@@ -25875,9 +26430,10 @@ function registerCtxFlushCommand(pi, deps) {
25875
26430
  pi.registerCommand("ctx-flush", {
25876
26431
  description: "Force pending Magic Context drops to materialize on the next provider call",
25877
26432
  handler: async (_args, ctx) => {
26433
+ const sendStatus = createCtxStatusSender(pi, ctx);
25878
26434
  const sessionId = resolveSessionId(ctx);
25879
26435
  if (!sessionId) {
25880
- sendCtxStatusMessage(pi, {
26436
+ sendStatus({
25881
26437
  title: "/ctx-flush",
25882
26438
  text: `## /ctx-flush
25883
26439
 
@@ -25887,7 +26443,7 @@ No active Pi session is available.`,
25887
26443
  return;
25888
26444
  }
25889
26445
  if (deps.compactionOff) {
25890
- sendCtxStatusMessage(pi, {
26446
+ sendStatus({
25891
26447
  title: "/ctx-flush",
25892
26448
  text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
25893
26449
  level: "warning"
@@ -25906,7 +26462,7 @@ Flushed ${pendingBefore} pending ops; next provider call will materialize.
25906
26462
  ${result}` : `## /ctx-flush
25907
26463
 
25908
26464
  ${result}`;
25909
- sendCtxStatusMessage(pi, {
26465
+ sendStatus({
25910
26466
  title: "/ctx-flush",
25911
26467
  text,
25912
26468
  level: result.startsWith("Error:") ? "error" : "success"
@@ -25926,7 +26482,7 @@ function cleanupHistorianStateFile(path6) {
25926
26482
  }
25927
26483
  // ../plugin/src/hooks/magic-context/compartment-runner-historian.ts
25928
26484
  import { mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
25929
- import { join as join4 } from "node:path";
26485
+ import { join as join5 } from "node:path";
25930
26486
 
25931
26487
  // ../plugin/src/agents/historian.ts
25932
26488
  var HISTORIAN_AGENT = "historian";
@@ -26263,8 +26819,8 @@ function isTransientHistorianPromptError2(message) {
26263
26819
  ].some((token) => normalized.includes(token));
26264
26820
  }
26265
26821
  function sleep(ms) {
26266
- return new Promise((resolve2) => {
26267
- setTimeout(resolve2, ms);
26822
+ return new Promise((resolve3) => {
26823
+ setTimeout(resolve3, ms);
26268
26824
  });
26269
26825
  }
26270
26826
  function cleanupHistorianDump(sessionId, dumpPath) {
@@ -26286,7 +26842,7 @@ function dumpHistorianResponse(sessionId, directory, label, text) {
26286
26842
  ensureCortexKitArtifactGitignore(directory);
26287
26843
  const safeSessionId = sanitizeDumpName(sessionId);
26288
26844
  const safeLabel = sanitizeDumpName(label);
26289
- const dumpPath = join4(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
26845
+ const dumpPath = join5(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
26290
26846
  writeFileSync5(dumpPath, text, "utf8");
26291
26847
  sessionLog(sessionId, "compartment agent: historian response dumped", {
26292
26848
  label,
@@ -26319,6 +26875,7 @@ function insertRecompCompartmentRows(db, sessionId, compartments, now) {
26319
26875
  }
26320
26876
  function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
26321
26877
  const now = Date.now();
26878
+ const transactionStartedAt = performance.now();
26322
26879
  db.exec("BEGIN IMMEDIATE");
26323
26880
  let finished = false;
26324
26881
  try {
@@ -26347,6 +26904,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
26347
26904
  clearCachedM0M1(db, sessionId);
26348
26905
  db.exec("COMMIT");
26349
26906
  finished = true;
26907
+ logSlowWriteTransaction("historian-publish:recomp", transactionStartedAt);
26350
26908
  return { compartments: staging.compartments, facts: staging.facts };
26351
26909
  } finally {
26352
26910
  if (!finished) {
@@ -27130,6 +27688,8 @@ function createPiHistorianClient(args) {
27130
27688
  const sessions = new Map;
27131
27689
  let counter = 0;
27132
27690
  async function prompt(input) {
27691
+ if (args.signal?.aborted)
27692
+ throw new Error("prompt aborted by external signal");
27133
27693
  const body = readBody(input);
27134
27694
  const sessionId = readPathId(input);
27135
27695
  if (body.noReply) {
@@ -27150,8 +27710,11 @@ function createPiHistorianClient(args) {
27150
27710
  cwd: args.directory,
27151
27711
  thinkingLevel: modelOverride ? modelOverride.qualifier : args.thinkingLevel,
27152
27712
  accountingSessionId: args.accountingSessionId,
27153
- accountingSubagent: "recomp"
27713
+ accountingSubagent: "recomp",
27714
+ signal: args.signal
27154
27715
  });
27716
+ if (args.signal?.aborted)
27717
+ throw new Error("prompt aborted by external signal");
27155
27718
  if (!result.ok) {
27156
27719
  throw new Error(`Pi recomp historian failed (${result.reason}): ${result.error}`);
27157
27720
  }
@@ -27162,6 +27725,8 @@ function createPiHistorianClient(args) {
27162
27725
  session: {
27163
27726
  get: async () => ({ directory: args.directory }),
27164
27727
  create: async () => {
27728
+ if (args.signal?.aborted)
27729
+ throw new Error("prompt aborted by external signal");
27165
27730
  const id = `magic-context-pi-recomp-${++counter}`;
27166
27731
  sessions.set(id, []);
27167
27732
  return { id };
@@ -27218,16 +27783,13 @@ function makeMessage2(role, text) {
27218
27783
 
27219
27784
  // src/pi-recomp-marker.ts
27220
27785
  function stagePiRecompMarker(args) {
27221
- const readBranchEntries = resolvePiReadBranchEntries2(args.ctx);
27222
- if (!readBranchEntries)
27223
- return;
27224
27786
  const compartments = getCompartments(args.db, args.sessionId);
27225
27787
  const last = compartments[compartments.length - 1];
27226
27788
  if (!last)
27227
27789
  return;
27228
27790
  let firstKeptEntryId = null;
27229
27791
  try {
27230
- firstKeptEntryId = findFirstKeptEntryId(readBranchEntries(), last.endMessage);
27792
+ firstKeptEntryId = findFirstKeptEntryId(args.branchEntries, last.endMessage);
27231
27793
  } catch {
27232
27794
  firstKeptEntryId = null;
27233
27795
  }
@@ -27243,19 +27805,6 @@ function stagePiRecompMarker(args) {
27243
27805
  });
27244
27806
  signalPiDeferredHistoryRefresh(args.sessionId);
27245
27807
  }
27246
- function resolvePiReadBranchEntries2(ctx) {
27247
- const sm = ctx?.sessionManager;
27248
- if (typeof sm?.getBranch !== "function")
27249
- return;
27250
- return () => {
27251
- try {
27252
- const entries = sm.getBranch?.call(sm);
27253
- return Array.isArray(entries) ? entries : [];
27254
- } catch {
27255
- return [];
27256
- }
27257
- };
27258
- }
27259
27808
 
27260
27809
  // src/status-line.ts
27261
27810
  var STATUS_KEY = "magic-context";
@@ -27301,7 +27850,6 @@ function renderStatusText(ctx, db, sessionId) {
27301
27850
  const meta = readSessionMetaStatus(db, sessionId);
27302
27851
  const liveInputTokens = typeof usage?.tokens === "number" && Number.isFinite(usage.tokens) ? usage.tokens : undefined;
27303
27852
  const persistedInputTokens = typeof meta?.last_input_tokens === "number" && Number.isFinite(meta.last_input_tokens) && meta.last_input_tokens >= 0 ? meta.last_input_tokens : undefined;
27304
- const inputTokens = liveInputTokens ?? persistedInputTokens;
27305
27853
  const windowGeometry = resolvePiWindowGeometry({
27306
27854
  rawContextWindow: usage?.contextWindow ?? ctx.model?.contextWindow,
27307
27855
  model: ctx.model,
@@ -27309,8 +27857,14 @@ function renderStatusText(ctx, db, sessionId) {
27309
27857
  persistedInputTokens,
27310
27858
  persistedPercentage: typeof meta?.last_context_percentage === "number" ? meta.last_context_percentage : undefined
27311
27859
  });
27312
- const usableSoft = windowGeometry?.usableSoft;
27313
- const pct = inputTokens !== undefined && usableSoft !== undefined && usableSoft > 0 ? inputTokens / usableSoft * 100 : typeof meta?.last_context_percentage === "number" ? meta.last_context_percentage : undefined;
27860
+ const pressure = liveInputTokens !== undefined || persistedInputTokens !== undefined ? resolvePiPressureSnapshot({
27861
+ persistedPercentage: meta?.last_context_percentage ?? 0,
27862
+ persistedInputTokens: persistedInputTokens ?? 0,
27863
+ liveInputTokens,
27864
+ usableContextLimit: windowGeometry?.usableSoft
27865
+ }) : undefined;
27866
+ const inputTokens = pressure?.inputTokens;
27867
+ const pct = pressure?.percentage;
27314
27868
  const state2 = renderHistorianState(meta, recompSessions.has(sessionId));
27315
27869
  return `mc: ${inputTokens === undefined ? "--" : fmt(inputTokens)} (${pct === undefined ? "--" : `${Math.round(pct)}%`}) · ${state2}`;
27316
27870
  }
@@ -27367,29 +27921,41 @@ var inFlightRecomp = new Map;
27367
27921
  function isPiRecompInFlight(sessionId) {
27368
27922
  return inFlightRecomp.has(sessionId);
27369
27923
  }
27370
- async function awaitInFlightRecomps() {
27371
- if (inFlightRecomp.size === 0)
27924
+ async function awaitInFlightRecomps(sessionId) {
27925
+ const runs = sessionId ? [inFlightRecomp.get(sessionId)?.promise].filter((run) => run !== undefined) : [...inFlightRecomp.values()].map((run) => run.promise);
27926
+ if (runs.length === 0)
27372
27927
  return;
27373
- await Promise.allSettled(Array.from(inFlightRecomp.values()));
27928
+ await Promise.allSettled(runs);
27929
+ }
27930
+ function abortInFlightRecomps(sessionId) {
27931
+ inFlightRecomp.get(sessionId)?.controller.abort();
27374
27932
  }
27375
27933
  function spawnPiRecompRun(args) {
27376
27934
  const { sessionId, provider, onStatusChange, work } = args;
27935
+ const controller = new AbortController;
27377
27936
  const unregister = setRawMessageProvider(sessionId, provider);
27378
27937
  setMagicContextRecompActive(sessionId, true);
27379
- onStatusChange();
27380
- const runPromise = (async () => {
27938
+ let run;
27939
+ const runPromise = Promise.resolve().then(async () => {
27381
27940
  try {
27382
- await work();
27941
+ await work(controller.signal);
27383
27942
  } catch (err) {
27384
- sessionLog(sessionId, `pi recomp run failed (detached): ${err instanceof Error ? err.message : String(err)}`);
27943
+ if (!controller.signal.aborted) {
27944
+ sessionLog(sessionId, `pi recomp run failed (detached): ${err instanceof Error ? err.message : String(err)}`);
27945
+ }
27946
+ }
27947
+ }).finally(() => {
27948
+ if (inFlightRecomp.get(sessionId) === run) {
27949
+ inFlightRecomp.delete(sessionId);
27385
27950
  }
27386
- })().finally(() => {
27387
- inFlightRecomp.delete(sessionId);
27388
27951
  setMagicContextRecompActive(sessionId, false);
27389
27952
  unregister();
27390
- onStatusChange();
27953
+ if (!controller.signal.aborted)
27954
+ onStatusChange();
27391
27955
  });
27392
- inFlightRecomp.set(sessionId, runPromise);
27956
+ run = { promise: runPromise, controller };
27957
+ inFlightRecomp.set(sessionId, run);
27958
+ onStatusChange();
27393
27959
  }
27394
27960
 
27395
27961
  // src/commands/ctx-recomp.ts
@@ -27406,9 +27972,10 @@ function registerCtxRecompCommand(pi, deps) {
27406
27972
  pi.registerCommand("ctx-recomp", {
27407
27973
  description: "Rebuild Magic Context compartments from raw Pi session history",
27408
27974
  handler: async (args, ctx) => {
27975
+ const sendStatus = createCtxStatusSender(pi, ctx);
27409
27976
  const sessionId = resolveSessionId(ctx);
27410
27977
  if (!sessionId) {
27411
- sendCtxStatusMessage(pi, {
27978
+ sendStatus({
27412
27979
  title: "/ctx-recomp",
27413
27980
  text: `## Magic Recomp
27414
27981
 
@@ -27419,7 +27986,7 @@ No active Pi session is available.`,
27419
27986
  }
27420
27987
  const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
27421
27988
  if (currentDeps.compactionOff) {
27422
- sendCtxStatusMessage(pi, {
27989
+ sendStatus({
27423
27990
  title: "/ctx-recomp",
27424
27991
  text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
27425
27992
  level: "warning"
@@ -27428,7 +27995,7 @@ No active Pi session is available.`,
27428
27995
  }
27429
27996
  const parsed = parseRecompArgs(args);
27430
27997
  if (parsed.kind === "error") {
27431
- sendCtxStatusMessage(pi, {
27998
+ sendStatus({
27432
27999
  title: "/ctx-recomp",
27433
28000
  text: `## Magic Recomp — Invalid Arguments
27434
28001
 
@@ -27438,15 +28005,16 @@ ${parsed.message}`,
27438
28005
  return;
27439
28006
  }
27440
28007
  if (parsed.kind === "upgrade") {
27441
- sendCtxStatusMessage(pi, {
28008
+ sendStatus({
27442
28009
  title: "/ctx-recomp",
27443
28010
  text: executeRecompUpgradeStub(currentDeps.db, sessionId),
27444
- level: "info"
28011
+ level: "info",
28012
+ rpcDisplay: "dialog"
27445
28013
  });
27446
28014
  return;
27447
28015
  }
27448
28016
  if (!currentDeps.historianModel) {
27449
- sendCtxStatusMessage(pi, {
28017
+ sendStatus({
27450
28018
  title: "/ctx-recomp",
27451
28019
  text: "## Magic Recomp\n\n/ctx-recomp is unavailable because `historian.model` is not configured.",
27452
28020
  level: "error"
@@ -27463,7 +28031,7 @@ ${parsed.message}`,
27463
28031
  confirmationBySession.delete(sessionId);
27464
28032
  else
27465
28033
  confirmationBySession.set(sessionId, { timestamp: now, argsKey });
27466
- sendCtxStatusMessage(pi, {
28034
+ sendStatus({
27467
28035
  title: "/ctx-recomp",
27468
28036
  text: warning.text,
27469
28037
  level: warning.confirmable ? "warning" : "error"
@@ -27471,7 +28039,7 @@ ${parsed.message}`,
27471
28039
  return;
27472
28040
  }
27473
28041
  if (isWrapupInProgress(currentDeps.db, sessionId)) {
27474
- sendCtxStatusMessage(pi, {
28042
+ sendStatus({
27475
28043
  title: "/ctx-recomp",
27476
28044
  text: "## Magic Recomp\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
27477
28045
  level: "warning"
@@ -27479,7 +28047,7 @@ ${parsed.message}`,
27479
28047
  return;
27480
28048
  }
27481
28049
  if (isPiRecompInFlight(sessionId)) {
27482
- sendCtxStatusMessage(pi, {
28050
+ sendStatus({
27483
28051
  title: "/ctx-recomp",
27484
28052
  text: `## Magic Recomp
27485
28053
 
@@ -27489,7 +28057,7 @@ A recomp or upgrade is already running for this session in the background. Wait
27489
28057
  return;
27490
28058
  }
27491
28059
  confirmationBySession.delete(sessionId);
27492
- sendCtxStatusMessage(pi, {
28060
+ sendStatus({
27493
28061
  title: "/ctx-recomp",
27494
28062
  text: parsed.kind === "partial" ? `## Magic Recomp
27495
28063
 
@@ -27498,17 +28066,21 @@ Partial recomp started for range ${parsed.range.start}-${parsed.range.end}.` : `
27498
28066
  Historian recomp started. Rebuilding compartments and facts from raw Pi session history now.`,
27499
28067
  level: "info"
27500
28068
  });
28069
+ const cwd = ctx.cwd;
28070
+ const fallbackModelId = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
28071
+ const snapshot = readPiSessionSnapshot(ctx);
27501
28072
  const provider = {
27502
- readMessages: () => readPiSessionMessages(ctx)
28073
+ readMessages: () => snapshot.rawMessages
27503
28074
  };
27504
28075
  spawnPiRecompRun({
27505
28076
  sessionId,
27506
28077
  provider,
27507
28078
  onStatusChange: () => updateStatusLine(ctx, {
27508
28079
  db: currentDeps.db,
27509
- projectIdentity: ctx.cwd
28080
+ projectIdentity: cwd
27510
28081
  }),
27511
- work: async () => {
28082
+ work: async (signal) => {
28083
+ const detachedSendStatus = createCtxStatusSender(pi, ctx, signal);
27512
28084
  const result = await executeContextRecompWithResult({
27513
28085
  client: createPiHistorianClient({
27514
28086
  runner: currentDeps.runner,
@@ -27517,10 +28089,11 @@ Historian recomp started. Rebuilding compartments and facts from raw Pi session
27517
28089
  fallbackModels: currentDeps.historianFallbacks,
27518
28090
  timeoutMs: currentDeps.historianTimeoutMs,
27519
28091
  thinkingLevel: currentDeps.historianThinkingLevel,
27520
- directory: ctx.cwd,
28092
+ directory: cwd,
27521
28093
  accountingSessionId: sessionId,
28094
+ signal,
27522
28095
  notify: (text) => {
27523
- sendCtxStatusMessage(pi, {
28096
+ detachedSendStatus({
27524
28097
  title: "/ctx-recomp",
27525
28098
  text,
27526
28099
  level: inferLevel(text)
@@ -27530,15 +28103,17 @@ Historian recomp started. Rebuilding compartments and facts from raw Pi session
27530
28103
  db: currentDeps.db,
27531
28104
  sessionId,
27532
28105
  historianChunkTokens: currentDeps.historianChunkTokens,
27533
- directory: ctx.cwd,
28106
+ directory: cwd,
27534
28107
  historianTimeoutMs: currentDeps.historianTimeoutMs,
27535
28108
  memoryEnabled: currentDeps.memoryEnabled,
27536
28109
  autoPromote: currentDeps.autoPromote,
27537
28110
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
27538
28111
  fallbackModels: currentDeps.historianFallbacks,
27539
28112
  language: currentDeps.language,
27540
- fallbackModelId: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined
28113
+ fallbackModelId
27541
28114
  }, parsed.kind === "partial" ? { range: parsed.range } : {});
28115
+ if (signal.aborted)
28116
+ return;
27542
28117
  if (result.published) {
27543
28118
  try {
27544
28119
  clearEmergencyRecovery(currentDeps.db, sessionId);
@@ -27546,14 +28121,18 @@ Historian recomp started. Rebuilding compartments and facts from raw Pi session
27546
28121
  sessionLog(sessionId, `/ctx-recomp: clearEmergencyRecovery failed (continuing): ${describeError(recoveryError).brief}`);
27547
28122
  }
27548
28123
  try {
27549
- stagePiRecompMarker({ db: currentDeps.db, sessionId, ctx });
28124
+ stagePiRecompMarker({
28125
+ db: currentDeps.db,
28126
+ sessionId,
28127
+ branchEntries: snapshot.branchEntries
28128
+ });
27550
28129
  } catch (markerError) {
27551
28130
  sessionLog(sessionId, `/ctx-recomp: marker staging failed (recomp already published; continuing): ${describeError(markerError).brief}`);
27552
28131
  }
27553
28132
  signalPiDeferredHistoryRefresh(sessionId);
27554
28133
  signalPiDeferredMaterialization(sessionId);
27555
28134
  }
27556
- sendCtxStatusMessage(pi, {
28135
+ detachedSendStatus({
27557
28136
  title: "/ctx-recomp",
27558
28137
  text: result.message,
27559
28138
  level: inferLevel(result.message)
@@ -27785,7 +28364,13 @@ function contextualizeUpgradeReason(reason) {
27785
28364
 
27786
28365
  // src/pi-memory-migration.ts
27787
28366
  var MIGRATION_SYSTEM_PROMPT2 = "You re-organize a software project's long-term memory into a stricter taxonomy. " + "Follow the user instructions exactly. Output ONLY the requested XML blocks, nothing else.";
28367
+ var CANCELLED_OUTCOME = {
28368
+ ran: false,
28369
+ summary: "Memory migration cancelled during session shutdown."
28370
+ };
27788
28371
  async function runPiMemoryMigration(deps) {
28372
+ if (deps.signal?.aborted)
28373
+ return CANCELLED_OUTCOME;
27789
28374
  const projectPath = resolveProjectIdentityForSession(deps.directory, deps.allowHomeProject);
27790
28375
  if (!projectPath) {
27791
28376
  return {
@@ -27823,6 +28408,8 @@ async function runPiMemoryMigration(deps) {
27823
28408
  let parsed = null;
27824
28409
  let lastFailReason = "no output";
27825
28410
  for (let i = 0;i < modelChain.length; i += 1) {
28411
+ if (deps.signal?.aborted)
28412
+ return CANCELLED_OUTCOME;
27826
28413
  const model = modelChain[i];
27827
28414
  if (i > 0) {
27828
28415
  sessionLog(deps.sessionId, `memory-migration: escalating to configured fallback model ${model.model} (${i}/${modelChain.length - 1})`);
@@ -27837,8 +28424,11 @@ async function runPiMemoryMigration(deps) {
27837
28424
  cwd: deps.directory,
27838
28425
  thinkingLevel: model.qualifier,
27839
28426
  accountingSessionId: deps.sessionId,
27840
- accountingSubagent: "recomp"
28427
+ accountingSubagent: "recomp",
28428
+ signal: deps.signal
27841
28429
  });
28430
+ if (deps.signal?.aborted)
28431
+ return CANCELLED_OUTCOME;
27842
28432
  if (!result.ok) {
27843
28433
  lastFailReason = `historian ${result.reason}`;
27844
28434
  continue;
@@ -27869,6 +28459,8 @@ async function runPiMemoryMigration(deps) {
27869
28459
  summary: "Memory migration skipped: the model extracted user traits but user memories are disabled. Enable `dreamer.user_memories` so they can be preserved, then re-run /ctx-session-upgrade."
27870
28460
  };
27871
28461
  }
28462
+ if (deps.signal?.aborted)
28463
+ return CANCELLED_OUTCOME;
27872
28464
  let routed = 0;
27873
28465
  if (deps.userMemoriesEnabled && parsed.userObservations.length > 0) {
27874
28466
  insertUserMemoryCandidates(deps.db, parsed.userObservations.map((content) => ({
@@ -27893,9 +28485,10 @@ function registerCtxSessionUpgradeCommand(pi, deps) {
27893
28485
  pi.registerCommand("ctx-session-upgrade", {
27894
28486
  description: "Upgrade this session to the current Magic Context history format and re-organize project memories",
27895
28487
  handler: async (_args, ctx) => {
28488
+ const sendStatus = createCtxStatusSender(pi, ctx);
27896
28489
  const sessionId = resolveSessionId(ctx);
27897
28490
  if (!sessionId) {
27898
- sendCtxStatusMessage(pi, {
28491
+ sendStatus({
27899
28492
  title: "/ctx-session-upgrade",
27900
28493
  text: `## Session Upgrade
27901
28494
 
@@ -27906,7 +28499,7 @@ No active Pi session is available.`,
27906
28499
  }
27907
28500
  const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
27908
28501
  if (currentDeps.compactionOff) {
27909
- sendCtxStatusMessage(pi, {
28502
+ sendStatus({
27910
28503
  title: "/ctx-session-upgrade",
27911
28504
  text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
27912
28505
  level: "warning"
@@ -27914,7 +28507,7 @@ No active Pi session is available.`,
27914
28507
  return;
27915
28508
  }
27916
28509
  if (!currentDeps.historianModel) {
27917
- sendCtxStatusMessage(pi, {
28510
+ sendStatus({
27918
28511
  title: "/ctx-session-upgrade",
27919
28512
  text: "## Session Upgrade\n\nUnavailable because `historian.model` is not configured.",
27920
28513
  level: "error"
@@ -27922,7 +28515,7 @@ No active Pi session is available.`,
27922
28515
  return;
27923
28516
  }
27924
28517
  if (isWrapupInProgress(currentDeps.db, sessionId)) {
27925
- sendCtxStatusMessage(pi, {
28518
+ sendStatus({
27926
28519
  title: "/ctx-session-upgrade",
27927
28520
  text: `## Session Upgrade
27928
28521
 
@@ -27932,7 +28525,7 @@ No active Pi session is available.`,
27932
28525
  return;
27933
28526
  }
27934
28527
  if (isPiRecompInFlight(sessionId)) {
27935
- sendCtxStatusMessage(pi, {
28528
+ sendStatus({
27936
28529
  title: "/ctx-session-upgrade",
27937
28530
  text: `## Session Upgrade
27938
28531
 
@@ -27943,9 +28536,10 @@ An upgrade or recomp is already running for this session in the background. Wait
27943
28536
  }
27944
28537
  const compartments = getCompartments(currentDeps.db, sessionId);
27945
28538
  const upgradableCount = compartments.filter((c) => c.legacy === 1 || !c.p1 || c.p1.trim() === "").length;
28539
+ const cwd = ctx.cwd;
27946
28540
  const sessionMainModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
27947
28541
  const migrationEnabled = currentDeps.memoryEnabled;
27948
- const runMigration = async () => {
28542
+ const runMigration = async (signal) => {
27949
28543
  if (!migrationEnabled) {
27950
28544
  return "Memory migration skipped (memory disabled).";
27951
28545
  }
@@ -27958,24 +28552,27 @@ An upgrade or recomp is already running for this session in the background. Wait
27958
28552
  fallbackModels: currentDeps.historianFallbacks,
27959
28553
  timeoutMs: currentDeps.historianTimeoutMs,
27960
28554
  thinkingLevel: currentDeps.historianThinkingLevel,
27961
- directory: ctx.cwd,
28555
+ directory: cwd,
27962
28556
  allowHomeProject: currentDeps.allowHomeProject,
27963
28557
  sessionId,
27964
28558
  userMemoriesEnabled: currentDeps.userMemoriesEnabled,
27965
- language: currentDeps.language
28559
+ language: currentDeps.language,
28560
+ signal
27966
28561
  });
27967
28562
  return outcome.summary;
27968
28563
  } catch (error) {
28564
+ if (signal.aborted)
28565
+ return "Memory migration cancelled.";
27969
28566
  return `Memory migration skipped (error): ${describeError(error).brief}`;
27970
28567
  }
27971
28568
  };
27972
28569
  if (upgradableCount === 0) {
27973
- const projectPath = resolveProjectIdentityForSession(ctx.cwd, currentDeps.allowHomeProject);
28570
+ const projectPath = resolveProjectIdentityForSession(cwd, currentDeps.allowHomeProject);
27974
28571
  if (!projectPath)
27975
28572
  return;
27976
28573
  const migrationPending = migrationEnabled && !isMemoryMigrationDone(currentDeps.db, projectPath);
27977
28574
  if (!migrationPending) {
27978
- sendCtxStatusMessage(pi, {
28575
+ sendStatus({
27979
28576
  title: "/ctx-session-upgrade",
27980
28577
  text: [
27981
28578
  "## Session Upgrade — Already Up To Date",
@@ -27983,56 +28580,64 @@ An upgrade or recomp is already running for this session in the background. Wait
27983
28580
  compartments.length === 0 ? "This session has no compartment history to upgrade yet." : "This session's compartments are already in the current format."
27984
28581
  ].join(`
27985
28582
  `),
27986
- level: "info"
28583
+ level: "info",
28584
+ rpcDisplay: "dialog"
27987
28585
  });
27988
28586
  return;
27989
28587
  }
27990
- sendCtxStatusMessage(pi, {
28588
+ sendStatus({
27991
28589
  title: "/ctx-session-upgrade",
27992
28590
  text: `## Session Upgrade
27993
28591
 
27994
28592
  Compartments are already current. Re-organizing project memories. This may take a while.`,
27995
28593
  level: "info"
27996
28594
  });
28595
+ const snapshot2 = readPiSessionSnapshot(ctx);
27997
28596
  spawnPiRecompRun({
27998
28597
  sessionId,
27999
28598
  provider: {
28000
- readMessages: () => readPiSessionMessages(ctx)
28599
+ readMessages: () => snapshot2.rawMessages
28001
28600
  },
28002
28601
  onStatusChange: () => updateStatusLine(ctx, {
28003
28602
  db: currentDeps.db,
28004
- projectIdentity: ctx.cwd
28603
+ projectIdentity: cwd
28005
28604
  }),
28006
- work: async () => {
28007
- const summary = await runMigration();
28008
- sendCtxStatusMessage(pi, {
28605
+ work: async (signal) => {
28606
+ const detachedSendStatus = createCtxStatusSender(pi, ctx, signal);
28607
+ const summary = await runMigration(signal);
28608
+ if (signal.aborted)
28609
+ return;
28610
+ detachedSendStatus({
28009
28611
  title: "/ctx-session-upgrade",
28010
28612
  text: ["## Session Upgrade — Complete", "", summary].join(`
28011
28613
  `),
28012
- level: "info"
28614
+ level: "info",
28615
+ rpcDisplay: "dialog"
28013
28616
  });
28014
28617
  }
28015
28618
  });
28016
28619
  return;
28017
28620
  }
28018
- sendCtxStatusMessage(pi, {
28621
+ sendStatus({
28019
28622
  title: "/ctx-session-upgrade",
28020
28623
  text: `## Session Upgrade
28021
28624
 
28022
28625
  Rebuilding compartments into the v2 format and re-organizing project memories. This may take a while.`,
28023
28626
  level: "info"
28024
28627
  });
28628
+ const snapshot = readPiSessionSnapshot(ctx);
28025
28629
  const provider = {
28026
- readMessages: () => readPiSessionMessages(ctx)
28630
+ readMessages: () => snapshot.rawMessages
28027
28631
  };
28028
28632
  spawnPiRecompRun({
28029
28633
  sessionId,
28030
28634
  provider,
28031
28635
  onStatusChange: () => updateStatusLine(ctx, {
28032
28636
  db: currentDeps.db,
28033
- projectIdentity: ctx.cwd
28637
+ projectIdentity: cwd
28034
28638
  }),
28035
- work: async () => {
28639
+ work: async (signal) => {
28640
+ const detachedSendStatus = createCtxStatusSender(pi, ctx, signal);
28036
28641
  const recompResult = await executeContextRecompWithResult({
28037
28642
  client: createPiHistorianClient({
28038
28643
  runner: currentDeps.runner,
@@ -28040,10 +28645,11 @@ Rebuilding compartments into the v2 format and re-organizing project memories. T
28040
28645
  fallbackModels: currentDeps.historianFallbacks,
28041
28646
  timeoutMs: currentDeps.historianTimeoutMs,
28042
28647
  thinkingLevel: currentDeps.historianThinkingLevel,
28043
- directory: ctx.cwd,
28648
+ directory: cwd,
28044
28649
  accountingSessionId: sessionId,
28650
+ signal,
28045
28651
  systemPrompt: withContentLanguageDirective(COMPARTMENT_STRUCTURAL_SYSTEM_PROMPT, currentDeps.language, { preserveUserQuotes: true }),
28046
- notify: (text) => sendCtxStatusMessage(pi, {
28652
+ notify: (text) => detachedSendStatus({
28047
28653
  title: "/ctx-session-upgrade",
28048
28654
  text,
28049
28655
  level: "info"
@@ -28052,7 +28658,7 @@ Rebuilding compartments into the v2 format and re-organizing project memories. T
28052
28658
  db: currentDeps.db,
28053
28659
  sessionId,
28054
28660
  historianChunkTokens: currentDeps.historianChunkTokens,
28055
- directory: ctx.cwd,
28661
+ directory: cwd,
28056
28662
  historianTimeoutMs: currentDeps.historianTimeoutMs,
28057
28663
  memoryEnabled: currentDeps.memoryEnabled,
28058
28664
  autoPromote: currentDeps.autoPromote,
@@ -28061,9 +28667,11 @@ Rebuilding compartments into the v2 format and re-organizing project memories. T
28061
28667
  fallbackModelId: sessionMainModel,
28062
28668
  language: currentDeps.language
28063
28669
  }, {});
28670
+ if (signal.aborted)
28671
+ return;
28064
28672
  if (!recompResult.published || !isRecompComplete(recompResult.message)) {
28065
28673
  const reason = contextualizeUpgradeReason(isRecompFailure(recompResult.message) ? extractRecompReason(recompResult.message) : `Compartments were not fully rebuilt: ${extractRecompReason(recompResult.message)}`);
28066
- sendCtxStatusMessage(pi, {
28674
+ detachedSendStatus({
28067
28675
  title: "/ctx-session-upgrade",
28068
28676
  text: `## Session Upgrade — Incomplete
28069
28677
 
@@ -28073,14 +28681,20 @@ ${reason}`,
28073
28681
  return;
28074
28682
  }
28075
28683
  try {
28076
- stagePiRecompMarker({ db: currentDeps.db, sessionId, ctx });
28684
+ stagePiRecompMarker({
28685
+ db: currentDeps.db,
28686
+ sessionId,
28687
+ branchEntries: snapshot.branchEntries
28688
+ });
28077
28689
  } catch (markerError) {
28078
28690
  sessionLog(sessionId, `pi /ctx-session-upgrade marker staging failed (non-fatal, recomp already published): ${describeError(markerError).brief}`);
28079
28691
  }
28080
28692
  signalPiDeferredHistoryRefresh(sessionId);
28081
28693
  signalPiDeferredMaterialization(sessionId);
28082
- const migrationSummary = await runMigration();
28083
- sendCtxStatusMessage(pi, {
28694
+ const migrationSummary = await runMigration(signal);
28695
+ if (signal.aborted)
28696
+ return;
28697
+ detachedSendStatus({
28084
28698
  title: "/ctx-session-upgrade",
28085
28699
  text: [
28086
28700
  "## Session Upgrade — Complete",
@@ -28092,7 +28706,8 @@ ${migrationSummary}` : "",
28092
28706
  recompResult.message
28093
28707
  ].join(`
28094
28708
  `),
28095
- level: "info"
28709
+ level: "info",
28710
+ rpcDisplay: "dialog"
28096
28711
  });
28097
28712
  }
28098
28713
  });
@@ -28118,6 +28733,9 @@ function formatThresholdClampNote(opts) {
28118
28733
  return ` [clamped: ${opts.configuredValue}% > ${opts.maxPercentage}%]`;
28119
28734
  }
28120
28735
 
28736
+ // ../plugin/src/shared/rust-mode-status.ts
28737
+ var RUST_MODE_HOST_PATHS_LINE = "Host backends → MODULE: ctx_memory, ctx_note; historian: module-side";
28738
+
28121
28739
  // ../plugin/src/shared/tail-hygiene-status.ts
28122
28740
  function finiteNumber(value, fallback = 0) {
28123
28741
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
@@ -28184,7 +28802,7 @@ function formatExecuteThreshold(detail, contextLimit) {
28184
28802
  }
28185
28803
  return `${percentage}%${clampNote}`;
28186
28804
  }
28187
- function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageConfig = DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, liveModelKey, historyBudgetPercentage, commitClusterTrigger, executeThresholdTokens, contextLimit, dreamer, windowGeometry, tailHygiene) {
28805
+ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageConfig = DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, liveModelKey, historyBudgetPercentage, commitClusterTrigger, executeThresholdTokens, contextLimit, dreamer, windowGeometry, tailHygiene, contextUsage, rustMode = false) {
28188
28806
  const thresholdDetail = resolveExecuteThresholdDetail(executeThresholdPercentageConfig, liveModelKey, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, {
28189
28807
  tokensConfig: executeThresholdTokens,
28190
28808
  contextLimit,
@@ -28209,7 +28827,9 @@ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageC
28209
28827
  const remainingMs = Math.max(0, ttlMs - elapsed);
28210
28828
  const cacheExpired = remainingMs === 0 && meta.lastResponseTime > 0;
28211
28829
  const proactiveCompartmentTrigger = getProactiveCompartmentTriggerPercentage(executeThresholdPercentage);
28212
- const displayContextLimit = contextLimit && contextLimit > 0 ? contextLimit : meta.lastContextPercentage > 0 ? Math.round(meta.lastInputTokens / (meta.lastContextPercentage / 100)) : 0;
28830
+ const displayInputTokens = contextUsage?.inputTokens ?? meta.lastInputTokens;
28831
+ const displayPercentage = contextUsage?.percentage ?? meta.lastContextPercentage;
28832
+ const displayContextLimit = contextLimit && contextLimit > 0 ? contextLimit : displayPercentage > 0 ? Math.round(displayInputTokens / (displayPercentage / 100)) : 0;
28213
28833
  const lines = [
28214
28834
  "## Magic Status",
28215
28835
  "",
@@ -28234,11 +28854,15 @@ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageC
28234
28854
  "",
28235
28855
  "### Execute Threshold",
28236
28856
  `- Execute threshold: ${formatExecuteThreshold(thresholdDetail, displayContextLimit)}`,
28237
- `- Last input tokens: ${meta.lastInputTokens.toLocaleString()} tokens`,
28857
+ `- Last input tokens: ${displayInputTokens.toLocaleString()} tokens`,
28238
28858
  "",
28239
28859
  `**Protected tags:** ${protectedTags}`,
28240
28860
  `**Subagent session:** ${meta.isSubagent}`
28241
28861
  ];
28862
+ const storage = getMagicContextStorageResolution();
28863
+ lines.push("", `**Storage:** ${storage.path} (${storage.source})`);
28864
+ if (rustMode)
28865
+ lines.push("", "### Rust Mode", `- ${RUST_MODE_HOST_PATHS_LINE}`);
28242
28866
  if (tailHygiene !== undefined) {
28243
28867
  lines.push("", "### Tail Hygiene", `- Reclaimable / eligible: ${formatTailHygiene(tailHygiene)}`, "- Reasoning is excluded from both terms.");
28244
28868
  }
@@ -28247,8 +28871,8 @@ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageC
28247
28871
  `- Running: ${dreamer.progress.task} — ${dreamer.progress.processed}/${dreamer.progress.total} processed`
28248
28872
  ] : [], ...formatDreamTaskBacklogs(dreamer.backlog).split("\\n"));
28249
28873
  }
28250
- if (meta.lastContextPercentage > 0 || meta.lastInputTokens > 0) {
28251
- lines.push("", "### Context Usage", `- Last percentage: ${meta.lastContextPercentage.toFixed(1)}%`, `- Last input tokens: ${meta.lastInputTokens.toLocaleString()}`, `- Resolved context limit: ${displayContextLimit > 0 ? displayContextLimit.toLocaleString() : "unknown"}`, ...windowGeometry ? [`- ${formatWindowDerivationLine(meta.lastInputTokens, windowGeometry)}`] : [], `- Proactive compartment evaluation: ${proactiveCompartmentTrigger}%`, `- Post-drop target for historian: ${(executeThresholdPercentage * POST_DROP_TARGET_RATIO).toFixed(0)}% (${executeThresholdPercentage}% * ${POST_DROP_TARGET_RATIO})`, `- Commit cluster trigger: ${commitClusterTrigger?.enabled !== false ? `enabled (min ${commitClusterTrigger?.min_clusters ?? 3} clusters)` : "disabled"}, tail-size trigger: > 3x compartment budget`);
28874
+ if (displayPercentage > 0 || displayInputTokens > 0) {
28875
+ lines.push("", "### Context Usage", `- Last percentage: ${displayPercentage.toFixed(1)}%`, `- Last input tokens: ${displayInputTokens.toLocaleString()}`, `- Resolved context limit: ${displayContextLimit > 0 ? displayContextLimit.toLocaleString() : "unknown"}`, ...windowGeometry ? [`- ${formatWindowDerivationLine(displayInputTokens, windowGeometry)}`] : [], `- Proactive compartment evaluation: ${proactiveCompartmentTrigger}%`, `- Post-drop target for historian: ${(executeThresholdPercentage * POST_DROP_TARGET_RATIO).toFixed(0)}% (${executeThresholdPercentage}% * ${POST_DROP_TARGET_RATIO})`, `- Commit cluster trigger: ${commitClusterTrigger?.enabled !== false ? `enabled (min ${commitClusterTrigger?.min_clusters ?? 3} clusters)` : "disabled"}, tail-size trigger: > 3x compartment budget`);
28252
28876
  }
28253
28877
  const compartments = getCompartments(db, sessionId);
28254
28878
  let historyBlockTokens = 0;
@@ -28285,7 +28909,7 @@ ${c.content}
28285
28909
 
28286
28910
  // src/dialogs/status-dialog.ts
28287
28911
  import {
28288
- matchesKey,
28912
+ matchesKey as matchesKey2,
28289
28913
  truncateToWidth,
28290
28914
  visibleWidth
28291
28915
  } from "@earendil-works/pi-tui";
@@ -28349,7 +28973,7 @@ ${c.content}
28349
28973
  // package.json
28350
28974
  var package_default = {
28351
28975
  name: "@cortexkit/pi-magic-context",
28352
- version: "0.40.1",
28976
+ version: "0.41.0",
28353
28977
  type: "module",
28354
28978
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
28355
28979
  main: "dist/index.js",
@@ -28377,7 +29001,7 @@ var package_default = {
28377
29001
  "README.md"
28378
29002
  ],
28379
29003
  scripts: {
28380
- build: "tsc -p ../retina-local-fs/tsconfig.build.json && rm -f dist/index.js dist/subagent-entry.js dist/*-*.js && bun build src/index.ts src/subagent-entry.ts --outdir dist --target node --format esm --splitting --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external @huggingface/transformers --external node:sqlite",
29004
+ build: "tsc -p ../retina-local-fs/tsconfig.build.json && rm -f dist/index.js dist/subagent-entry.js dist/historian-calibration-extension.js dist/*-*.js && bun build src/index.ts src/subagent-entry.ts src/historian-calibration-extension.ts --outdir dist --target node --format esm --splitting --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external @huggingface/transformers --external node:sqlite",
28381
29005
  "build:e2e-argv": "bun build src/subagent-runner.ts --outfile ../../tests/docker/.generated/subagent-runner-e2e.mjs --target node --format esm --external node:sqlite",
28382
29006
  typecheck: "tsc -p ../retina-local-fs/tsconfig.build.json && tsc --noEmit",
28383
29007
  test: "bun install --frozen-lockfile && bun test",
@@ -28485,7 +29109,7 @@ class StatusDialogComponent {
28485
29109
  }, REFRESH_INTERVAL_MS);
28486
29110
  }
28487
29111
  handleInput(data) {
28488
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c") || matchesKey(data, "return")) {
29112
+ if (matchesKey2(data, "escape") || matchesKey2(data, "ctrl+c") || matchesKey2(data, "return")) {
28489
29113
  this.close();
28490
29114
  }
28491
29115
  }
@@ -28586,8 +29210,6 @@ function drawBorder(inner, width, theme) {
28586
29210
  function buildPiStatusDetail(pi, ctx, deps, sessionId) {
28587
29211
  const usage = ctx.getContextUsage?.();
28588
29212
  const meta = getOrCreateSessionMeta(deps.db, sessionId);
28589
- const hasPersistedPressure = meta.lastInputTokens > 0 && meta.lastContextPercentage > 0;
28590
- const inputTokens = hasPersistedPressure ? meta.lastInputTokens : typeof usage?.tokens === "number" ? usage.tokens : 0;
28591
29213
  let detectedContextLimit;
28592
29214
  try {
28593
29215
  const detected = getOverflowState(deps.db, sessionId).detectedContextLimit;
@@ -28601,8 +29223,15 @@ function buildPiStatusDetail(pi, ctx, deps, sessionId) {
28601
29223
  persistedInputTokens: meta.lastInputTokens,
28602
29224
  persistedPercentage: meta.lastContextPercentage
28603
29225
  });
28604
- const contextLimit = windowGeometry?.usableSoft ?? 0;
28605
- const usagePercentage = contextLimit > 0 && inputTokens > 0 ? inputTokens / contextLimit * 100 : meta.lastContextPercentage;
29226
+ const pressure = resolvePiPressureSnapshot({
29227
+ persistedPercentage: meta.lastContextPercentage,
29228
+ persistedInputTokens: meta.lastInputTokens,
29229
+ liveInputTokens: usage?.tokens,
29230
+ usableContextLimit: windowGeometry?.usableSoft
29231
+ });
29232
+ const inputTokens = pressure.inputTokens;
29233
+ const contextLimit = pressure.contextLimit ?? 0;
29234
+ const usagePercentage = pressure.percentage;
28606
29235
  const compartments = getCompartments(deps.db, sessionId);
28607
29236
  const metaRow = readSessionMetaRow(deps.db, sessionId);
28608
29237
  const memoryBlockCount = Number(metaRow?.memory_block_count ?? 0);
@@ -28859,12 +29488,13 @@ function registerCtxStatusCommand(pi, deps) {
28859
29488
  pi.registerCommand("ctx-status", {
28860
29489
  description: "Show Magic Context status for the current Pi session",
28861
29490
  handler: async (_args, ctx) => {
29491
+ const sendStatus = createCtxStatusSender(pi, ctx);
28862
29492
  const runtimeDeps = deps.resolveStatusDeps?.(ctx) ?? deps;
28863
29493
  const projectIdentity = runtimeDeps.resolveProject?.(ctx).projectIdentity ?? runtimeDeps.projectIdentity;
28864
29494
  const currentDeps = { ...runtimeDeps, projectIdentity };
28865
29495
  const sessionId = resolveSessionId(ctx);
28866
29496
  if (!sessionId) {
28867
- sendCtxStatusMessage(pi, {
29497
+ sendStatus({
28868
29498
  title: "/ctx-status",
28869
29499
  text: `## Magic Status
28870
29500
 
@@ -28895,20 +29525,30 @@ No active Pi session is available.`,
28895
29525
  persistedPercentage: meta.lastContextPercentage
28896
29526
  });
28897
29527
  const usableContextLimit = windowGeometry?.usableSoft;
29528
+ const pressure = resolvePiPressureSnapshot({
29529
+ persistedPercentage: meta.lastContextPercentage,
29530
+ persistedInputTokens: meta.lastInputTokens,
29531
+ liveInputTokens: usage?.tokens,
29532
+ usableContextLimit
29533
+ });
28898
29534
  const statusText = executeStatus(currentDeps.db, sessionId, currentDeps.protectedTags ?? 20, currentDeps.executeThresholdPercentage, modelKey, currentDeps.historyBudgetPercentage, currentDeps.commitClusterTrigger, currentDeps.executeThresholdTokens, usableContextLimit, {
28899
29535
  backlog: getDreamTaskBacklogs(currentDeps.db, currentDeps.projectIdentity, CANONICAL_DREAM_TASKS)
28900
- }, windowGeometry, resolveTailHygieneStatus(getPiChannel1Baseline(sessionId)));
29536
+ }, windowGeometry, resolveTailHygieneStatus(getPiChannel1Baseline(sessionId)), pressure);
28901
29537
  const details = buildStatusDetails(currentDeps, sessionId);
28902
29538
  const profileStatus = currentDeps.activeProfile ?? "none";
28903
- sendCtxStatusMessage(pi, {
29539
+ const storage = getMagicContextStorageResolution();
29540
+ sendStatus({
28904
29541
  title: "/ctx-status",
28905
29542
  text: `${statusText}
28906
29543
 
28907
- Active profile: ${profileStatus}`,
28908
- level: "info"
29544
+ Active profile: ${profileStatus}
29545
+
29546
+ Storage: ${storage.path} (${storage.source})`,
29547
+ level: "info",
29548
+ rpcDisplay: "dialog"
28909
29549
  }, details);
28910
29550
  } catch (error) {
28911
- sendCtxStatusMessage(pi, {
29551
+ sendStatus({
28912
29552
  title: "/ctx-status",
28913
29553
  text: `## Magic Status — Failed
28914
29554
 
@@ -28996,9 +29636,10 @@ function registerCtxWrapupCommand(pi, deps) {
28996
29636
  pi.registerCommand("ctx-wrapup", {
28997
29637
  description: "Compact older Magic Context history while keeping the newest messages raw",
28998
29638
  handler: async (args, ctx) => {
29639
+ const sendStatus = createCtxStatusSender(pi, ctx);
28999
29640
  const sessionId = resolveSessionId(ctx);
29000
29641
  if (!sessionId) {
29001
- sendCtxStatusMessage(pi, {
29642
+ sendStatus({
29002
29643
  title: "/ctx-wrapup",
29003
29644
  text: `## Magic Wrapup
29004
29645
 
@@ -29009,7 +29650,7 @@ No active Pi session is available.`,
29009
29650
  }
29010
29651
  const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
29011
29652
  if (currentDeps.compactionOff) {
29012
- sendCtxStatusMessage(pi, {
29653
+ sendStatus({
29013
29654
  title: "/ctx-wrapup",
29014
29655
  text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
29015
29656
  level: "warning"
@@ -29018,7 +29659,7 @@ No active Pi session is available.`,
29018
29659
  }
29019
29660
  const sessionMeta = getOrCreateSessionMeta(currentDeps.db, sessionId);
29020
29661
  if (sessionMeta.isSubagent) {
29021
- sendCtxStatusMessage(pi, {
29662
+ sendStatus({
29022
29663
  title: "/ctx-wrapup",
29023
29664
  text: `## Magic Wrapup — Skipped
29024
29665
 
@@ -29028,7 +29669,7 @@ No active Pi session is available.`,
29028
29669
  return;
29029
29670
  }
29030
29671
  if (!currentDeps.historianModel) {
29031
- sendCtxStatusMessage(pi, {
29672
+ sendStatus({
29032
29673
  title: "/ctx-wrapup",
29033
29674
  text: "## Magic Wrapup\n\n/ctx-wrapup is unavailable because `historian.model` is not configured.",
29034
29675
  level: "error"
@@ -29037,7 +29678,7 @@ No active Pi session is available.`,
29037
29678
  }
29038
29679
  const parsed = parseWrapupArgs(args);
29039
29680
  if (!parsed.ok) {
29040
- sendCtxStatusMessage(pi, {
29681
+ sendStatus({
29041
29682
  title: "/ctx-wrapup",
29042
29683
  text: `## Magic Wrapup — Invalid Arguments
29043
29684
 
@@ -29047,7 +29688,7 @@ ${parsed.message}`,
29047
29688
  return;
29048
29689
  }
29049
29690
  const result = await runPiWrapup(pi, currentDeps, ctx, sessionId, parsed.messagesToKeep);
29050
- sendCtxStatusMessage(pi, {
29691
+ sendStatus({
29051
29692
  title: "/ctx-wrapup",
29052
29693
  text: result,
29053
29694
  level: result.includes("Failed") || result.includes("Partial") ? "error" : "success"
@@ -29056,6 +29697,7 @@ ${parsed.message}`,
29056
29697
  });
29057
29698
  }
29058
29699
  async function runPiWrapup(pi, deps, ctx, sessionId, messagesToKeep) {
29700
+ const sendStatus = createCtxStatusSender(pi, ctx);
29059
29701
  if (getOrCreateSessionMeta(deps.db, sessionId).isSubagent) {
29060
29702
  return `## Magic Wrapup — Skipped
29061
29703
 
@@ -29135,7 +29777,7 @@ No runnable wrapup boundary is available yet; wrapped up 0 messages into 0 compa
29135
29777
  }
29136
29778
  }, 60000);
29137
29779
  try {
29138
- sendCtxStatusMessage(pi, {
29780
+ sendStatus({
29139
29781
  title: "/ctx-wrapup",
29140
29782
  text: `## Magic Wrapup
29141
29783
 
@@ -29191,7 +29833,7 @@ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleS
29191
29833
  failure = `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
29192
29834
  break;
29193
29835
  }
29194
- sendCtxStatusMessage(pi, {
29836
+ sendStatus({
29195
29837
  title: "/ctx-wrapup",
29196
29838
  text: `## Magic Wrapup
29197
29839
 
@@ -29246,7 +29888,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
29246
29888
  language: deps.language,
29247
29889
  compartmentLeaseHolderId: leaseHolder,
29248
29890
  readBranchEntries: () => readBranchEntries(ctx),
29249
- notifyIssue: (text) => sendCtxStatusMessage(pi, {
29891
+ notifyIssue: (text) => sendStatus({
29250
29892
  title: "/ctx-wrapup",
29251
29893
  text,
29252
29894
  level: "warning"
@@ -29323,7 +29965,7 @@ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarke
29323
29965
  return { ok: true, holderId };
29324
29966
  if (!renewWrapupMarker({}))
29325
29967
  return { ok: false, reason: "ownership_lost" };
29326
- await new Promise((resolve2) => setTimeout(resolve2, Math.min(LEASE_WAIT_MS, remainingMs())));
29968
+ await new Promise((resolve3) => setTimeout(resolve3, Math.min(LEASE_WAIT_MS, remainingMs())));
29327
29969
  }
29328
29970
  }
29329
29971
  function estimateChunks(tokens, chunkTokens) {
@@ -29400,42 +30042,6 @@ function registerPiFailClosedSurface(pi, args) {
29400
30042
  log(`${PREFIX} fail-closed blocking surface registered (${args.reason.kind}); primary turns will error until storage recovers or the build is upgraded`);
29401
30043
  }
29402
30044
 
29403
- // src/pi-pressure.ts
29404
- function extractAssistantUsage(message) {
29405
- if (!message || typeof message !== "object")
29406
- return null;
29407
- const m = message;
29408
- if (m.role !== "assistant")
29409
- return null;
29410
- if (!m.usage || typeof m.usage !== "object")
29411
- return null;
29412
- const u = m.usage;
29413
- const result = {};
29414
- if (typeof u.input === "number")
29415
- result.input = u.input;
29416
- if (typeof u.output === "number")
29417
- result.output = u.output;
29418
- if (typeof u.cacheRead === "number")
29419
- result.cacheRead = u.cacheRead;
29420
- if (typeof u.cacheWrite === "number")
29421
- result.cacheWrite = u.cacheWrite;
29422
- if (typeof u.totalTokens === "number")
29423
- result.totalTokens = u.totalTokens;
29424
- return result;
29425
- }
29426
- function computePiPressure(usage, contextLimit) {
29427
- if (!usage)
29428
- return null;
29429
- const input = usage.input ?? 0;
29430
- const cacheRead = usage.cacheRead ?? 0;
29431
- const cacheWrite = usage.cacheWrite ?? 0;
29432
- const inputTokens = input + cacheRead + cacheWrite;
29433
- if (inputTokens === 0)
29434
- return null;
29435
- const percentage = contextLimit > 0 ? inputTokens / contextLimit * 100 : 0;
29436
- return { inputTokens, percentage };
29437
- }
29438
-
29439
30045
  // src/strip-tag-prefix.ts
29440
30046
  function stripTagPrefixFromAssistantMessage(message) {
29441
30047
  if (message.role !== "assistant")
@@ -29461,19 +30067,43 @@ function stripTagPrefixFromAssistantMessage(message) {
29461
30067
 
29462
30068
  // src/index.ts
29463
30069
  var PREFIX2 = "[magic-context][pi]";
29464
- var PI_ACTIVE_LATCH = Symbol.for("magic-context.pi.active");
29465
- function isPiMagicContextActiveInProcess() {
29466
- return globalThis[PI_ACTIVE_LATCH] === true;
30070
+ var PI_CHILD_INIT_CONTEXT = Symbol.for("magic-context.pi.child-init-context");
30071
+ var SUBAGENT_CHILD_SESSION_CREATED = "subagents:child:session-created";
30072
+ var SUBAGENT_CHILD_DISPOSED = "subagents:child:disposed";
30073
+ var PI_STARTUP_MAINTENANCE_SCHEDULED = Symbol.for("magic-context.pi.startup-maintenance-scheduled");
30074
+ function getPiChildInitContext() {
30075
+ const globals = globalThis;
30076
+ const existing = globals[PI_CHILD_INIT_CONTEXT];
30077
+ if (existing instanceof AsyncLocalStorage)
30078
+ return existing;
30079
+ const context = new AsyncLocalStorage;
30080
+ globals[PI_CHILD_INIT_CONTEXT] = context;
30081
+ return context;
29467
30082
  }
29468
- function markPiMagicContextActive() {
29469
- globalThis[PI_ACTIVE_LATCH] = true;
30083
+ function isPiInProcessSubagentInit() {
30084
+ return getPiChildInitContext().getStore() === true;
29470
30085
  }
29471
- function clearPiMagicContextActive() {
29472
- try {
29473
- delete globalThis[PI_ACTIVE_LATCH];
29474
- } catch {
29475
- globalThis[PI_ACTIVE_LATCH] = undefined;
29476
- }
30086
+ function clearPiInProcessSubagentInitContext() {
30087
+ getPiChildInitContext().enterWith(false);
30088
+ }
30089
+ function registerPiSubagentInitContext(pi) {
30090
+ const context = getPiChildInitContext();
30091
+ const unsubscribeCreated = pi.events.on(SUBAGENT_CHILD_SESSION_CREATED, () => context.enterWith(true));
30092
+ const unsubscribeDisposed = pi.events.on(SUBAGENT_CHILD_DISPOSED, () => context.enterWith(false));
30093
+ return () => {
30094
+ unsubscribeCreated();
30095
+ unsubscribeDisposed();
30096
+ };
30097
+ }
30098
+ function claimPiStartupMaintenance() {
30099
+ const globals = globalThis;
30100
+ if (globals[PI_STARTUP_MAINTENANCE_SCHEDULED] === true)
30101
+ return false;
30102
+ globals[PI_STARTUP_MAINTENANCE_SCHEDULED] = true;
30103
+ return true;
30104
+ }
30105
+ function clearPiStartupMaintenanceClaim() {
30106
+ delete globalThis[PI_STARTUP_MAINTENANCE_SCHEDULED];
29477
30107
  }
29478
30108
  function resolveCurrentProject(ctx, allowHomeProject = false) {
29479
30109
  const projectDir = ctx.cwd;
@@ -29587,7 +30217,7 @@ function ensureConfigLocationsMigrated(dir) {
29587
30217
  });
29588
30218
  }
29589
30219
  function logPiConfigLoad(args) {
29590
- const key = resolve2(args.dir);
30220
+ const key = resolve3(args.dir);
29591
30221
  if (args.dedupe && loggedPiConfigDirs.has(key))
29592
30222
  return;
29593
30223
  if (args.dedupe) {
@@ -29607,9 +30237,9 @@ var __test = {
29607
30237
  resetLoggedPiConfigDirs() {
29608
30238
  loggedPiConfigDirs.clear();
29609
30239
  },
29610
- isPiMagicContextActiveInProcess,
29611
- markPiMagicContextActive,
29612
- clearPiMagicContextActive
30240
+ clearPiInProcessSubagentInitContext,
30241
+ claimPiStartupMaintenance,
30242
+ clearPiStartupMaintenanceClaim
29613
30243
  };
29614
30244
  function formatTokens(value) {
29615
30245
  return value.toLocaleString();
@@ -29681,7 +30311,7 @@ async function persistPiPressureFromMessageEnd(args) {
29681
30311
  }
29682
30312
  var PLUGIN_VERSION = (() => {
29683
30313
  try {
29684
- const req = createRequire3(import.meta.url);
30314
+ const req = createRequire2(import.meta.url);
29685
30315
  return req("../package.json").version;
29686
30316
  } catch {
29687
30317
  return "0.0.0";
@@ -29722,6 +30352,8 @@ function resolveHistorianFromConfig(config) {
29722
30352
  fallbackModels,
29723
30353
  historianChunkTokens,
29724
30354
  timeoutMs: config.historian_timeout_ms,
30355
+ temperature: historian?.temperature ?? 0.1,
30356
+ maxOutputTokens: historian?.maxTokens ?? 32000,
29725
30357
  twoPass: historian?.two_pass === true,
29726
30358
  thinkingLevel: resolved.primary?.qualifier,
29727
30359
  executeThresholdPercentage: config.execute_threshold_percentage,
@@ -29754,11 +30386,12 @@ async function src_default(pi) {
29754
30386
  log(`${PREFIX2} subagent child detected (${MAGIC_CONTEXT_PI_SUBAGENT_ENV}=1); skipping full extension registration`);
29755
30387
  return;
29756
30388
  }
29757
- if (isPiMagicContextActiveInProcess()) {
29758
- log(`${PREFIX2} in-process re-init detected (Magic Context already active in this process); skipping full extension registration`);
30389
+ if (isPiInProcessSubagentInit()) {
30390
+ log(`${PREFIX2} in-process subagent child detected; skipping full extension registration`);
29759
30391
  return;
29760
30392
  }
29761
- markPiMagicContextActive();
30393
+ const unregisterPiSubagentInitContext = registerPiSubagentInitContext(pi);
30394
+ registerPiSubagentInitContextCleanup(pi, unregisterPiSubagentInitContext);
29762
30395
  beginBootQuietPeriod();
29763
30396
  const bootProjectDir = process.cwd();
29764
30397
  ensureConfigLocationsMigrated(bootProjectDir);
@@ -29769,7 +30402,7 @@ async function src_default(pi) {
29769
30402
  mmapSizeMb: bootConfig.config.sqlite.mmap_size_mb
29770
30403
  });
29771
30404
  const storageDir = getMagicContextStorageDir();
29772
- const dbPath = join5(storageDir, "context.db");
30405
+ const dbPath = join6(storageDir, "context.db");
29773
30406
  let db;
29774
30407
  let openFailureCause = null;
29775
30408
  try {
@@ -29830,27 +30463,44 @@ async function src_default(pi) {
29830
30463
  }
29831
30464
  async function startPiMagicContextRuntime(pi, database, dbPath) {
29832
30465
  const db = database;
29833
- scheduleAfterBootQuiet(() => {
29834
- runDeferredV22Backfill(db).catch((err) => {
29835
- warn(`[v22-backfill] background runner failed: ${err}`);
30466
+ if (claimPiStartupMaintenance()) {
30467
+ scheduleAfterBootQuiet(() => {
30468
+ runDeferredV22Backfill(db).catch((err) => {
30469
+ warn(`[v22-backfill] background runner failed: ${err}`);
30470
+ });
29836
30471
  });
29837
- });
29838
- scheduleAfterBootQuiet(() => {
29839
- (async () => {
29840
- try {
29841
- const api = await loadDefaultPiSessionApi();
29842
- const sessions = await api.listSessions();
29843
- await runSessionProjectBackfill(database, sessions.map((session) => ({
29844
- sessionId: typeof session?.id === "string" ? session.id : "",
29845
- directory: typeof session?.cwd === "string" ? session.cwd : ""
29846
- })));
29847
- } catch (err) {
29848
- warn(`[session-projects] background runner failed: ${err}`);
29849
- }
29850
- })();
29851
- }, 0);
30472
+ scheduleAfterBootQuiet(() => {
30473
+ (async () => {
30474
+ try {
30475
+ let sessions;
30476
+ await runSessionProjectBackfill(database, async (afterSessionId, limit) => {
30477
+ if (!sessions) {
30478
+ const api = await loadDefaultPiSessionApi();
30479
+ const sessionsById2 = new Map;
30480
+ for (const session of await api.listSessions()) {
30481
+ const sessionId = typeof session?.id === "string" ? session.id : "";
30482
+ const directory = typeof session?.cwd === "string" ? session.cwd : "";
30483
+ if (sessionId && (!sessionsById2.has(sessionId) || directory)) {
30484
+ sessionsById2.set(sessionId, { sessionId, directory });
30485
+ }
30486
+ }
30487
+ sessions = [...sessionsById2.values()];
30488
+ }
30489
+ const offset = afterSessionId === null ? 0 : sessions.findIndex((session) => session.sessionId === afterSessionId) + 1;
30490
+ return sessions.slice(offset, offset + limit);
30491
+ });
30492
+ } catch (err) {
30493
+ warn(`[session-projects] background runner failed: ${err}`);
30494
+ }
30495
+ })();
30496
+ }, 0);
30497
+ }
29852
30498
  const projectDir = process.cwd();
29853
30499
  const seenDreamerProjectIdentities = new Set;
30500
+ const dreamerRegistrationOwner = {};
30501
+ const commandLifecycleController = new AbortController;
30502
+ registerCtxStatusLifecycleSignal(pi, commandLifecycleController.signal);
30503
+ let sessionShuttingDown = false;
29854
30504
  ensureConfigLocationsMigrated(projectDir);
29855
30505
  const { config, warnings, loadedFromPaths, registrationPromptSurface } = loadPiConfig({
29856
30506
  cwd: projectDir
@@ -29993,6 +30643,32 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29993
30643
  }
29994
30644
  const bootProjectDeps = buildProjectDeps(projectDir, projectIdentity, config);
29995
30645
  projectDepsByDir.set(projectDir, bootProjectDeps);
30646
+ function syncDreamerProjectRegistration(current) {
30647
+ if (sessionShuttingDown)
30648
+ return;
30649
+ seenDreamerProjectIdentities.add(current.projectIdentity);
30650
+ if (!current.dreamerConfig) {
30651
+ unregisterPiDreamerProject({
30652
+ projectIdentity: current.projectIdentity,
30653
+ registrationOwner: dreamerRegistrationOwner
30654
+ });
30655
+ return;
30656
+ }
30657
+ registerPiDreamerProject({
30658
+ db,
30659
+ projectDir: current.projectDir,
30660
+ projectIdentity: current.projectIdentity,
30661
+ registrationOwner: dreamerRegistrationOwner,
30662
+ config: current.dreamerConfig,
30663
+ embeddingConfig: current.config.embedding,
30664
+ memoryEnabled: current.config.memory.enabled,
30665
+ retinaHandoff: current.config.smart_notes.retina_handoff,
30666
+ mural: current.config.mural,
30667
+ language: current.config.language,
30668
+ gitCommitIndexing: current.config.memory.git_commit_indexing,
30669
+ onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject
30670
+ });
30671
+ }
29996
30672
  const todowriteEnabled = bootProjectDeps.config.todowrite.enabled !== false;
29997
30673
  const todowriteOverlayEnabled = todowriteEnabled && bootProjectDeps.config.todowrite.overlay !== false;
29998
30674
  registerMagicContextTools(pi, {
@@ -30187,7 +30863,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
30187
30863
  },
30188
30864
  dreamerEnabled: bootProjectDeps.dreamerEnabled,
30189
30865
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
30190
- onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
30866
+ onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity),
30867
+ ensureRegistered: (ctx) => syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx)),
30868
+ registrationOwner: dreamerRegistrationOwner
30191
30869
  });
30192
30870
  info("registered /ctx-dream");
30193
30871
  registerCtxEmbedCommand(pi, {
@@ -30207,19 +30885,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
30207
30885
  info("registered /ctx-embed");
30208
30886
  const dreamerConfig = bootProjectDeps.dreamerConfig;
30209
30887
  if (dreamerConfig) {
30210
- registerPiDreamerProject({
30211
- db,
30212
- projectDir,
30213
- projectIdentity,
30214
- config: dreamerConfig,
30215
- embeddingConfig: bootProjectDeps.config.embedding,
30216
- memoryEnabled: bootProjectDeps.config.memory.enabled,
30217
- retinaHandoff: bootProjectDeps.config.smart_notes.retina_handoff,
30218
- mural: bootProjectDeps.config.mural,
30219
- language: bootProjectDeps.config.language,
30220
- gitCommitIndexing: bootProjectDeps.config.memory.git_commit_indexing,
30221
- onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject
30222
- });
30888
+ syncDreamerProjectRegistration(bootProjectDeps);
30223
30889
  info(`registered dreamer (${summarizeDreamSchedule(dreamerConfig)})`);
30224
30890
  } else {
30225
30891
  info(bootProjectDeps.dreamerEnabled ? "registered dreamer: DISABLED (no dreamer config)" : "registered dreamer: DISABLED (dreamer.disable=true or no dreamer config)");
@@ -30250,33 +30916,10 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
30250
30916
  projectIdentity: effectiveProjectDeps.projectIdentity
30251
30917
  };
30252
30918
  const effectiveConfig = effectiveProjectDeps.config;
30253
- seenDreamerProjectIdentities.add(currentProject.projectIdentity);
30254
- const effectiveDreamerConfig = effectiveProjectDeps.dreamerConfig;
30255
- if (effectiveDreamerConfig) {
30256
- try {
30257
- registerPiDreamerProject({
30258
- db,
30259
- projectDir: currentProject.projectDir,
30260
- projectIdentity: currentProject.projectIdentity,
30261
- config: effectiveDreamerConfig,
30262
- embeddingConfig: effectiveConfig.embedding,
30263
- memoryEnabled: effectiveConfig.memory.enabled,
30264
- retinaHandoff: effectiveConfig.smart_notes.retina_handoff,
30265
- language: effectiveConfig.language,
30266
- gitCommitIndexing: effectiveConfig.memory.git_commit_indexing,
30267
- onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject
30268
- });
30269
- } catch (err) {
30270
- warn("before_agent_start: registerPiDreamerProject threw:", err);
30271
- }
30272
- } else {
30273
- try {
30274
- unregisterPiDreamerProject({
30275
- projectIdentity: currentProject.projectIdentity
30276
- });
30277
- } catch (err) {
30278
- warn("before_agent_start: unregisterPiDreamerProject threw:", err);
30279
- }
30919
+ try {
30920
+ syncDreamerProjectRegistration(effectiveProjectDeps);
30921
+ } catch (err) {
30922
+ warn("before_agent_start: dreamer registration sync threw:", err);
30280
30923
  }
30281
30924
  const sm = ctx.sessionManager;
30282
30925
  let sessionId;
@@ -30538,39 +31181,47 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
30538
31181
  }
30539
31182
  });
30540
31183
  pi.on("session_shutdown", async (_event, ctx) => {
31184
+ sessionShuttingDown = true;
31185
+ commandLifecycleController.abort();
30541
31186
  const SHUTDOWN_DRAIN_MS = 5000;
31187
+ const sessionId = resolveSessionId(ctx);
30542
31188
  try {
30543
- await withTimeout(awaitInFlightHistorians(), SHUTDOWN_DRAIN_MS);
31189
+ for (const identity of seenDreamerProjectIdentities) {
31190
+ unregisterPiDreamerProject({
31191
+ projectIdentity: identity,
31192
+ registrationOwner: dreamerRegistrationOwner
31193
+ });
31194
+ }
30544
31195
  } catch (err) {
30545
- warn("shutdown: historian drain threw:", err);
31196
+ warn("shutdown: unregisterPiDreamerProject threw:", err);
30546
31197
  }
30547
- try {
30548
- await withTimeout(awaitInFlightRecomps(), SHUTDOWN_DRAIN_MS);
30549
- } catch (err) {
30550
- warn("shutdown: recomp drain threw:", err);
31198
+ if (sessionId) {
31199
+ try {
31200
+ await withTimeout(awaitInFlightHistorians(sessionId), SHUTDOWN_DRAIN_MS);
31201
+ } catch (err) {
31202
+ warn("shutdown: historian drain threw:", err);
31203
+ }
31204
+ try {
31205
+ await withTimeout(awaitInFlightRecomps(sessionId), SHUTDOWN_DRAIN_MS);
31206
+ } catch (err) {
31207
+ warn("shutdown: recomp drain threw:", err);
31208
+ }
31209
+ abortInFlightRecomps(sessionId);
30551
31210
  }
30552
31211
  try {
30553
- await withTimeout(awaitInFlightDreamers(), SHUTDOWN_DRAIN_MS);
31212
+ await withTimeout(awaitInFlightDreamers(dreamerRegistrationOwner), SHUTDOWN_DRAIN_MS);
30554
31213
  } catch (err) {
30555
31214
  warn("shutdown: dreamer drain threw:", err);
30556
31215
  }
30557
- try {
30558
- for (const identity of seenDreamerProjectIdentities) {
30559
- unregisterPiDreamerProject({ projectIdentity: identity });
30560
- }
30561
- } catch (err) {
30562
- warn("shutdown: unregisterPiDreamerProject threw:", err);
30563
- }
30564
31216
  try {
30565
31217
  const sm = ctx.sessionManager;
30566
- const sessionId = typeof sm?.getSessionId === "function" ? sm.getSessionId() : undefined;
30567
- if (typeof sessionId === "string" && sessionId.length > 0) {
30568
- clearPiSystemPromptSession(sessionId);
30569
- promptSurfaceGuidanceEpochs.clear(sessionId);
30570
- clearContextHandlerSession(sessionId);
31218
+ const sessionId2 = typeof sm?.getSessionId === "function" ? sm.getSessionId() : undefined;
31219
+ if (typeof sessionId2 === "string" && sessionId2.length > 0) {
31220
+ clearPiSystemPromptSession(sessionId2);
31221
+ promptSurfaceGuidanceEpochs.clear(sessionId2);
31222
+ clearContextHandlerSession(sessionId2);
30571
31223
  }
30572
31224
  } catch {}
30573
- clearPiMagicContextActive();
30574
31225
  });
30575
31226
  pi.on("session_before_switch", (_event, ctx) => {
30576
31227
  try {
@@ -30584,6 +31235,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
30584
31235
  } catch {}
30585
31236
  });
30586
31237
  }
31238
+ function registerPiSubagentInitContextCleanup(pi, unsubscribe) {
31239
+ pi.on("session_shutdown", unsubscribe);
31240
+ }
30587
31241
  function formatExecuteThresholdForLog(value) {
30588
31242
  if (value === undefined)
30589
31243
  return "65%";