@cortexkit/pi-magic-context 0.31.1 → 0.31.3

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
@@ -155029,6 +155029,13 @@ function setSessionWorkMetrics(db, sessionId, newWorkTokens, totalInputTokens) {
155029
155029
  SET new_work_tokens = ?, total_input_tokens = ?
155030
155030
  WHERE session_id = ?`).run(Math.max(0, Math.floor(newWorkTokens)), Math.max(0, Math.floor(totalInputTokens)), sessionId);
155031
155031
  }
155032
+ function getSessionWorkMetrics(db, sessionId) {
155033
+ const row = db.prepare("SELECT new_work_tokens, total_input_tokens FROM session_meta WHERE session_id = ?").get(sessionId);
155034
+ return {
155035
+ newWorkTokens: typeof row?.new_work_tokens === "number" ? row.new_work_tokens : 0,
155036
+ totalInputTokens: typeof row?.total_input_tokens === "number" ? row.total_input_tokens : 0
155037
+ };
155038
+ }
155032
155039
  // ../plugin/src/features/magic-context/storage-meta-session.ts
155033
155040
  import { Buffer as Buffer3 } from "node:buffer";
155034
155041
 
@@ -157979,7 +157986,8 @@ function registerCtxAugCommand(pi, config) {
157979
157986
  const branch = ctx.sessionManager.getBranch();
157980
157987
  const lastEntryId = branch.length > 0 ? branch[branch.length - 1]?.id : "unknown";
157981
157988
  const sessionLabel = `pi-session-${lastEntryId}`;
157982
- if (!config) {
157989
+ const currentConfig = typeof config === "function" ? config(ctx) : config;
157990
+ if (!currentConfig) {
157983
157991
  ctx.ui.notify("/ctx-aug: Sidekick is not configured. Add `sidekick.model` to your magic-context.jsonc to enable this command.", "warning");
157984
157992
  return;
157985
157993
  }
@@ -157991,20 +157999,20 @@ function registerCtxAugCommand(pi, config) {
157991
157999
  ctx.ui.notify("\uD83D\uDD0D Preparing augmentation… 2-10s depending on your sidekick provider.", "info");
157992
158000
  }
157993
158001
  sessionLog(sessionLabel, "/ctx-aug: spawning sidekick", {
157994
- model: config.model
158002
+ model: currentConfig.model
157995
158003
  });
157996
158004
  const projectIdentity = resolveProjectIdentity(ctx.cwd);
157997
158005
  sessionLog(sessionLabel, "/ctx-aug: project identity", projectIdentity);
157998
158006
  const result = await runner.run({
157999
158007
  agent: "sidekick",
158000
- systemPrompt: withContentLanguageDirective(config.systemPrompt ?? SIDEKICK_SYSTEM_PROMPT, config.language),
158008
+ systemPrompt: withContentLanguageDirective(currentConfig.systemPrompt ?? SIDEKICK_SYSTEM_PROMPT, currentConfig.language),
158001
158009
  userMessage: prompt,
158002
- model: config.model,
158003
- fallbackModels: config.fallbackModels,
158004
- timeoutMs: config.timeoutMs ?? 30000,
158010
+ model: currentConfig.model,
158011
+ fallbackModels: currentConfig.fallbackModels,
158012
+ timeoutMs: currentConfig.timeoutMs ?? 30000,
158005
158013
  cwd: ctx.cwd,
158006
158014
  signal: ctx.signal,
158007
- thinkingLevel: config.thinking_level,
158015
+ thinkingLevel: currentConfig.thinking_level,
158008
158016
  accountingSessionId: sessionLabel,
158009
158017
  accountingSubagent: "sidekick"
158010
158018
  });
@@ -173234,6 +173242,10 @@ var MagicContextConfigSchema = exports_external.object({
173234
173242
  }).describe("Embedding provider configuration"),
173235
173243
  temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
173236
173244
  keep_subagents: exports_external.boolean().default(false).describe("Debug: keep the child sessions Magic Context spawns for its own subagents (historian, dreamer, sidekick, memory-migration) instead of deleting them on success. Useful for short-term inspection/data collection — their full transcript (prompt, tool calls, token usage, output) stays in the host session store. Kept sessions accumulate until manually cleared; leave false for normal use. Requires a restart to take effect."),
173245
+ todowrite: exports_external.object({
173246
+ enabled: exports_external.boolean().default(true).describe("Pi only: register Magic Context's todowrite task-list tool. Disable if you use your own todo extension. OpenCode ships its own built-in todowrite; this setting has no effect there."),
173247
+ overlay: exports_external.boolean().default(true).describe("Pi only: show the persistent todo overlay above the editor while tasks are active.")
173248
+ }).default({ enabled: true, overlay: true }).describe("Pi-only todowrite tool and overlay controls. Pi registers tools and widgets at extension boot, so changing this after /cd requires /reload or restart."),
173237
173249
  smart_drops: exports_external.boolean().default(false).describe("Content-aware reclaim of provably-superseded tool output, layered on the existing execute-pass auto-drop. When on: superseded todowrite (keep newest 1), spent ctx_reduce (keep newest 5), and zero-value meta (bash_status, bash_kill, ctx_note read/dismiss) outputs are dropped; older edits to a file are compressed to a filePath-preserving marker while the newest edit per file stays full. Only acts on passes already busting the cache, so it never originates a cache bust. Honors the protected-tag reserve. Experimental: opt-in, default off until cache stability is proven; when off the wire is byte-identical to the positional-only reclaim. Requires a restart."),
173238
173250
  caveman_text_compression: exports_external.object({
173239
173251
  enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Active for primary sessions when enabled; never for subagents. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
@@ -176773,9 +176785,11 @@ import path7 from "node:path";
176773
176785
  import { promisify } from "node:util";
176774
176786
  var execFileAsync = promisify(execFile);
176775
176787
  var GIT_TIMEOUT_MS2 = 1e4;
176788
+ var defaultExecFileForVerificationPaths = async (file2, args, options) => await execFileAsync(file2, [...args], options);
176789
+ var execFileForVerificationPaths = defaultExecFileForVerificationPaths;
176776
176790
  async function runGit(cwd, args) {
176777
176791
  try {
176778
- const result = await execFileAsync("git", [...args], {
176792
+ const result = await execFileForVerificationPaths("git", args, {
176779
176793
  cwd,
176780
176794
  timeout: GIT_TIMEOUT_MS2,
176781
176795
  maxBuffer: 16 * 1024 * 1024,
@@ -184714,6 +184728,7 @@ function registerCtxDreamCommand(pi, deps) {
184714
184728
  projectDir: deps.projectDir,
184715
184729
  projectIdentity: deps.projectIdentity
184716
184730
  };
184731
+ const dreamerEnabled = deps.resolveDreamerEnabled?.(ctx) ?? deps.dreamerEnabled;
184717
184732
  deps.onProjectSeen?.(project.projectIdentity);
184718
184733
  const requested = typeof args === "string" ? args.trim() : String(args ?? "").trim();
184719
184734
  let task;
@@ -184733,7 +184748,7 @@ Unknown task "${requested}".`,
184733
184748
  }
184734
184749
  task = requested;
184735
184750
  }
184736
- if (deps.dreamerEnabled === false) {
184751
+ if (dreamerEnabled === false) {
184737
184752
  sendCtxStatusMessage(pi, {
184738
184753
  title: "/ctx-dream",
184739
184754
  text: "## /ctx-dream\n\nDreamer is disabled for this project (`dreamer.disable=true`).",
@@ -184848,6 +184863,8 @@ function formatEmbedStatusText(coverage, drain) {
184848
184863
  }
184849
184864
 
184850
184865
  // src/commands/ctx-embed.ts
184866
+ var EMBED_PROGRESS_COMPARTMENT_STEP = 8;
184867
+ var EMBED_PROGRESS_MIN_INTERVAL_MS = 1e4;
184851
184868
  function clearPiEmbedSessionState(sessionId) {
184852
184869
  embedPauseBySession.delete(sessionId);
184853
184870
  const ctrl = embedRunStateBySession.get(sessionId);
@@ -184857,7 +184874,7 @@ function clearPiEmbedSessionState(sessionId) {
184857
184874
  }
184858
184875
  autoEmbedAttemptedBySession.delete(sessionId);
184859
184876
  }
184860
- async function runEmbedDrain(db, projectIdentity, sessionId) {
184877
+ async function runEmbedDrain(db, projectIdentity, sessionId, options = {}) {
184861
184878
  const activeCtrl = embedRunStateBySession.get(sessionId);
184862
184879
  if (activeCtrl && !activeCtrl.signal.aborted) {
184863
184880
  return {
@@ -184873,10 +184890,46 @@ Embedding is already running for this session.`,
184873
184890
  prior.abort();
184874
184891
  const controller = new AbortController;
184875
184892
  embedRunStateBySession.set(sessionId, controller);
184893
+ const now = options.now ?? Date.now;
184894
+ let startEmitted = false;
184895
+ let lastProgressEmbedded = 0;
184896
+ let lastProgressAt = 0;
184897
+ const emitProgress = (progress) => {
184898
+ if (progress.total <= 0)
184899
+ return;
184900
+ if (!startEmitted) {
184901
+ startEmitted = true;
184902
+ lastProgressAt = now();
184903
+ options.onStatus?.({
184904
+ text: `## /ctx-embed
184905
+
184906
+ Embedding ${progress.total} compartment${progress.total === 1 ? "" : "s"} of history…`,
184907
+ level: "info"
184908
+ });
184909
+ return;
184910
+ }
184911
+ if (progress.embedded <= 0 || progress.embedded >= progress.total)
184912
+ return;
184913
+ const currentTime = now();
184914
+ const enoughCompartments = progress.embedded - lastProgressEmbedded >= EMBED_PROGRESS_COMPARTMENT_STEP;
184915
+ const enoughTime = currentTime - lastProgressAt >= EMBED_PROGRESS_MIN_INTERVAL_MS && progress.embedded > lastProgressEmbedded;
184916
+ if (!enoughCompartments && !enoughTime)
184917
+ return;
184918
+ lastProgressEmbedded = progress.embedded;
184919
+ lastProgressAt = currentTime;
184920
+ options.onStatus?.({
184921
+ text: `## /ctx-embed
184922
+
184923
+ Embedded ${progress.embedded}/${progress.total} compartments so far…`,
184924
+ level: "info"
184925
+ });
184926
+ };
184876
184927
  let outcome;
184877
184928
  try {
184878
184929
  outcome = await embedSessionCompartmentChunks(db, projectIdentity, sessionId, {
184879
- signal: controller.signal
184930
+ signal: controller.signal,
184931
+ onProgress: emitProgress,
184932
+ ...options.batchSize !== undefined ? { batchSize: options.batchSize } : {}
184880
184933
  });
184881
184934
  } finally {
184882
184935
  if (embedRunStateBySession.get(sessionId) === controller) {
@@ -184949,6 +185002,7 @@ No active Pi session is available.`,
184949
185002
  projectDir: deps.projectDir,
184950
185003
  projectIdentity: deps.projectIdentity
184951
185004
  };
185005
+ const memoryEnabled = deps.resolveMemoryEnabled?.(ctx) ?? deps.memoryEnabled;
184952
185006
  const sub = args.trim().toLowerCase();
184953
185007
  if (sub === "pause") {
184954
185008
  embedPauseBySession.add(sessionId);
@@ -184965,7 +185019,7 @@ Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`,
184965
185019
  });
184966
185020
  return;
184967
185021
  }
184968
- if (deps.memoryEnabled === false) {
185022
+ if (memoryEnabled === false) {
184969
185023
  sendCtxStatusMessage(pi, {
184970
185024
  title: "/ctx-embed",
184971
185025
  text: `## /ctx-embed
@@ -184977,7 +185031,12 @@ Memory is disabled for this project, so there is no semantic embedding to backfi
184977
185031
  }
184978
185032
  await ensureProjectRegisteredFromPiDirectory(project.projectDir, deps.db);
184979
185033
  if (sub === "start") {
184980
- const { text, level } = await runEmbedDrain(deps.db, project.projectIdentity, sessionId);
185034
+ const { text, level } = await runEmbedDrain(deps.db, project.projectIdentity, sessionId, {
185035
+ onStatus: (status) => sendCtxStatusMessage(pi, {
185036
+ title: "/ctx-embed",
185037
+ ...status
185038
+ })
185039
+ });
184981
185040
  sendCtxStatusMessage(pi, { title: "/ctx-embed", text, level });
184982
185041
  return;
184983
185042
  }
@@ -188010,6 +188069,11 @@ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions
188010
188069
  `);
188011
188070
  }
188012
188071
 
188072
+ // ../plugin/src/hooks/magic-context/sentinel.ts
188073
+ function modelAcceptsEmptyContent(providerID) {
188074
+ return providerID === "anthropic";
188075
+ }
188076
+
188013
188077
  // ../plugin/src/hooks/magic-context/supersession-reclaim.ts
188014
188078
  var TODOWRITE_KEEP = 1;
188015
188079
  var CTX_REDUCE_KEEP = 5;
@@ -190510,8 +190574,8 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config2, pr
190510
190574
  sessionLog(sessionId, `emergency tiered drop skipped: ${plan.reason}`);
190511
190575
  }
190512
190576
  }
190513
- const staleReduce = collectStaleReduceCallIds(piMessages, buildMessageIdToMaxTagFromTargets(targets), toolAgeCutoff, resolveStableId);
190514
- if (staleReduce.composite.size > 0 || staleReduce.bareCallIds.size > 0) {
190577
+ const staleReduce = config2.staleReduceStripEnabled ? collectStaleReduceCallIds(piMessages, buildMessageIdToMaxTagFromTargets(targets), toolAgeCutoff, resolveStableId) : { composite: new Set, bareCallIds: new Set };
190578
+ if (config2.staleReduceStripEnabled && (staleReduce.composite.size > 0 || staleReduce.bareCallIds.size > 0)) {
190515
190579
  db.transaction(() => {
190516
190580
  for (const tag of tags) {
190517
190581
  if (tag.status !== "active")
@@ -194792,8 +194856,122 @@ function buildReferenceBlocks(args) {
194792
194856
  init_logger();
194793
194857
  var HISTORIAN_AGENT_NAME = "magic-context-historian";
194794
194858
  var DEFAULT_HISTORIAN_TIMEOUT_MS2 = 120000;
194859
+ var MAX_HISTORIAN_RETRIES = 2;
194795
194860
  var HISTORIAN_ALERT_COOLDOWN_MS = 60 * 1000;
194796
194861
  var lastHistorianAlertBySession = new Map;
194862
+ function getHistorianRetryBackoffMs(retryIndex) {
194863
+ if (retryIndex === 0) {
194864
+ return 2000 + Math.floor(Math.random() * 1001);
194865
+ }
194866
+ return 6000 + Math.floor(Math.random() * 2001);
194867
+ }
194868
+ function isTransientHistorianPromptError(message) {
194869
+ const normalized = message.toLowerCase();
194870
+ if (normalized.includes("invalid request") || normalized.includes("bad request") || normalized.includes("unauthorized") || normalized.includes("forbidden") || normalized.includes("authentication") || normalized.includes("auth") || normalized.includes(" 400") || normalized.startsWith("400")) {
194871
+ return false;
194872
+ }
194873
+ return [
194874
+ "429",
194875
+ "rate limit",
194876
+ "timeout",
194877
+ "econnreset",
194878
+ "etimedout",
194879
+ "503",
194880
+ "502",
194881
+ "500",
194882
+ "overloaded"
194883
+ ].some((token) => normalized.includes(token));
194884
+ }
194885
+ function isTransientHistorianRunFailure(result) {
194886
+ if (result.reason === "abort")
194887
+ return false;
194888
+ if (result.reason === "timeout")
194889
+ return true;
194890
+ return isTransientHistorianPromptError(result.error);
194891
+ }
194892
+ function historianAbortResult(startedAt) {
194893
+ return {
194894
+ ok: false,
194895
+ reason: "abort",
194896
+ error: "pi subagent aborted by caller",
194897
+ durationMs: Date.now() - startedAt
194898
+ };
194899
+ }
194900
+ async function sleepWithAbort(ms, signal) {
194901
+ if (signal?.aborted)
194902
+ return true;
194903
+ if (ms <= 0)
194904
+ return signal?.aborted === true;
194905
+ return new Promise((resolve3) => {
194906
+ let settled = false;
194907
+ let timeout;
194908
+ const finish = (aborted2) => {
194909
+ if (settled)
194910
+ return;
194911
+ settled = true;
194912
+ if (timeout)
194913
+ clearTimeout(timeout);
194914
+ signal?.removeEventListener("abort", onAbort);
194915
+ resolve3(aborted2);
194916
+ };
194917
+ const onAbort = () => finish(true);
194918
+ signal?.addEventListener("abort", onAbort, { once: true });
194919
+ timeout = setTimeout(() => finish(false), ms);
194920
+ });
194921
+ }
194922
+ async function runHistorianSubagentWithTransientRetries(args) {
194923
+ const startedAt = Date.now();
194924
+ if (args.options.signal?.aborted)
194925
+ return historianAbortResult(startedAt);
194926
+ for (let retryIndex = 0;retryIndex <= MAX_HISTORIAN_RETRIES; retryIndex += 1) {
194927
+ const attemptStart = Date.now();
194928
+ let result;
194929
+ try {
194930
+ result = await args.runner.run({
194931
+ ...args.options,
194932
+ fallbackModels: undefined
194933
+ });
194934
+ } catch (error51) {
194935
+ const desc = describeError(error51);
194936
+ result = {
194937
+ ok: false,
194938
+ reason: "model_failed",
194939
+ error: desc.brief,
194940
+ durationMs: Date.now() - attemptStart
194941
+ };
194942
+ }
194943
+ if (result.ok)
194944
+ return result;
194945
+ if (result.reason === "abort" || args.options.signal?.aborted) {
194946
+ return result.reason === "abort" ? result : historianAbortResult(startedAt);
194947
+ }
194948
+ const shouldRetry = retryIndex < MAX_HISTORIAN_RETRIES && isTransientHistorianRunFailure(result);
194949
+ if (!shouldRetry)
194950
+ return result;
194951
+ const backoffMs2 = args.retryBackoffMs?.(retryIndex) ?? getHistorianRetryBackoffMs(retryIndex);
194952
+ sessionLog(args.sessionId, `historian[${args.passLabel}] transient failure; retry ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES} on same model after ${backoffMs2}ms: ${result.error}`);
194953
+ const aborted2 = await sleepWithAbort(backoffMs2, args.options.signal);
194954
+ if (aborted2)
194955
+ return historianAbortResult(startedAt);
194956
+ }
194957
+ return historianAbortResult(startedAt);
194958
+ }
194959
+ function buildHistorianFallbackChain(primaryModel, fallbackModels, fallbackModelId) {
194960
+ const seen = new Set;
194961
+ if (primaryModel)
194962
+ seen.add(primaryModel);
194963
+ const chain = [];
194964
+ for (const candidate of fallbackModels ?? []) {
194965
+ if (!candidate || seen.has(candidate))
194966
+ continue;
194967
+ seen.add(candidate);
194968
+ chain.push({ modelId: candidate, kind: "configured" });
194969
+ }
194970
+ if (fallbackModelId && !seen.has(fallbackModelId)) {
194971
+ chain.push({ modelId: fallbackModelId, kind: "session" });
194972
+ }
194973
+ return chain;
194974
+ }
194797
194975
  function parseSourceMessageTime(value) {
194798
194976
  if (typeof value === "number" && Number.isFinite(value))
194799
194977
  return value;
@@ -194842,11 +195020,14 @@ async function runPiHistorian(deps) {
194842
195020
  runner,
194843
195021
  historianModel,
194844
195022
  fallbackModels,
195023
+ fallbackModelId,
194845
195024
  historianChunkTokens,
194846
195025
  boundarySnapshot: providedBoundarySnapshot,
194847
195026
  refreshBoundarySnapshot,
194848
195027
  currentContextLimit,
194849
195028
  historianTimeoutMs = DEFAULT_HISTORIAN_TIMEOUT_MS2,
195029
+ signal,
195030
+ retryBackoffMs,
194850
195031
  twoPass,
194851
195032
  thinkingLevel,
194852
195033
  memoryEnabled,
@@ -194860,7 +195041,9 @@ async function runPiHistorian(deps) {
194860
195041
  forceDrainQuota,
194861
195042
  forceKeepLastCompartment
194862
195043
  } = deps;
195044
+ let issueNotified = false;
194863
195045
  const notify = async (message) => {
195046
+ issueNotified = true;
194864
195047
  if (shouldSuppressHistorianAlert(sessionId)) {
194865
195048
  sessionLog(sessionId, "historian alert suppressed (cooldown)");
194866
195049
  return;
@@ -195051,61 +195234,77 @@ ${chunkText}`,
195051
195234
  retainDrainReservationForRetryThrottle = true;
195052
195235
  const historianSystemPrompt = withContentLanguageDirective(COMPARTMENT_AGENT_SYSTEM_PROMPT, deps.language, { preserveUserQuotes: true });
195053
195236
  const historianEditorSystemPrompt = withContentLanguageDirective(HISTORIAN_EDITOR_SYSTEM_PROMPT, deps.language, { preserveUserQuotes: true });
195054
- const firstResult = await runner.run({
195055
- agent: HISTORIAN_AGENT_NAME,
195056
- systemPrompt: historianSystemPrompt,
195057
- userMessage: prompt,
195058
- model: historianModel,
195059
- fallbackModels,
195060
- timeoutMs: historianTimeoutMs,
195061
- cwd: directory,
195062
- thinkingLevel,
195063
- onProgress: buildProgressLogger("first"),
195064
- accountingSessionId: sessionId,
195065
- accountingSubagent: "historian"
195066
- });
195067
- let validatedPass = await validateHistorianResult(firstResult, sessionId, chunk, priorCompartments, sequenceOffset);
195068
- let validatedDraftText = firstResult.ok ? firstResult.assistantText : null;
195069
- if (validatedPass.kind === "validation-failed") {
195070
- sessionLog(sessionId, `historian: first pass validation failed, retrying with repair prompt: ${validatedPass.error}`);
195071
- const repairPrompt = buildHistorianRepairPrompt(prompt, validatedPass.rawText, validatedPass.error, deps.language);
195072
- const repairResult = await runner.run({
195237
+ const firstResult = await runHistorianSubagentWithTransientRetries({
195238
+ runner,
195239
+ sessionId,
195240
+ passLabel: "first",
195241
+ retryBackoffMs,
195242
+ options: {
195073
195243
  agent: HISTORIAN_AGENT_NAME,
195074
195244
  systemPrompt: historianSystemPrompt,
195075
- userMessage: repairPrompt,
195245
+ userMessage: prompt,
195076
195246
  model: historianModel,
195077
- fallbackModels,
195078
195247
  timeoutMs: historianTimeoutMs,
195079
195248
  cwd: directory,
195249
+ signal,
195080
195250
  thinkingLevel,
195081
- onProgress: buildProgressLogger("repair"),
195251
+ onProgress: buildProgressLogger("first"),
195082
195252
  accountingSessionId: sessionId,
195083
195253
  accountingSubagent: "historian"
195084
- });
195085
- validatedPass = await validateHistorianResult(repairResult, sessionId, chunk, priorCompartments, sequenceOffset);
195086
- if (validatedPass.kind === "ok" && repairResult.ok) {
195087
- validatedDraftText = repairResult.assistantText;
195088
195254
  }
195089
- }
195090
- if (validatedPass.kind !== "ok" && (fallbackModels?.length ?? 0) > 0) {
195091
- const seen = new Set([historianModel].filter(Boolean));
195092
- for (const candidate of fallbackModels ?? []) {
195093
- if (!candidate || seen.has(candidate))
195094
- continue;
195095
- seen.add(candidate);
195096
- sessionLog(sessionId, `historian: escalating to configured fallback model ${candidate}`);
195097
- const fbResult = await runner.run({
195255
+ });
195256
+ let validatedPass = await validateHistorianResult(firstResult, sessionId, chunk, priorCompartments, sequenceOffset);
195257
+ let validatedDraftText = firstResult.ok ? firstResult.assistantText : null;
195258
+ if (validatedPass.kind === "validation-failed") {
195259
+ sessionLog(sessionId, `historian: first pass validation failed, retrying with repair prompt: ${validatedPass.error}`);
195260
+ const repairPrompt = buildHistorianRepairPrompt(prompt, validatedPass.rawText, validatedPass.error, deps.language);
195261
+ const repairResult = await runHistorianSubagentWithTransientRetries({
195262
+ runner,
195263
+ sessionId,
195264
+ passLabel: "repair",
195265
+ retryBackoffMs,
195266
+ options: {
195098
195267
  agent: HISTORIAN_AGENT_NAME,
195099
195268
  systemPrompt: historianSystemPrompt,
195100
- userMessage: prompt,
195101
- model: candidate,
195102
- fallbackModels: undefined,
195269
+ userMessage: repairPrompt,
195270
+ model: historianModel,
195103
195271
  timeoutMs: historianTimeoutMs,
195104
195272
  cwd: directory,
195273
+ signal,
195105
195274
  thinkingLevel,
195106
- onProgress: buildProgressLogger("fallback"),
195275
+ onProgress: buildProgressLogger("repair"),
195107
195276
  accountingSessionId: sessionId,
195108
195277
  accountingSubagent: "historian"
195278
+ }
195279
+ });
195280
+ validatedPass = await validateHistorianResult(repairResult, sessionId, chunk, priorCompartments, sequenceOffset);
195281
+ if (validatedPass.kind === "ok" && repairResult.ok) {
195282
+ validatedDraftText = repairResult.assistantText;
195283
+ }
195284
+ }
195285
+ const fallbackChain = buildHistorianFallbackChain(historianModel, fallbackModels, fallbackModelId);
195286
+ if (validatedPass.kind !== "ok" && !(validatedPass.kind === "spawn-failed" && validatedPass.reason === "abort") && fallbackChain.length > 0) {
195287
+ for (let i = 0;i < fallbackChain.length; i += 1) {
195288
+ const candidate = fallbackChain[i];
195289
+ sessionLog(sessionId, `historian: escalating to ${candidate.kind === "session" ? "session-model last resort" : "configured fallback model"} ${candidate.modelId}`);
195290
+ const fbResult = await runHistorianSubagentWithTransientRetries({
195291
+ runner,
195292
+ sessionId,
195293
+ passLabel: candidate.kind === "session" ? "fallback-session" : "fallback",
195294
+ retryBackoffMs,
195295
+ options: {
195296
+ agent: HISTORIAN_AGENT_NAME,
195297
+ systemPrompt: historianSystemPrompt,
195298
+ userMessage: prompt,
195299
+ model: candidate.modelId,
195300
+ timeoutMs: historianTimeoutMs,
195301
+ cwd: directory,
195302
+ signal,
195303
+ thinkingLevel,
195304
+ onProgress: buildProgressLogger("fallback"),
195305
+ accountingSessionId: sessionId,
195306
+ accountingSubagent: "historian"
195307
+ }
195109
195308
  });
195110
195309
  const fbPass = await validateHistorianResult(fbResult, sessionId, chunk, priorCompartments, sequenceOffset);
195111
195310
  if (fbPass.kind === "ok") {
@@ -195114,6 +195313,10 @@ ${chunkText}`,
195114
195313
  validatedDraftText = fbResult.assistantText;
195115
195314
  break;
195116
195315
  }
195316
+ if (fbPass.kind === "spawn-failed" && fbPass.reason === "abort") {
195317
+ validatedPass = fbPass;
195318
+ break;
195319
+ }
195117
195320
  }
195118
195321
  }
195119
195322
  if (validatedPass.kind !== "ok") {
@@ -195130,17 +195333,24 @@ ${chunkText}`,
195130
195333
  const draftAssistantText = validatedDraftText ?? "";
195131
195334
  if (draftAssistantText.trim().length > 0) {
195132
195335
  sessionLog(sessionId, "historian two-pass: running editor on draft");
195133
- const editorResult = await runner.run({
195134
- agent: HISTORIAN_AGENT_NAME,
195135
- systemPrompt: historianEditorSystemPrompt,
195136
- userMessage: buildHistorianEditorPrompt(draftAssistantText),
195137
- model: historianModel,
195138
- timeoutMs: historianTimeoutMs,
195139
- cwd: directory,
195140
- thinkingLevel,
195141
- onProgress: buildProgressLogger("editor"),
195142
- accountingSessionId: sessionId,
195143
- accountingSubagent: "historian_editor"
195336
+ const editorResult = await runHistorianSubagentWithTransientRetries({
195337
+ runner,
195338
+ sessionId,
195339
+ passLabel: "editor",
195340
+ retryBackoffMs,
195341
+ options: {
195342
+ agent: HISTORIAN_AGENT_NAME,
195343
+ systemPrompt: historianEditorSystemPrompt,
195344
+ userMessage: buildHistorianEditorPrompt(draftAssistantText),
195345
+ model: historianModel,
195346
+ timeoutMs: historianTimeoutMs,
195347
+ cwd: directory,
195348
+ signal,
195349
+ thinkingLevel,
195350
+ onProgress: buildProgressLogger("editor"),
195351
+ accountingSessionId: sessionId,
195352
+ accountingSubagent: "historian_editor"
195353
+ }
195144
195354
  });
195145
195355
  const editorPass = await validateHistorianResult(editorResult, sessionId, chunk, priorCompartments, sequenceOffset);
195146
195356
  if (editorPass.kind === "ok") {
@@ -195353,7 +195563,7 @@ ${chunkText}`,
195353
195563
  const desc = describeError(error51);
195354
195564
  telemetry.failureReason = `exception: ${desc.brief}`;
195355
195565
  sessionLog(sessionId, `historian failure: source=exception ${desc.brief}${desc.stackHead ? ` stackHead="${desc.stackHead}"` : ""}`);
195356
- {
195566
+ if (!issueNotified) {
195357
195567
  const failCount = incrementHistorianFailure(db, sessionId, desc.brief);
195358
195568
  await notify(buildHistorianFailureNotice(failCount, desc.brief));
195359
195569
  }
@@ -197302,6 +197512,8 @@ function registerPiContextHandler(pi, baseOptions) {
197302
197512
  }
197303
197513
  const sessionMeta = sessionMetaForUsage;
197304
197514
  const modelKey = liveModelBySession.get(sessionId);
197515
+ const providerId = typeof ctx.model?.provider === "string" ? ctx.model.provider : undefined;
197516
+ const canUseEmptySentinels = modelAcceptsEmptyContent(providerId);
197305
197517
  if (usageContextLimit === undefined) {
197306
197518
  const modelWindow = ctx.model?.contextWindow;
197307
197519
  if (isSaneLimit(modelWindow)) {
@@ -197312,6 +197524,7 @@ function registerPiContextHandler(pi, baseOptions) {
197312
197524
  usagePercentage = usageInputTokens / usageContextLimit * 100;
197313
197525
  }
197314
197526
  ({ percentage: usagePercentage, inputTokens: usageInputTokens } = applyForwardPressureFloor(usagePercentage, usageInputTokens, piUsage?.tokens, usageContextLimit));
197527
+ const realUsagePercentageBeforeEmergencyBump = usagePercentage;
197315
197528
  if (needsEmergencyBump) {
197316
197529
  sessionLog(sessionId, `transform: overflow recovery flag set — bumping percentage to 95% (detectedLimit=${usageContextLimit ?? "unknown"})`);
197317
197530
  usagePercentage = Math.max(usagePercentage, 95);
@@ -197382,7 +197595,7 @@ function registerPiContextHandler(pi, baseOptions) {
197382
197595
  sessionLog(sessionId, "EMERGENCY: historian wait completed (or timed out)");
197383
197596
  } catch {}
197384
197597
  }
197385
- if (emergencyRecoveryArmed && !inFlightHistorian.has(sessionId) && !hasEligiblePiCompartmentHistory(options.db, sessionId)) {
197598
+ if (emergencyRecoveryArmed && realUsagePercentageBeforeEmergencyBump < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId) && !hasEligiblePiCompartmentHistory(options.db, sessionId)) {
197386
197599
  try {
197387
197600
  clearEmergencyRecovery(options.db, sessionId);
197388
197601
  sessionLog(sessionId, "EMERGENCY: disarming recovery — no eligible pre-tail history to compact (would otherwise loop at 95%)");
@@ -197437,6 +197650,7 @@ function registerPiContextHandler(pi, baseOptions) {
197437
197650
  reasoningClearing: {
197438
197651
  clearReasoningAge: options.heuristics?.clearReasoningAge ?? DEFAULT_CLEAR_REASONING_AGE
197439
197652
  },
197653
+ canUseEmptySentinels,
197440
197654
  temporalAwareness: options.injection?.temporalAwareness === true,
197441
197655
  appendCompaction: resolvePiAppendCompaction(ctx),
197442
197656
  readBranchEntries: resolvePiReadBranchEntries(ctx),
@@ -197458,6 +197672,7 @@ function registerPiContextHandler(pi, baseOptions) {
197458
197672
  }
197459
197673
  if (options.historian) {
197460
197674
  maybeFireHistorian({
197675
+ pi,
197461
197676
  ctx,
197462
197677
  sessionId,
197463
197678
  db: options.db,
@@ -197698,6 +197913,7 @@ function sendPiIgnoredNotification(ctx, message) {
197698
197913
  }
197699
197914
  function spawnPiHistorianRun(args) {
197700
197915
  const {
197916
+ pi,
197701
197917
  ctx,
197702
197918
  sessionId,
197703
197919
  db,
@@ -197706,7 +197922,8 @@ function spawnPiHistorianRun(args) {
197706
197922
  unregister,
197707
197923
  boundarySnapshot,
197708
197924
  refreshBoundarySnapshot,
197709
- currentContextLimit
197925
+ currentContextLimit,
197926
+ fallbackModelId
197710
197927
  } = args;
197711
197928
  const holderId = crypto3.randomUUID();
197712
197929
  const runPromise = (async () => {
@@ -197732,6 +197949,7 @@ function spawnPiHistorianRun(args) {
197732
197949
  runner: historian.runner,
197733
197950
  historianModel: historian.model,
197734
197951
  fallbackModels: historian.fallbackModels,
197952
+ fallbackModelId,
197735
197953
  historianChunkTokens: historian.historianChunkTokens,
197736
197954
  boundarySnapshot,
197737
197955
  refreshBoundarySnapshot,
@@ -197744,6 +197962,17 @@ function spawnPiHistorianRun(args) {
197744
197962
  userMemoriesEnabled: historian.userMemoriesEnabled,
197745
197963
  language: historian.language,
197746
197964
  compartmentLeaseHolderId: holderId,
197965
+ notifyIssue: (text) => {
197966
+ if (!isContextHandlerSessionActive(sessionId)) {
197967
+ sessionLog(sessionId, "historian failure notice skipped after session context cleared");
197968
+ return;
197969
+ }
197970
+ sendCtxStatusMessage(pi, {
197971
+ title: "Magic Context",
197972
+ text,
197973
+ level: "warning"
197974
+ });
197975
+ },
197747
197976
  onPublished: () => {
197748
197977
  const sessionStillActive = isContextHandlerSessionActive(sessionId);
197749
197978
  try {
@@ -197893,6 +198122,7 @@ function maybeFireHistorian(args) {
197893
198122
 
197894
198123
  Historian previously failed ${failureState.failureCount} time(s), so Magic Context is retrying history comparting immediately after restart.`);
197895
198124
  spawnPiHistorianRun({
198125
+ pi: args.pi,
197896
198126
  ctx,
197897
198127
  sessionId,
197898
198128
  db,
@@ -197901,7 +198131,8 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
197901
198131
  unregister,
197902
198132
  boundarySnapshot,
197903
198133
  refreshBoundarySnapshot: resolveRunnablePiBoundarySnapshot,
197904
- currentContextLimit: boundaryContextLimit
198134
+ currentContextLimit: boundaryContextLimit,
198135
+ fallbackModelId: modelKey
197905
198136
  });
197906
198137
  return;
197907
198138
  }
@@ -197923,6 +198154,7 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
197923
198154
  triggered = true;
197924
198155
  sessionLog(sessionId, `historian trigger fired (reason=${trigger.reason ?? "unknown"}) usage=${usage.percentage.toFixed(1)}% — spawning subagent`);
197925
198156
  spawnPiHistorianRun({
198157
+ pi: args.pi,
197926
198158
  ctx,
197927
198159
  sessionId,
197928
198160
  db,
@@ -197934,7 +198166,8 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
197934
198166
  triggerBoundarySnapshot: trigger.boundarySnapshot
197935
198167
  }),
197936
198168
  refreshBoundarySnapshot: resolveRunnablePiBoundarySnapshot,
197937
- currentContextLimit: boundaryContextLimit
198169
+ currentContextLimit: boundaryContextLimit,
198170
+ fallbackModelId: modelKey
197938
198171
  });
197939
198172
  } catch (err) {
197940
198173
  const message = err instanceof Error ? err.message : String(err);
@@ -198014,7 +198247,10 @@ async function runPipeline(args) {
198014
198247
  historyBudgetTokens: args.injection.historyBudgetTokens,
198015
198248
  hardSignals: piHardSignals
198016
198249
  }, args.db, getCompartments(args.db, args.sessionId)).value : false;
198017
- const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || hasPendingMaterialization(args.sessionId) || deferredMaterializeEligible || m0HardFoldThisPass || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
198250
+ const historianRunning = inFlightHistorian.has(args.sessionId);
198251
+ const bypassHistorianGate = args.forceMaterialization === true || m0HardFoldThisPass;
198252
+ const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
198253
+ const shouldRunHeuristics = args.heuristics !== undefined && (!historianRunning || bypassHistorianGate) && (args.forceMaterialization === true || hasPendingMaterializeSignal || deferredMaterializeEligible || m0HardFoldThisPass || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
198018
198254
  const entryFingerprintByMessageId = buildEntryFingerprintMap(args.messages, stableIdResolver);
198019
198255
  adoptPiFallbackTags(args.db, args.sessionId, args.tagger, entryFingerprintByMessageId, {
198020
198256
  messages: args.messages,
@@ -198048,14 +198284,13 @@ async function runPipeline(args) {
198048
198284
  } catch (err) {
198049
198285
  sessionLog(args.sessionId, `commit-detect failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
198050
198286
  }
198051
- const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
198052
198287
  const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
198053
198288
  const deferredHistoryRefreshWasPending = deferredHistoryWasPendingAtPassStart;
198054
198289
  const pendingOps = getPendingOps(args.db, args.sessionId);
198055
198290
  const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal || m0HardFoldThisPass;
198056
198291
  const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
198057
198292
  const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
198058
- const shouldApplyPendingOps = baseShouldApplyPendingOps || deferredMaterialize;
198293
+ const shouldApplyPendingOps = (baseShouldApplyPendingOps || deferredMaterialize) && (!historianRunning || bypassHistorianGate);
198059
198294
  if (shouldApplyPendingOps) {
198060
198295
  const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : m0HardFoldThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into known m[0] bust, scheduler=${args.schedulerDecision})` : `scheduler_execute (scheduler=${args.schedulerDecision})`;
198061
198296
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
@@ -198144,6 +198379,7 @@ async function runPipeline(args) {
198144
198379
  const tHeuristic = performance.now();
198145
198380
  heuristicsResult = applyPiHeuristicCleanup(args.sessionId, args.db, targets, args.messages, {
198146
198381
  protectedTags: args.protectedTags,
198382
+ staleReduceStripEnabled: args.canUseEmptySentinels,
198147
198383
  emergency: args.forceMaterialization === true && args.emergencyCeilingTokens !== undefined && args.emergencyCeilingTokens > 0 ? {
198148
198384
  currentTotalInputTokens: args.contextUsage.inputTokens,
198149
198385
  ceilingTokens: args.emergencyCeilingTokens
@@ -198729,7 +198965,7 @@ init_data_path();
198729
198965
  function historianResponseDumpDir(directory) {
198730
198966
  return getProjectMagicContextHistorianDir(directory);
198731
198967
  }
198732
- var MAX_HISTORIAN_RETRIES = 2;
198968
+ var MAX_HISTORIAN_RETRIES2 = 2;
198733
198969
  async function runValidatedHistorianPass(args) {
198734
198970
  const firstRun = await runHistorianPrompt({
198735
198971
  ...args,
@@ -198870,7 +199106,7 @@ async function runHistorianPrompt(args) {
198870
199106
  });
198871
199107
  return { ok: false, error: "Historian could not create its child session." };
198872
199108
  }
198873
- for (let retryIndex = 0;retryIndex <= MAX_HISTORIAN_RETRIES; retryIndex += 1) {
199109
+ for (let retryIndex = 0;retryIndex <= MAX_HISTORIAN_RETRIES2; retryIndex += 1) {
198874
199110
  try {
198875
199111
  await promptSyncWithModelSuggestionRetry(client, {
198876
199112
  path: { id: agentSessionId },
@@ -198885,17 +199121,17 @@ async function runHistorianPrompt(args) {
198885
199121
  fallbackModels: modelOverride ? undefined : fallbackModels,
198886
199122
  callContext: agentId === HISTORIAN_EDITOR_AGENT ? "historian:editor" : "historian"
198887
199123
  });
198888
- sessionLog(parentSessionId, `historian: prompt completed (attempt ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES + 1})`);
199124
+ sessionLog(parentSessionId, `historian: prompt completed (attempt ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES2 + 1})`);
198889
199125
  break;
198890
199126
  } catch (error51) {
198891
199127
  const errorMsg = getErrorMessage(error51);
198892
199128
  sessionLog(parentSessionId, `historian: prompt attempt ${retryIndex + 1} failed: ${errorMsg}`);
198893
- const shouldRetry = retryIndex < MAX_HISTORIAN_RETRIES && isTransientHistorianPromptError(errorMsg);
199129
+ const shouldRetry = retryIndex < MAX_HISTORIAN_RETRIES2 && isTransientHistorianPromptError2(errorMsg);
198894
199130
  if (!shouldRetry) {
198895
199131
  throw error51;
198896
199132
  }
198897
- const backoffMs2 = getHistorianRetryBackoffMs(retryIndex);
198898
- sessionLog(parentSessionId, `historian retry ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES} after ${backoffMs2}ms: ${errorMsg}`);
199133
+ const backoffMs2 = getHistorianRetryBackoffMs2(retryIndex);
199134
+ sessionLog(parentSessionId, `historian retry ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES2} after ${backoffMs2}ms: ${errorMsg}`);
198899
199135
  await sleep(backoffMs2);
198900
199136
  }
198901
199137
  }
@@ -198988,13 +199224,13 @@ function parseModelOverride(modelId) {
198988
199224
  }
198989
199225
  return { providerID, modelID };
198990
199226
  }
198991
- function getHistorianRetryBackoffMs(retryIndex) {
199227
+ function getHistorianRetryBackoffMs2(retryIndex) {
198992
199228
  if (retryIndex === 0) {
198993
199229
  return 2000 + Math.floor(Math.random() * 1001);
198994
199230
  }
198995
199231
  return 6000 + Math.floor(Math.random() * 2001);
198996
199232
  }
198997
- function isTransientHistorianPromptError(message) {
199233
+ function isTransientHistorianPromptError2(message) {
198998
199234
  const normalized = message.toLowerCase();
198999
199235
  if (normalized.includes("invalid request") || normalized.includes("bad request") || normalized.includes("unauthorized") || normalized.includes("forbidden") || normalized.includes("authentication") || normalized.includes("auth") || normalized.includes(" 400") || normalized.startsWith("400")) {
199000
199236
  return false;
@@ -200238,6 +200474,13 @@ function spawnPiRecompRun(args) {
200238
200474
  // src/commands/ctx-recomp.ts
200239
200475
  var confirmationBySession = new Map;
200240
200476
  var RECOMP_CONFIRMATION_WINDOW_MS = 60000;
200477
+ var RECOMP_USAGE = [
200478
+ "Usage:",
200479
+ "- `/ctx-recomp` — full rebuild from message 1 to the protected tail",
200480
+ "- `/ctx-recomp <start>-<end>` — partial rebuild of a message range (e.g. `/ctx-recomp 1-11322`)",
200481
+ "- `/ctx-recomp --upgrade` — upgrade legacy v1 compartments to v2 layout (Wave 3 runner)"
200482
+ ].join(`
200483
+ `);
200241
200484
  function registerCtxRecompCommand(pi, deps) {
200242
200485
  pi.registerCommand("ctx-recomp", {
200243
200486
  description: "Rebuild Magic Context compartments from raw Pi session history",
@@ -200253,21 +200496,30 @@ No active Pi session is available.`,
200253
200496
  });
200254
200497
  return;
200255
200498
  }
200256
- if (!deps.historianModel) {
200499
+ const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
200500
+ const parsed = parseRecompArgs(args);
200501
+ if (parsed.kind === "error") {
200257
200502
  sendCtxStatusMessage(pi, {
200258
200503
  title: "/ctx-recomp",
200259
- text: "## Magic Recomp\n\n/ctx-recomp is unavailable because `historian.model` is not configured.",
200504
+ text: `## Magic Recomp — Invalid Arguments
200505
+
200506
+ ${parsed.message}`,
200260
200507
  level: "error"
200261
200508
  });
200262
200509
  return;
200263
200510
  }
200264
- const parsed = parseRecompArgs(args);
200265
- if (parsed.kind === "error") {
200511
+ if (parsed.kind === "upgrade") {
200266
200512
  sendCtxStatusMessage(pi, {
200267
200513
  title: "/ctx-recomp",
200268
- text: `## Magic Recomp — Invalid Arguments
200269
-
200270
- ${parsed.message}`,
200514
+ text: executeRecompUpgradeStub(currentDeps.db, sessionId),
200515
+ level: "info"
200516
+ });
200517
+ return;
200518
+ }
200519
+ if (!currentDeps.historianModel) {
200520
+ sendCtxStatusMessage(pi, {
200521
+ title: "/ctx-recomp",
200522
+ text: "## Magic Recomp\n\n/ctx-recomp is unavailable because `historian.model` is not configured.",
200271
200523
  level: "error"
200272
200524
  });
200273
200525
  return;
@@ -200277,7 +200529,7 @@ ${parsed.message}`,
200277
200529
  const confirmation = confirmationBySession.get(sessionId);
200278
200530
  const confirmed = confirmation !== undefined && now - confirmation.timestamp < RECOMP_CONFIRMATION_WINDOW_MS && confirmation.argsKey === argsKey;
200279
200531
  if (!confirmed) {
200280
- const warning = buildConfirmationWarning(deps.db, sessionId, parsed);
200532
+ const warning = buildConfirmationWarning(currentDeps.db, sessionId, parsed);
200281
200533
  if (!warning.confirmable)
200282
200534
  confirmationBySession.delete(sessionId);
200283
200535
  else
@@ -200289,7 +200541,7 @@ ${parsed.message}`,
200289
200541
  });
200290
200542
  return;
200291
200543
  }
200292
- if (isWrapupInProgress(deps.db, sessionId)) {
200544
+ if (isWrapupInProgress(currentDeps.db, sessionId)) {
200293
200545
  sendCtxStatusMessage(pi, {
200294
200546
  title: "/ctx-recomp",
200295
200547
  text: "## Magic Recomp\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
@@ -200323,16 +200575,19 @@ Historian recomp started. Rebuilding compartments and facts from raw Pi session
200323
200575
  spawnPiRecompRun({
200324
200576
  sessionId,
200325
200577
  provider: provider2,
200326
- onStatusChange: () => updateStatusLine(ctx, { db: deps.db, projectIdentity: ctx.cwd }),
200578
+ onStatusChange: () => updateStatusLine(ctx, {
200579
+ db: currentDeps.db,
200580
+ projectIdentity: ctx.cwd
200581
+ }),
200327
200582
  work: async () => {
200328
200583
  const result = await executeContextRecompWithResult({
200329
200584
  client: createPiHistorianClient({
200330
- runner: deps.runner,
200331
- model: deps.historianModel,
200332
- systemPrompt: withContentLanguageDirective(COMPARTMENT_STRUCTURAL_SYSTEM_PROMPT, deps.language, { preserveUserQuotes: true }),
200333
- fallbackModels: deps.historianFallbacks,
200334
- timeoutMs: deps.historianTimeoutMs,
200335
- thinkingLevel: deps.historianThinkingLevel,
200585
+ runner: currentDeps.runner,
200586
+ model: currentDeps.historianModel,
200587
+ systemPrompt: withContentLanguageDirective(COMPARTMENT_STRUCTURAL_SYSTEM_PROMPT, currentDeps.language, { preserveUserQuotes: true }),
200588
+ fallbackModels: currentDeps.historianFallbacks,
200589
+ timeoutMs: currentDeps.historianTimeoutMs,
200590
+ thinkingLevel: currentDeps.historianThinkingLevel,
200336
200591
  directory: ctx.cwd,
200337
200592
  accountingSessionId: sessionId,
200338
200593
  notify: (text) => {
@@ -200343,26 +200598,26 @@ Historian recomp started. Rebuilding compartments and facts from raw Pi session
200343
200598
  });
200344
200599
  }
200345
200600
  }),
200346
- db: deps.db,
200601
+ db: currentDeps.db,
200347
200602
  sessionId,
200348
- historianChunkTokens: deps.historianChunkTokens,
200603
+ historianChunkTokens: currentDeps.historianChunkTokens,
200349
200604
  directory: ctx.cwd,
200350
- historianTimeoutMs: deps.historianTimeoutMs,
200351
- memoryEnabled: deps.memoryEnabled,
200352
- autoPromote: deps.autoPromote,
200605
+ historianTimeoutMs: currentDeps.historianTimeoutMs,
200606
+ memoryEnabled: currentDeps.memoryEnabled,
200607
+ autoPromote: currentDeps.autoPromote,
200353
200608
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
200354
- fallbackModels: deps.historianFallbacks,
200355
- language: deps.language,
200609
+ fallbackModels: currentDeps.historianFallbacks,
200610
+ language: currentDeps.language,
200356
200611
  fallbackModelId: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined
200357
200612
  }, parsed.kind === "partial" ? { range: parsed.range } : {});
200358
200613
  if (result.published) {
200359
200614
  try {
200360
- clearEmergencyRecovery(deps.db, sessionId);
200615
+ clearEmergencyRecovery(currentDeps.db, sessionId);
200361
200616
  } catch (recoveryError) {
200362
200617
  sessionLog(sessionId, `/ctx-recomp: clearEmergencyRecovery failed (continuing): ${describeError(recoveryError).brief}`);
200363
200618
  }
200364
200619
  try {
200365
- stagePiRecompMarker({ db: deps.db, sessionId, ctx });
200620
+ stagePiRecompMarker({ db: currentDeps.db, sessionId, ctx });
200366
200621
  } catch (markerError) {
200367
200622
  sessionLog(sessionId, `/ctx-recomp: marker staging failed (recomp already published; continuing): ${describeError(markerError).brief}`);
200368
200623
  }
@@ -200383,11 +200638,15 @@ function parseRecompArgs(raw) {
200383
200638
  const trimmed = raw.trim();
200384
200639
  if (trimmed.length === 0)
200385
200640
  return { kind: "full" };
200641
+ if (trimmed === "--upgrade")
200642
+ return { kind: "upgrade" };
200386
200643
  const match = trimmed.match(/^(\d+)\s*-\s*(\d+)$/);
200387
200644
  if (!match) {
200388
200645
  return {
200389
200646
  kind: "error",
200390
- message: "Usage:\n- `/ctx-recomp` — full rebuild from message 1 to the protected tail\n- `/ctx-recomp <start>-<end>` — partial rebuild of a message range"
200647
+ message: `Invalid /ctx-recomp arguments: \`${trimmed}\`.
200648
+
200649
+ ${RECOMP_USAGE}`
200391
200650
  };
200392
200651
  }
200393
200652
  const start = Number.parseInt(match[1], 10);
@@ -200401,6 +200660,21 @@ function parseRecompArgs(raw) {
200401
200660
  };
200402
200661
  return { kind: "partial", range: { start, end } };
200403
200662
  }
200663
+ function executeRecompUpgradeStub(db, sessionId) {
200664
+ const legacyCount = getCompartments(db, sessionId).filter((compartment) => compartment.legacy === 1).length;
200665
+ if (legacyCount === 0) {
200666
+ return `## Magic Recomp Upgrade
200667
+
200668
+ Nothing to upgrade: this session has no legacy compartments.`;
200669
+ }
200670
+ return [
200671
+ "## Magic Recomp Upgrade",
200672
+ "",
200673
+ `Found ${legacyCount} legacy compartment${legacyCount === 1 ? "" : "s"} for this session.`,
200674
+ "The `--upgrade` flag is deprecated. Run `/ctx-session-upgrade` to upgrade this session."
200675
+ ].join(`
200676
+ `);
200677
+ }
200404
200678
  function buildConfirmationWarning(db, sessionId, parsed) {
200405
200679
  const compartments = getCompartments(db, sessionId);
200406
200680
  if (parsed.kind === "partial") {
@@ -200680,7 +200954,8 @@ No active Pi session is available.`,
200680
200954
  });
200681
200955
  return;
200682
200956
  }
200683
- if (!deps.historianModel) {
200957
+ const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
200958
+ if (!currentDeps.historianModel) {
200684
200959
  sendCtxStatusMessage(pi, {
200685
200960
  title: "/ctx-session-upgrade",
200686
200961
  text: "## Session Upgrade\n\nUnavailable because `historian.model` is not configured.",
@@ -200688,7 +200963,7 @@ No active Pi session is available.`,
200688
200963
  });
200689
200964
  return;
200690
200965
  }
200691
- if (isWrapupInProgress(deps.db, sessionId)) {
200966
+ if (isWrapupInProgress(currentDeps.db, sessionId)) {
200692
200967
  sendCtxStatusMessage(pi, {
200693
200968
  title: "/ctx-session-upgrade",
200694
200969
  text: `## Session Upgrade
@@ -200708,27 +200983,27 @@ An upgrade or recomp is already running for this session in the background. Wait
200708
200983
  });
200709
200984
  return;
200710
200985
  }
200711
- const compartments = getCompartments(deps.db, sessionId);
200986
+ const compartments = getCompartments(currentDeps.db, sessionId);
200712
200987
  const upgradableCount = compartments.filter((c) => c.legacy === 1 || !c.p1 || c.p1.trim() === "").length;
200713
200988
  const sessionMainModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
200714
- const migrationEnabled = deps.memoryEnabled;
200989
+ const migrationEnabled = currentDeps.memoryEnabled;
200715
200990
  const runMigration = async () => {
200716
200991
  if (!migrationEnabled) {
200717
200992
  return "Memory migration skipped (memory disabled).";
200718
200993
  }
200719
200994
  try {
200720
200995
  const outcome = await runPiMemoryMigration({
200721
- db: deps.db,
200722
- runner: deps.runner,
200996
+ db: currentDeps.db,
200997
+ runner: currentDeps.runner,
200723
200998
  primaryModel: sessionMainModel,
200724
- model: deps.historianModel,
200725
- fallbackModels: deps.historianFallbacks,
200726
- timeoutMs: deps.historianTimeoutMs,
200727
- thinkingLevel: deps.historianThinkingLevel,
200999
+ model: currentDeps.historianModel,
201000
+ fallbackModels: currentDeps.historianFallbacks,
201001
+ timeoutMs: currentDeps.historianTimeoutMs,
201002
+ thinkingLevel: currentDeps.historianThinkingLevel,
200728
201003
  directory: ctx.cwd,
200729
201004
  sessionId,
200730
- userMemoriesEnabled: deps.userMemoriesEnabled,
200731
- language: deps.language
201005
+ userMemoriesEnabled: currentDeps.userMemoriesEnabled,
201006
+ language: currentDeps.language
200732
201007
  });
200733
201008
  return outcome.summary;
200734
201009
  } catch (error51) {
@@ -200737,7 +201012,7 @@ An upgrade or recomp is already running for this session in the background. Wait
200737
201012
  };
200738
201013
  if (upgradableCount === 0) {
200739
201014
  const projectPath = resolveProjectIdentity(ctx.cwd);
200740
- const migrationPending = migrationEnabled && !isMemoryMigrationDone(deps.db, projectPath);
201015
+ const migrationPending = migrationEnabled && !isMemoryMigrationDone(currentDeps.db, projectPath);
200741
201016
  if (!migrationPending) {
200742
201017
  sendCtxStatusMessage(pi, {
200743
201018
  title: "/ctx-session-upgrade",
@@ -200763,7 +201038,10 @@ Compartments are already current. Re-organizing project memories. This may take
200763
201038
  provider: {
200764
201039
  readMessages: () => readPiSessionMessages(ctx)
200765
201040
  },
200766
- onStatusChange: () => updateStatusLine(ctx, { db: deps.db, projectIdentity: ctx.cwd }),
201041
+ onStatusChange: () => updateStatusLine(ctx, {
201042
+ db: currentDeps.db,
201043
+ projectIdentity: ctx.cwd
201044
+ }),
200767
201045
  work: async () => {
200768
201046
  const summary = await runMigration();
200769
201047
  sendCtxStatusMessage(pi, {
@@ -200789,35 +201067,38 @@ Rebuilding compartments into the v2 format and re-organizing project memories. T
200789
201067
  spawnPiRecompRun({
200790
201068
  sessionId,
200791
201069
  provider: provider2,
200792
- onStatusChange: () => updateStatusLine(ctx, { db: deps.db, projectIdentity: ctx.cwd }),
201070
+ onStatusChange: () => updateStatusLine(ctx, {
201071
+ db: currentDeps.db,
201072
+ projectIdentity: ctx.cwd
201073
+ }),
200793
201074
  work: async () => {
200794
201075
  const recompResult = await executeContextRecompWithResult({
200795
201076
  client: createPiHistorianClient({
200796
- runner: deps.runner,
200797
- model: deps.historianModel,
200798
- fallbackModels: deps.historianFallbacks,
200799
- timeoutMs: deps.historianTimeoutMs,
200800
- thinkingLevel: deps.historianThinkingLevel,
201077
+ runner: currentDeps.runner,
201078
+ model: currentDeps.historianModel,
201079
+ fallbackModels: currentDeps.historianFallbacks,
201080
+ timeoutMs: currentDeps.historianTimeoutMs,
201081
+ thinkingLevel: currentDeps.historianThinkingLevel,
200801
201082
  directory: ctx.cwd,
200802
201083
  accountingSessionId: sessionId,
200803
- systemPrompt: withContentLanguageDirective(COMPARTMENT_STRUCTURAL_SYSTEM_PROMPT, deps.language, { preserveUserQuotes: true }),
201084
+ systemPrompt: withContentLanguageDirective(COMPARTMENT_STRUCTURAL_SYSTEM_PROMPT, currentDeps.language, { preserveUserQuotes: true }),
200804
201085
  notify: (text) => sendCtxStatusMessage(pi, {
200805
201086
  title: "/ctx-session-upgrade",
200806
201087
  text,
200807
201088
  level: "info"
200808
201089
  })
200809
201090
  }),
200810
- db: deps.db,
201091
+ db: currentDeps.db,
200811
201092
  sessionId,
200812
- historianChunkTokens: deps.historianChunkTokens,
201093
+ historianChunkTokens: currentDeps.historianChunkTokens,
200813
201094
  directory: ctx.cwd,
200814
- historianTimeoutMs: deps.historianTimeoutMs,
200815
- memoryEnabled: deps.memoryEnabled,
200816
- autoPromote: deps.autoPromote,
201095
+ historianTimeoutMs: currentDeps.historianTimeoutMs,
201096
+ memoryEnabled: currentDeps.memoryEnabled,
201097
+ autoPromote: currentDeps.autoPromote,
200817
201098
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
200818
- fallbackModels: deps.historianFallbacks,
201099
+ fallbackModels: currentDeps.historianFallbacks,
200819
201100
  fallbackModelId: sessionMainModel,
200820
- language: deps.language
201101
+ language: currentDeps.language
200821
201102
  }, {});
200822
201103
  if (!recompResult.published || !isRecompComplete(recompResult.message)) {
200823
201104
  const reason = contextualizeUpgradeReason(isRecompFailure(recompResult.message) ? extractRecompReason(recompResult.message) : `Compartments were not fully rebuilt: ${extractRecompReason(recompResult.message)}`);
@@ -200831,7 +201112,7 @@ ${reason}`,
200831
201112
  return;
200832
201113
  }
200833
201114
  try {
200834
- stagePiRecompMarker({ db: deps.db, sessionId, ctx });
201115
+ stagePiRecompMarker({ db: currentDeps.db, sessionId, ctx });
200835
201116
  } catch (markerError) {
200836
201117
  sessionLog(sessionId, `pi /ctx-session-upgrade marker staging failed (non-fatal, recomp already published): ${describeError(markerError).brief}`);
200837
201118
  }
@@ -201029,7 +201310,7 @@ function formatThresholdPercent(value) {
201029
201310
  // package.json
201030
201311
  var package_default = {
201031
201312
  name: "@cortexkit/pi-magic-context",
201032
- version: "0.31.1",
201313
+ version: "0.31.3",
201033
201314
  type: "module",
201034
201315
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
201035
201316
  main: "dist/index.js",
@@ -201180,6 +201461,7 @@ function renderInner(s, theme, innerWidth) {
201180
201461
  lines.push(`${theme.fg("accent", theme.bold("⚡ Magic Context Status"))} ${theme.fg("muted", `v${package_default.version}`)}`);
201181
201462
  lines.push("");
201182
201463
  lines.push(`Context ${theme.fg(pctColor, theme.bold(`${s.usagePercentage.toFixed(1)}%`))} · ${fmt2(s.inputTokens)} / ${s.contextLimit > 0 ? fmt2(s.contextLimit) : "?"} tokens`);
201464
+ lines.push(`Work tokens ${fmt2(s.newWorkTokens)} new · ${fmt2(s.totalInputTokens)} total input`);
201183
201465
  lines.push(renderBar(s, innerWidth));
201184
201466
  for (const seg of breakdownSegments(s)) {
201185
201467
  const pct = (seg.tokens / (s.inputTokens || 1) * 100).toFixed(1);
@@ -201275,6 +201557,7 @@ ${safeStringify(tool.parameters)}`);
201275
201557
  }
201276
201558
  } catch {}
201277
201559
  const conversationTokens = Math.max(0, inputTokens - systemPromptTokens - compartmentTokens - factTokens - memoryTokens - docsTokens - profileTokens - toolCallTokens - toolDefinitionTokens);
201560
+ const workMetrics = getSessionWorkMetrics(deps.db, sessionId);
201278
201561
  const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
201279
201562
  const threshold = resolveExecuteThresholdDetail(deps.executeThresholdPercentage ?? 65, modelKey, 65, {
201280
201563
  tokensConfig: deps.executeThresholdTokens,
@@ -201338,6 +201621,8 @@ ${safeStringify(tool.parameters)}`);
201338
201621
  conversationTokens,
201339
201622
  toolCallTokens,
201340
201623
  toolDefinitionTokens,
201624
+ newWorkTokens: workMetrics.newWorkTokens,
201625
+ totalInputTokens: workMetrics.totalInputTokens,
201341
201626
  upgradeNeededCount: safeRead(() => countCompartmentsNeedingUpgrade(deps.db, sessionId), 0),
201342
201627
  recompInFlight: isPiRecompInFlight(sessionId)
201343
201628
  };
@@ -201492,8 +201777,9 @@ function registerCtxStatusCommand(pi, deps) {
201492
201777
  pi.registerCommand("ctx-status", {
201493
201778
  description: "Show Magic Context status for the current Pi session",
201494
201779
  handler: async (_args, ctx) => {
201495
- const projectIdentity = deps.resolveProject?.(ctx).projectIdentity ?? deps.projectIdentity;
201496
- const currentDeps = { ...deps, projectIdentity };
201780
+ const runtimeDeps = deps.resolveStatusDeps?.(ctx) ?? deps;
201781
+ const projectIdentity = runtimeDeps.resolveProject?.(ctx).projectIdentity ?? runtimeDeps.projectIdentity;
201782
+ const currentDeps = { ...runtimeDeps, projectIdentity };
201497
201783
  const sessionId = resolveSessionId(ctx);
201498
201784
  if (!sessionId) {
201499
201785
  sendCtxStatusMessage(pi, {
@@ -201608,7 +201894,8 @@ No active Pi session is available.`,
201608
201894
  });
201609
201895
  return;
201610
201896
  }
201611
- const sessionMeta = getOrCreateSessionMeta(deps.db, sessionId);
201897
+ const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
201898
+ const sessionMeta = getOrCreateSessionMeta(currentDeps.db, sessionId);
201612
201899
  if (sessionMeta.isSubagent) {
201613
201900
  sendCtxStatusMessage(pi, {
201614
201901
  title: "/ctx-wrapup",
@@ -201619,7 +201906,7 @@ No active Pi session is available.`,
201619
201906
  });
201620
201907
  return;
201621
201908
  }
201622
- if (!deps.historianModel) {
201909
+ if (!currentDeps.historianModel) {
201623
201910
  sendCtxStatusMessage(pi, {
201624
201911
  title: "/ctx-wrapup",
201625
201912
  text: "## Magic Wrapup\n\n/ctx-wrapup is unavailable because `historian.model` is not configured.",
@@ -201638,7 +201925,7 @@ ${parsed.message}`,
201638
201925
  });
201639
201926
  return;
201640
201927
  }
201641
- const result = await runPiWrapup(pi, deps, ctx, sessionId, parsed.messagesToKeep);
201928
+ const result = await runPiWrapup(pi, currentDeps, ctx, sessionId, parsed.messagesToKeep);
201642
201929
  sendCtxStatusMessage(pi, {
201643
201930
  title: "/ctx-wrapup",
201644
201931
  text: result,
@@ -201812,6 +202099,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
201812
202099
  runner: deps.runner,
201813
202100
  historianModel: deps.historianModel,
201814
202101
  fallbackModels: deps.historianFallbacks,
202102
+ fallbackModelId: modelKey,
201815
202103
  historianChunkTokens: deps.historianChunkTokens,
201816
202104
  boundarySnapshot: plan.snapshot,
201817
202105
  refreshBoundarySnapshot: () => resolveWrapupProtectedTailBoundary({
@@ -206393,16 +206681,16 @@ __export(exports_typebox, {
206393
206681
  // src/tools/ctx-expand.ts
206394
206682
  var ParamsSchema = exports_typebox.Object({
206395
206683
  start: exports_typebox.Optional(exports_typebox.Number({
206396
- description: "Start message ordinal (from compartment start attribute)"
206684
+ description: `First message ordinal to expand — a compartment's start="N" attribute, or an ordinal from a ctx_search message hit`
206397
206685
  })),
206398
206686
  end: exports_typebox.Optional(exports_typebox.Number({
206399
- description: "End message ordinal (from compartment end attribute)"
206687
+ description: `Last message ordinal to expand (inclusive) — a compartment's end="M" attribute`
206400
206688
  })),
206401
206689
  verbose: exports_typebox.Optional(exports_typebox.Boolean({
206402
206690
  description: "With start/end: list each message separately with its ordinal [N] and per-part preview, so you can recover one in full by ordinal."
206403
206691
  })),
206404
206692
  message: exports_typebox.Optional(exports_typebox.Number({
206405
- description: "Full untruncated recovery of ONE message by its ordinal (text + every tool call's full input/output). Recovers a tool output you dropped with ctx_reduce."
206693
+ description: "Full untruncated recovery of ONE message by its ordinal (every text part + every tool call's complete input/output). Use an ordinal from a compartment, ctx_search hit, or verbose range. Recovers a tool output you dropped with ctx_reduce."
206406
206694
  }))
206407
206695
  });
206408
206696
  function ok(text) {
@@ -206553,25 +206841,45 @@ function formatMemoryList(memories) {
206553
206841
  id: String(m.id),
206554
206842
  category: m.category,
206555
206843
  status: m.status,
206844
+ verification: m.verificationStatus,
206556
206845
  updated: new Date(m.updatedAt).toISOString(),
206557
206846
  content: m.content.replace(/\s+/g, " ").trim()
206558
206847
  }));
206848
+ const headers = {
206849
+ id: "ID",
206850
+ category: "CATEGORY",
206851
+ status: "STATUS",
206852
+ verification: "VERIFY",
206853
+ updated: "UPDATED",
206854
+ content: "CONTENT"
206855
+ };
206559
206856
  const widths = {
206560
- id: Math.max(2, ...rows.map((r) => r.id.length)),
206561
- category: Math.max(8, ...rows.map((r) => r.category.length)),
206562
- status: Math.max(6, ...rows.map((r) => r.status.length)),
206563
- updated: Math.max(7, ...rows.map((r) => r.updated.length))
206857
+ id: Math.max(headers.id.length, ...rows.map((r) => r.id.length)),
206858
+ category: Math.max(headers.category.length, ...rows.map((r) => r.category.length)),
206859
+ status: Math.max(headers.status.length, ...rows.map((r) => r.status.length)),
206860
+ verification: Math.max(headers.verification.length, ...rows.map((r) => r.verification.length)),
206861
+ updated: Math.max(headers.updated.length, ...rows.map((r) => r.updated.length))
206564
206862
  };
206565
206863
  const fmt3 = (r) => [
206566
206864
  r.id.padEnd(widths.id),
206567
206865
  r.category.padEnd(widths.category),
206568
206866
  r.status.padEnd(widths.status),
206867
+ r.verification.padEnd(widths.verification),
206569
206868
  r.updated.padEnd(widths.updated),
206570
206869
  r.content
206571
206870
  ].join(" | ");
206572
206871
  return [
206573
206872
  `Found ${rows.length} active ${rows.length === 1 ? "memory" : "memories"}:`,
206574
206873
  "",
206874
+ fmt3(headers),
206875
+ [
206876
+ "-".repeat(widths.id),
206877
+ "-".repeat(widths.category),
206878
+ "-".repeat(widths.status),
206879
+ "-".repeat(widths.verification),
206880
+ "-".repeat(widths.updated),
206881
+ "-------"
206882
+ ].join("-+-"),
206575
206883
  ...rows.map(fmt3)
206576
206884
  ].join(`
206577
206885
  `);
@@ -206977,6 +207285,7 @@ function createCtxNoteTool(deps) {
206977
207285
  parameters: ParamsSchema3,
206978
207286
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
206979
207287
  const sessionId = ctx.sessionManager.getSessionId();
207288
+ const dreamerEnabled = deps.resolveDreamerEnabled?.(ctx) ?? deps.dreamerEnabled;
206980
207289
  const action2 = params.action ?? (params.content?.trim() ? "write" : "read");
206981
207290
  if (action2 === "write") {
206982
207291
  const content = params.content?.trim();
@@ -206985,7 +207294,7 @@ function createCtxNoteTool(deps) {
206985
207294
  const anchorOrdinal = captureAnchorOrdinal(deps.db, sessionId);
206986
207295
  const surfaceCondition = params.surface_condition?.trim();
206987
207296
  if (surfaceCondition) {
206988
- if (deps.dreamerEnabled !== true) {
207297
+ if (dreamerEnabled !== true) {
206989
207298
  return err3("Error: Smart notes require dreamer to be enabled. Enable dreamer in magic-context.jsonc to use surface_condition.");
206990
207299
  }
206991
207300
  const projectIdentity = resolveProjectIdentity(ctx.cwd);
@@ -206994,6 +207303,7 @@ function createCtxNoteTool(deps) {
206994
207303
  }
206995
207304
  const note2 = addNote(deps.db, "smart", {
206996
207305
  content,
207306
+ sessionId,
206997
207307
  projectPath: projectIdentity,
206998
207308
  surfaceCondition,
206999
207309
  anchorOrdinal
@@ -207245,6 +207555,7 @@ function createCtxReduceTool(deps) {
207245
207555
  parameters: ParamsSchema4,
207246
207556
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
207247
207557
  const sessionId = ctx.sessionManager.getSessionId();
207558
+ const protectedTags = Math.max(0, Math.floor(deps.resolveProtectedTags?.(ctx) ?? deps.protectedTags));
207248
207559
  if (!params.drop) {
207249
207560
  return err4("Error: 'drop' must be provided.");
207250
207561
  }
@@ -207262,7 +207573,7 @@ function createCtxReduceTool(deps) {
207262
207573
  return err4(`Error: Unknown tag(s) ${formatIds(unknownIds)}. Check available tags in conversation.`);
207263
207574
  }
207264
207575
  const activeTags = allTags.filter((tag) => tag.status === "active");
207265
- const protectedTagIds = activeTags.map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, deps.protectedTags);
207576
+ const protectedTagIds = activeTags.map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, protectedTags);
207266
207577
  const protectedSet = new Set(protectedTagIds);
207267
207578
  const tagStatusMap = new Map(allTags.map((tag) => [tag.tagNumber, tag.status]));
207268
207579
  const pendingOps = getPendingOps(deps.db, sessionId);
@@ -207489,6 +207800,417 @@ function createCtxSearchTool(deps) {
207489
207800
  };
207490
207801
  }
207491
207802
 
207803
+ // src/tools/todo-view-pi.ts
207804
+ import {
207805
+ truncateToWidth as truncateToWidth2
207806
+ } from "@earendil-works/pi-tui";
207807
+ var TODO_TOOL_NAME = "todowrite";
207808
+ var TODOS_COMMAND_NAME = "todos";
207809
+ var WIDGET_KEY = "magic-context-todos";
207810
+ var MAX_OVERLAY_CONTENT_ROWS = 12;
207811
+ var TODO_STATUSES = [
207812
+ "pending",
207813
+ "in_progress",
207814
+ "completed",
207815
+ "cancelled"
207816
+ ];
207817
+ var TODO_PRIORITIES = ["high", "medium", "low"];
207818
+ var STATUS_GLYPH = {
207819
+ pending: "○",
207820
+ in_progress: "◐",
207821
+ completed: "✓",
207822
+ cancelled: "✗"
207823
+ };
207824
+ var STATUS_COLOR = {
207825
+ pending: "dim",
207826
+ in_progress: "warning",
207827
+ completed: "success",
207828
+ cancelled: "error"
207829
+ };
207830
+ var snapshotsBySession = new Map;
207831
+ function isTodoStatus(value) {
207832
+ return TODO_STATUSES.includes(value);
207833
+ }
207834
+ function isTodoPriority(value) {
207835
+ return TODO_PRIORITIES.includes(value);
207836
+ }
207837
+ function cloneTodos(todos) {
207838
+ return todos.map((todo) => ({ ...todo }));
207839
+ }
207840
+ function parseTodos(input) {
207841
+ if (!Array.isArray(input))
207842
+ return null;
207843
+ const todos = [];
207844
+ for (const item of input) {
207845
+ if (item === null || typeof item !== "object")
207846
+ return null;
207847
+ const raw = item;
207848
+ if (typeof raw.content !== "string" || !isTodoStatus(raw.status)) {
207849
+ return null;
207850
+ }
207851
+ const todo = {
207852
+ content: raw.content,
207853
+ status: raw.status
207854
+ };
207855
+ if (isTodoPriority(raw.priority))
207856
+ todo.priority = raw.priority;
207857
+ if (typeof raw.id === "string" && raw.id.length > 0)
207858
+ todo.id = raw.id;
207859
+ todos.push(todo);
207860
+ }
207861
+ return todos;
207862
+ }
207863
+ function parseTodoStateJson(stateJson) {
207864
+ if (!stateJson)
207865
+ return null;
207866
+ try {
207867
+ return parseTodos(JSON.parse(stateJson));
207868
+ } catch {
207869
+ return null;
207870
+ }
207871
+ }
207872
+ function setTodoSnapshot(sessionId, todos) {
207873
+ const parsed = parseTodos(todos);
207874
+ if (parsed === null)
207875
+ return false;
207876
+ snapshotsBySession.set(sessionId, { todos: parsed });
207877
+ return true;
207878
+ }
207879
+ function seedTodoSnapshotFromStateJson(sessionId, stateJson) {
207880
+ const parsed = parseTodoStateJson(stateJson);
207881
+ if (parsed === null) {
207882
+ snapshotsBySession.delete(sessionId);
207883
+ return false;
207884
+ }
207885
+ snapshotsBySession.set(sessionId, { todos: parsed });
207886
+ return true;
207887
+ }
207888
+ function getTodoSnapshot(sessionId) {
207889
+ if (!sessionId)
207890
+ return { todos: [] };
207891
+ const snapshot = snapshotsBySession.get(sessionId);
207892
+ return { todos: snapshot ? cloneTodos(snapshot.todos) : [] };
207893
+ }
207894
+ function clearTodoSnapshot(sessionId) {
207895
+ snapshotsBySession.delete(sessionId);
207896
+ }
207897
+ function getSessionId(ctx) {
207898
+ try {
207899
+ const id = ctx.sessionManager?.getSessionId?.();
207900
+ return typeof id === "string" && id.length > 0 ? id : undefined;
207901
+ } catch {
207902
+ return;
207903
+ }
207904
+ }
207905
+ function countTodos(todos) {
207906
+ const counts = {
207907
+ total: todos.length,
207908
+ pending: 0,
207909
+ inProgress: 0,
207910
+ completed: 0,
207911
+ cancelled: 0
207912
+ };
207913
+ for (const todo of todos) {
207914
+ switch (todo.status) {
207915
+ case "pending":
207916
+ counts.pending++;
207917
+ break;
207918
+ case "in_progress":
207919
+ counts.inProgress++;
207920
+ break;
207921
+ case "completed":
207922
+ counts.completed++;
207923
+ break;
207924
+ case "cancelled":
207925
+ counts.cancelled++;
207926
+ break;
207927
+ }
207928
+ }
207929
+ return counts;
207930
+ }
207931
+ function activeTitleCount(todos) {
207932
+ return todos.filter((todo) => todo.status !== "completed").length;
207933
+ }
207934
+ function formatCounts(counts) {
207935
+ if (counts.total === 0)
207936
+ return "No todos";
207937
+ const parts = [`${counts.completed}/${counts.total} completed`];
207938
+ if (counts.inProgress > 0)
207939
+ parts.push(`${counts.inProgress} in progress`);
207940
+ if (counts.pending > 0)
207941
+ parts.push(`${counts.pending} pending`);
207942
+ if (counts.cancelled > 0)
207943
+ parts.push(`${counts.cancelled} cancelled`);
207944
+ return parts.join(" · ");
207945
+ }
207946
+ function formatTodoLine(todo, theme, options = {}) {
207947
+ const glyph = theme.fg(STATUS_COLOR[todo.status], STATUS_GLYPH[todo.status]);
207948
+ const id = options.showId && todo.id ? `${theme.fg("accent", `#${todo.id}`)} ` : "";
207949
+ const color = todo.status === "pending" || todo.status === "in_progress" ? "text" : "dim";
207950
+ let content = theme.fg(color, todo.content);
207951
+ if (todo.status === "completed" || todo.status === "cancelled") {
207952
+ content = theme.strikethrough(content);
207953
+ }
207954
+ return `${glyph} ${id}${content}`;
207955
+ }
207956
+ function formatCommandLine(todo) {
207957
+ const id = todo.id ? `#${todo.id} ` : "";
207958
+ return ` ${STATUS_GLYPH[todo.status]} ${id}${todo.content}`;
207959
+ }
207960
+ function lineComponent(renderLines) {
207961
+ return {
207962
+ render: renderLines,
207963
+ invalidate() {}
207964
+ };
207965
+ }
207966
+ function renderTodowriteCall(args, theme) {
207967
+ const todos = parseTodos(args.todos) ?? [];
207968
+ const active = activeTitleCount(todos);
207969
+ const activeColor = active > 0 ? "warning" : "success";
207970
+ const line = `${theme.fg("toolTitle", theme.bold("Todos"))} ${theme.fg("muted", "—")} ${theme.fg(activeColor, `${active} active`)}`;
207971
+ return lineComponent((width) => [truncateToWidth2(line, width, "…")]);
207972
+ }
207973
+ function renderTodowriteResult(result, theme) {
207974
+ const detailsTodos = result.details?.todos;
207975
+ let todos = parseTodos(detailsTodos);
207976
+ if (todos === null && Array.isArray(result.content)) {
207977
+ const text = result.content.find((part) => part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string")?.text;
207978
+ if (text) {
207979
+ try {
207980
+ todos = parseTodos(JSON.parse(text));
207981
+ } catch {}
207982
+ }
207983
+ }
207984
+ const renderedTodos = todos ?? [];
207985
+ return lineComponent((width) => {
207986
+ if (renderedTodos.length === 0)
207987
+ return [theme.fg("dim", "No todos")];
207988
+ return renderedTodos.map((todo) => truncateToWidth2(formatTodoLine(todo, theme, { showId: true }), width, "…"));
207989
+ });
207990
+ }
207991
+ function registerTodosCommand(pi) {
207992
+ pi.registerCommand(TODOS_COMMAND_NAME, {
207993
+ description: "Show the current Magic Context todo list",
207994
+ handler: async (_args, ctx) => {
207995
+ if (!ctx.hasUI) {
207996
+ ctx.ui.notify("The /todos command requires interactive UI.", "error");
207997
+ return;
207998
+ }
207999
+ const sessionId = getSessionId(ctx);
208000
+ const todos = getTodoSnapshot(sessionId).todos;
208001
+ if (todos.length === 0) {
208002
+ ctx.ui.notify("No todos yet.", "info");
208003
+ return;
208004
+ }
208005
+ const counts = countTodos(todos);
208006
+ const lines = [formatCounts(counts)];
208007
+ const groups = [
208008
+ ["pending", "── Pending ──"],
208009
+ ["in_progress", "── In Progress ──"],
208010
+ ["completed", "── Completed ──"],
208011
+ ["cancelled", "── Cancelled ──"]
208012
+ ];
208013
+ for (const [status, heading] of groups) {
208014
+ const group = todos.filter((todo) => todo.status === status);
208015
+ if (group.length === 0)
208016
+ continue;
208017
+ lines.push(heading);
208018
+ for (const todo of group)
208019
+ lines.push(formatCommandLine(todo));
208020
+ }
208021
+ ctx.ui.notify(lines.join(`
208022
+ `), "info");
208023
+ }
208024
+ });
208025
+ }
208026
+ function todoKey(todo, index) {
208027
+ return todo.id ? `id:${todo.id}` : `pos:${index}:${todo.content}`;
208028
+ }
208029
+ function isOverlayLive(todo) {
208030
+ return todo.status === "pending" || todo.status === "in_progress";
208031
+ }
208032
+
208033
+ class TodoOverlay {
208034
+ uiCtx;
208035
+ sessionId;
208036
+ widgetRegistered = false;
208037
+ tui;
208038
+ completedTaskIdsPendingHide = new Set;
208039
+ hiddenCompletedTaskIds = new Set;
208040
+ lastTodoKeys = new Set;
208041
+ setUICtx(sessionId, ctx) {
208042
+ if (ctx !== this.uiCtx || sessionId !== this.sessionId) {
208043
+ this.uiCtx = ctx;
208044
+ this.sessionId = sessionId;
208045
+ this.widgetRegistered = false;
208046
+ this.tui = undefined;
208047
+ this.resetCompletedDisplayState();
208048
+ }
208049
+ }
208050
+ update(sessionId = this.sessionId) {
208051
+ if (!this.uiCtx || !this.sessionId || sessionId !== this.sessionId)
208052
+ return;
208053
+ const todos = getTodoSnapshot(this.sessionId).todos;
208054
+ this.pruneCompletedDisplayState(todos);
208055
+ const visible = this.selectOverlayTodos(todos);
208056
+ if (visible.length === 0) {
208057
+ if (this.widgetRegistered) {
208058
+ this.uiCtx.setWidget(WIDGET_KEY, undefined);
208059
+ this.widgetRegistered = false;
208060
+ this.tui = undefined;
208061
+ }
208062
+ return;
208063
+ }
208064
+ if (!this.widgetRegistered) {
208065
+ this.uiCtx.setWidget(WIDGET_KEY, (tui, theme) => {
208066
+ this.tui = tui;
208067
+ return {
208068
+ render: (width) => this.renderWidget(theme, width),
208069
+ invalidate: () => {
208070
+ this.widgetRegistered = false;
208071
+ this.tui = undefined;
208072
+ }
208073
+ };
208074
+ }, { placement: "aboveEditor" });
208075
+ this.widgetRegistered = true;
208076
+ } else {
208077
+ this.tui?.requestRender();
208078
+ }
208079
+ }
208080
+ resetCompletedDisplayState() {
208081
+ this.completedTaskIdsPendingHide.clear();
208082
+ this.hiddenCompletedTaskIds.clear();
208083
+ this.lastTodoKeys.clear();
208084
+ }
208085
+ hideCompletedTasksFromPreviousTurn() {
208086
+ if (this.completedTaskIdsPendingHide.size === 0)
208087
+ return;
208088
+ for (const taskId of this.completedTaskIdsPendingHide) {
208089
+ this.hiddenCompletedTaskIds.add(taskId);
208090
+ }
208091
+ this.completedTaskIdsPendingHide.clear();
208092
+ this.update();
208093
+ }
208094
+ disposeSession(sessionId) {
208095
+ if (this.sessionId !== sessionId)
208096
+ return;
208097
+ this.dispose();
208098
+ }
208099
+ dispose() {
208100
+ if (this.uiCtx)
208101
+ this.uiCtx.setWidget(WIDGET_KEY, undefined);
208102
+ this.widgetRegistered = false;
208103
+ this.tui = undefined;
208104
+ this.uiCtx = undefined;
208105
+ this.sessionId = undefined;
208106
+ this.resetCompletedDisplayState();
208107
+ }
208108
+ pruneCompletedDisplayState(todos) {
208109
+ const currentKeys = new Set(todos.map((todo, index) => todoKey(todo, index)));
208110
+ const hasSharedKeys = [...currentKeys].some((key) => this.lastTodoKeys.has(key));
208111
+ if (this.lastTodoKeys.size > 0 && currentKeys.size > 0 && !hasSharedKeys) {
208112
+ this.resetCompletedDisplayState();
208113
+ }
208114
+ this.lastTodoKeys = currentKeys;
208115
+ const completedKeys = new Set(todos.map((todo, index) => ({ todo, key: todoKey(todo, index) })).filter(({ todo }) => todo.status === "completed").map(({ key }) => key));
208116
+ for (const taskId of this.completedTaskIdsPendingHide) {
208117
+ if (!completedKeys.has(taskId))
208118
+ this.completedTaskIdsPendingHide.delete(taskId);
208119
+ }
208120
+ for (const taskId of this.hiddenCompletedTaskIds) {
208121
+ if (!completedKeys.has(taskId))
208122
+ this.hiddenCompletedTaskIds.delete(taskId);
208123
+ }
208124
+ }
208125
+ selectOverlayTodos(todos) {
208126
+ return todos.map((todo, index) => ({ todo, key: todoKey(todo, index) })).filter(({ todo, key }) => {
208127
+ if (isOverlayLive(todo))
208128
+ return true;
208129
+ return todo.status === "completed" && !this.hiddenCompletedTaskIds.has(key);
208130
+ });
208131
+ }
208132
+ renderWidget(theme, width) {
208133
+ if (!this.sessionId)
208134
+ return [];
208135
+ const todos = getTodoSnapshot(this.sessionId).todos;
208136
+ this.pruneCompletedDisplayState(todos);
208137
+ const overlayTodos = this.selectOverlayTodos(todos);
208138
+ if (overlayTodos.length === 0)
208139
+ return [];
208140
+ const counts = countTodos(todos);
208141
+ const hasInProgress = counts.inProgress > 0;
208142
+ const headingColor = hasInProgress ? "accent" : "dim";
208143
+ const headingIcon = hasInProgress ? "●" : "○";
208144
+ const heading = `${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, "Todos")} ${theme.fg("muted", "—")} ${theme.fg("dim", formatCounts(counts))}`;
208145
+ const truncate3 = (line) => truncateToWidth2(line, width, "…");
208146
+ const lines = [truncate3(heading)];
208147
+ const hasTruncatedTail = overlayTodos.length > MAX_OVERLAY_CONTENT_ROWS;
208148
+ const visibleRows = hasTruncatedTail ? MAX_OVERLAY_CONTENT_ROWS - 1 : MAX_OVERLAY_CONTENT_ROWS;
208149
+ const truncatedTail = Math.max(0, overlayTodos.length - visibleRows);
208150
+ const visible = overlayTodos.slice(0, visibleRows);
208151
+ for (const [index, { todo, key }] of visible.entries()) {
208152
+ const isLast = index === visible.length - 1 && truncatedTail === 0;
208153
+ const branch = theme.fg("dim", isLast ? "└─" : "├─");
208154
+ lines.push(truncate3(`${branch} ${formatTodoLine(todo, theme, { showId: true })}`));
208155
+ if (todo.status === "completed")
208156
+ this.completedTaskIdsPendingHide.add(key);
208157
+ }
208158
+ if (truncatedTail > 0) {
208159
+ lines.push(truncate3(`${theme.fg("dim", "└─")} ${theme.fg("dim", `+${truncatedTail} more`)}`));
208160
+ }
208161
+ return lines;
208162
+ }
208163
+ }
208164
+ function registerTodoOverlay(pi, deps) {
208165
+ const overlay = new TodoOverlay;
208166
+ pi.on("session_start", async (_event, ctx) => {
208167
+ const sessionId = getSessionId(ctx);
208168
+ if (!sessionId)
208169
+ return;
208170
+ seedTodoSnapshotFromStateJson(sessionId, deps.readLastTodoState(sessionId));
208171
+ if (!ctx.hasUI)
208172
+ return;
208173
+ overlay.setUICtx(sessionId, ctx.ui);
208174
+ overlay.update(sessionId);
208175
+ });
208176
+ pi.on("agent_start", async () => {
208177
+ overlay.hideCompletedTasksFromPreviousTurn();
208178
+ });
208179
+ pi.on("session_shutdown", async (_event, ctx) => {
208180
+ const sessionId = getSessionId(ctx);
208181
+ if (!sessionId)
208182
+ return;
208183
+ clearTodoSnapshot(sessionId);
208184
+ overlay.disposeSession(sessionId);
208185
+ });
208186
+ pi.on("session_before_switch", async (_event, ctx) => {
208187
+ const sessionId = getSessionId(ctx);
208188
+ if (!sessionId)
208189
+ return;
208190
+ clearTodoSnapshot(sessionId);
208191
+ overlay.disposeSession(sessionId);
208192
+ });
208193
+ return overlay;
208194
+ }
208195
+ function registerTodoStateLifecycle(pi, deps) {
208196
+ pi.on("session_start", async (_event, ctx) => {
208197
+ const sessionId = getSessionId(ctx);
208198
+ if (!sessionId)
208199
+ return;
208200
+ seedTodoSnapshotFromStateJson(sessionId, deps.readLastTodoState(sessionId));
208201
+ });
208202
+ pi.on("session_shutdown", async (_event, ctx) => {
208203
+ const sessionId = getSessionId(ctx);
208204
+ if (sessionId)
208205
+ clearTodoSnapshot(sessionId);
208206
+ });
208207
+ pi.on("session_before_switch", async (_event, ctx) => {
208208
+ const sessionId = getSessionId(ctx);
208209
+ if (sessionId)
208210
+ clearTodoSnapshot(sessionId);
208211
+ });
208212
+ }
208213
+
207492
208214
  // src/tools/todowrite.ts
207493
208215
  var STATUS_VALUES = [
207494
208216
  "pending",
@@ -207508,29 +208230,20 @@ var TodowriteParams = exports_typebox.Object({
207508
208230
  description: "Replace the current task list with this complete set of todos. Include every task you intend to track this turn — pending, in_progress, completed, or cancelled — because the list overwrites previous state."
207509
208231
  })
207510
208232
  });
207511
- var TOOL_DESCRIPTION = [
207512
- "Manage your task list for this session.",
207513
- "",
207514
- "Use this tool to plan multi-step work, track in-flight tasks, and",
207515
- "mark progress as you complete steps. Pass the COMPLETE updated list",
207516
- "of todos every time — this tool replaces the prior list rather than",
207517
- "appending to it.",
207518
- "",
207519
- "Task states:",
207520
- " - pending: not started yet",
207521
- " - in_progress: currently working on (limit to ONE task at a time)",
207522
- " - completed: finished successfully",
207523
- " - cancelled: no longer needed",
207524
- "",
207525
- "Use this tool proactively for non-trivial work spanning 3+ steps.",
207526
- "Skip it for single-shot answers or trivial 1-2 step tasks."
207527
- ].join(`
207528
- `);
208233
+ var PROMPT_SNIPPET = "Manage a task list to track multi-step progress";
208234
+ var PROMPT_GUIDELINES = [
208235
+ "Use `todowrite` for non-trivial work spanning 3+ steps, when the user gives you multiple tasks, or when you need to track progress across a verify/fix loop. Skip it for single-shot answers or trivial one-step work.",
208236
+ "Pass the COMPLETE updated todo list every time. This tool replaces the prior list rather than appending to it, so include pending, in_progress, completed, and cancelled tasks that should remain visible.",
208237
+ "When starting a task, mark exactly one todo `in_progress` before doing the work. Mark items `completed` immediately when done; use `cancelled` only for work that is no longer needed.",
208238
+ "Never mark a todo completed if verification is failing, implementation is partial, or an unresolved blocker remains. Keep it `in_progress` and add or update a todo for the blocker instead."
208239
+ ];
207529
208240
  function createTodowriteTool() {
207530
208241
  return {
207531
- name: "todowrite",
208242
+ name: TODO_TOOL_NAME,
207532
208243
  label: "Todos",
207533
- description: TOOL_DESCRIPTION,
208244
+ description: "Manage the session task list.",
208245
+ promptSnippet: PROMPT_SNIPPET,
208246
+ promptGuidelines: PROMPT_GUIDELINES,
207534
208247
  parameters: TodowriteParams,
207535
208248
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
207536
208249
  const todos = params.todos ?? [];
@@ -207549,6 +208262,12 @@ function createTodowriteTool() {
207549
208262
  truncated: false
207550
208263
  }
207551
208264
  };
208265
+ },
208266
+ renderCall(args, theme) {
208267
+ return renderTodowriteCall(args, theme);
208268
+ },
208269
+ renderResult(result, _opts, theme) {
208270
+ return renderTodowriteResult(result, theme);
207552
208271
  }
207553
208272
  };
207554
208273
  }
@@ -207574,15 +208293,22 @@ function registerMagicContextTools(pi, opts) {
207574
208293
  if (!opts.sessionScopedToolsDisabled) {
207575
208294
  pi.registerTool(createCtxNoteTool({
207576
208295
  db: opts.db,
207577
- dreamerEnabled: opts.dreamerEnabled ?? false
208296
+ dreamerEnabled: opts.dreamerEnabled ?? false,
208297
+ resolveDreamerEnabled: opts.resolveDreamerEnabled
207578
208298
  }));
207579
208299
  pi.registerTool(createCtxExpandTool({ db: opts.db }));
207580
208300
  }
207581
- pi.registerTool(createTodowriteTool());
208301
+ if (opts.todowriteEnabled !== false) {
208302
+ pi.registerTool(createTodowriteTool());
208303
+ if (opts.todowriteCommandEnabled !== false) {
208304
+ registerTodosCommand(pi);
208305
+ }
208306
+ }
207582
208307
  if (!opts.sessionScopedToolsDisabled) {
207583
208308
  pi.registerTool(createCtxReduceTool({
207584
208309
  db: opts.db,
207585
- protectedTags: opts.protectedTags ?? 20
208310
+ protectedTags: opts.protectedTags ?? 20,
208311
+ resolveProtectedTags: opts.resolveProtectedTags
207586
208312
  }));
207587
208313
  }
207588
208314
  }
@@ -207594,6 +208320,10 @@ function resolveCurrentProject(ctx) {
207594
208320
  const projectIdentity = resolveProjectIdentityOrFallback(projectDir);
207595
208321
  return { projectDir, projectIdentity };
207596
208322
  }
208323
+ function signalPiDeferredCompactionMarkerDrain(sessionId) {
208324
+ signalPiDeferredHistoryRefresh(sessionId);
208325
+ signalPiDeferredMaterialization(sessionId);
208326
+ }
207597
208327
  function persistPiMessageEndModelMeta(args) {
207598
208328
  if (!args.message || typeof args.message !== "object")
207599
208329
  return;
@@ -207798,8 +208528,7 @@ async function src_default2(pi) {
207798
208528
  try {
207799
208529
  const pendingPiMarkerSessions = getSessionsWithPendingPiMarker(db);
207800
208530
  for (const sid of pendingPiMarkerSessions) {
207801
- signalPiDeferredHistoryRefresh(sid);
207802
- signalPiDeferredMaterialization(sid);
208531
+ signalPiDeferredCompactionMarkerDrain(sid);
207803
208532
  }
207804
208533
  if (pendingPiMarkerSessions.length > 0) {
207805
208534
  log(`${PREFIX} rehydrated ${pendingPiMarkerSessions.length} Pi deferred compaction marker session(s)`);
@@ -207832,26 +208561,7 @@ async function src_default2(pi) {
207832
208561
  }
207833
208562
  await ensureProjectRegisteredFromPiDirectory(projectDir, db);
207834
208563
  info(`registered embedding config for project ${projectIdentity}`);
207835
- registerMagicContextTools(pi, {
207836
- db,
207837
- ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
207838
- allowDreamerActions: false,
207839
- memoryToolEnabled: true,
207840
- protectedTags: config2.protected_tags ?? 20,
207841
- dreamerEnabled: isDreamerRunnable(config2)
207842
- });
207843
- info("registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, ctx_reduce");
207844
- const historianConfig = resolveHistorianFromConfig(config2);
207845
- if (historianConfig) {
207846
- historianConfig.onStatusChange = (ctx) => {
207847
- updateStatusLine(ctx, {
207848
- db,
207849
- projectIdentity: resolveCurrentProject(ctx).projectIdentity
207850
- });
207851
- };
207852
- }
207853
- const autoSearchConfig = resolveAutoSearchFromConfig(config2);
207854
- const contextOptionsByDir = new Map;
208564
+ const projectDepsByDir = new Map;
207855
208565
  const buildContextOptions = (cfg, hist, auto) => ({
207856
208566
  db: database,
207857
208567
  smartDrops: cfg.smart_drops === true,
@@ -207892,46 +208602,110 @@ async function src_default2(pi) {
207892
208602
  });
207893
208603
  }
207894
208604
  });
207895
- function resolveContextOptionsForProject(dir) {
207896
- const cached2 = contextOptionsByDir.get(dir);
207897
- if (cached2)
207898
- return cached2;
207899
- ensureConfigLocationsMigrated(dir);
207900
- const switchedConfig = loadPiConfig({ cwd: dir }).config;
207901
- const switchedHistorian = resolveHistorianFromConfig(switchedConfig);
207902
- if (switchedHistorian) {
207903
- switchedHistorian.onStatusChange = (ctx) => {
208605
+ function buildProjectDeps(dir, identity, cfg) {
208606
+ const hist = resolveHistorianFromConfig(cfg);
208607
+ if (hist) {
208608
+ hist.onStatusChange = (ctx) => {
207904
208609
  updateStatusLine(ctx, {
207905
208610
  db: database,
207906
208611
  projectIdentity: resolveCurrentProject(ctx).projectIdentity
207907
208612
  });
207908
208613
  };
207909
208614
  }
207910
- const built = buildContextOptions(switchedConfig, switchedHistorian, resolveAutoSearchFromConfig(switchedConfig));
207911
- contextOptionsByDir.set(dir, built);
208615
+ const auto = resolveAutoSearchFromConfig(cfg);
208616
+ return {
208617
+ projectDir: dir,
208618
+ projectIdentity: identity,
208619
+ config: cfg,
208620
+ historianConfig: hist,
208621
+ autoSearchConfig: auto,
208622
+ contextOptions: buildContextOptions(cfg, hist, auto),
208623
+ sidekickConfig: resolveSidekickFromConfig(cfg),
208624
+ dreamerConfig: resolveDreamerFromConfig(cfg),
208625
+ dreamerEnabled: isDreamerRunnable(cfg)
208626
+ };
208627
+ }
208628
+ function resolveProjectDepsForDir(dir, identityOverride) {
208629
+ const cached2 = projectDepsByDir.get(dir);
208630
+ if (cached2)
208631
+ return cached2;
208632
+ ensureConfigLocationsMigrated(dir);
208633
+ const switchedConfig = loadPiConfig({ cwd: dir }).config;
208634
+ const switchedIdentity = identityOverride ?? resolveProjectIdentityOrFallback(dir);
208635
+ const built = buildProjectDeps(dir, switchedIdentity, switchedConfig);
208636
+ projectDepsByDir.set(dir, built);
207912
208637
  return built;
207913
208638
  }
207914
- const bootContextOptions = buildContextOptions(config2, historianConfig, autoSearchConfig);
207915
- contextOptionsByDir.set(projectDir, bootContextOptions);
207916
- registerPiContextHandler(pi, bootContextOptions);
207917
- info(historianConfig ? `registered historian trigger (model=${historianConfig.model}, executeThreshold=${formatExecuteThresholdForLog(historianConfig.executeThresholdPercentage)})` : "registered historian trigger: DISABLED (set historian.model in magic-context.jsonc)");
207918
- info(autoSearchConfig.enabled ? `registered auto-search hint (threshold=${autoSearchConfig.scoreThreshold}, minChars=${autoSearchConfig.minPromptChars})` : "registered auto-search hint: DISABLED (memory.auto_search.enabled=false)");
207919
- const sidekickConfig = resolveSidekickFromConfig(config2);
207920
- registerCtxAugCommand(pi, sidekickConfig);
207921
- info(sidekickConfig ? `registered /ctx-aug (sidekick model=${sidekickConfig.model})` : "registered /ctx-aug (sidekick disabled — set sidekick.disable=false and sidekick.model in config)");
208639
+ function resolveCurrentProjectDeps(ctx) {
208640
+ const currentProject = resolveCurrentProject(ctx);
208641
+ return resolveProjectDepsForDir(currentProject.projectDir, currentProject.projectIdentity);
208642
+ }
208643
+ function resolveContextOptionsForProject(dir) {
208644
+ return resolveProjectDepsForDir(dir).contextOptions;
208645
+ }
208646
+ const bootProjectDeps = buildProjectDeps(projectDir, projectIdentity, config2);
208647
+ projectDepsByDir.set(projectDir, bootProjectDeps);
208648
+ const todowriteEnabled = bootProjectDeps.config.todowrite.enabled !== false;
208649
+ const todowriteOverlayEnabled = todowriteEnabled && bootProjectDeps.config.todowrite.overlay !== false;
208650
+ registerMagicContextTools(pi, {
208651
+ db,
208652
+ ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
208653
+ allowDreamerActions: false,
208654
+ memoryToolEnabled: true,
208655
+ protectedTags: config2.protected_tags ?? 20,
208656
+ resolveProtectedTags: (ctx) => resolveCurrentProjectDeps(ctx).config.protected_tags ?? 20,
208657
+ dreamerEnabled: isDreamerRunnable(config2),
208658
+ resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
208659
+ todowriteEnabled
208660
+ });
208661
+ info(todowriteEnabled ? "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, todowrite, ctx_reduce; registered /todos" : "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, ctx_reduce (todowrite disabled)");
208662
+ const readLastTodoState = (sessionId) => getOrCreateSessionMeta(db, sessionId).lastTodoState;
208663
+ if (todowriteEnabled) {
208664
+ registerTodoStateLifecycle(pi, { readLastTodoState });
208665
+ }
208666
+ const todoOverlay = todowriteOverlayEnabled ? registerTodoOverlay(pi, {
208667
+ readLastTodoState
208668
+ }) : undefined;
208669
+ info(todowriteOverlayEnabled ? "registered todowrite overlay" : "registered todowrite overlay: DISABLED (todowrite.enabled=false or todowrite.overlay=false)");
208670
+ registerPiContextHandler(pi, bootProjectDeps.contextOptions);
208671
+ info(bootProjectDeps.historianConfig ? `registered historian trigger (model=${bootProjectDeps.historianConfig.model}, executeThreshold=${formatExecuteThresholdForLog(bootProjectDeps.historianConfig.executeThresholdPercentage)})` : "registered historian trigger: DISABLED (set historian.model in magic-context.jsonc)");
208672
+ info(bootProjectDeps.autoSearchConfig.enabled ? `registered auto-search hint (threshold=${bootProjectDeps.autoSearchConfig.scoreThreshold}, minChars=${bootProjectDeps.autoSearchConfig.minPromptChars})` : "registered auto-search hint: DISABLED (memory.auto_search.enabled=false)");
208673
+ registerCtxAugCommand(pi, (ctx) => resolveCurrentProjectDeps(ctx).sidekickConfig);
208674
+ info(bootProjectDeps.sidekickConfig ? `registered /ctx-aug (sidekick model=${bootProjectDeps.sidekickConfig.model})` : "registered /ctx-aug (sidekick disabled — set sidekick.disable=false and sidekick.model in config)");
208675
+ const recompRunner = new PiSubagentRunner;
208676
+ const wrapupRunner = new PiSubagentRunner;
208677
+ const upgradeRunner = new PiSubagentRunner;
207922
208678
  registerCtxStatusCommand(pi, {
207923
208679
  db,
207924
208680
  projectIdentity,
207925
208681
  resolveProject: resolveCurrentProject,
207926
- protectedTags: config2.protected_tags,
207927
- executeThresholdPercentage: config2.execute_threshold_percentage,
207928
- historyBudgetPercentage: config2.history_budget_percentage,
207929
- injectionBudgetTokens: config2.memory?.injection_budget_tokens,
207930
- commitClusterTrigger: config2.commit_cluster_trigger,
207931
- executeThresholdTokens: config2.execute_threshold_tokens,
208682
+ protectedTags: bootProjectDeps.config.protected_tags,
208683
+ executeThresholdPercentage: bootProjectDeps.config.execute_threshold_percentage,
208684
+ historyBudgetPercentage: bootProjectDeps.config.history_budget_percentage,
208685
+ injectionBudgetTokens: bootProjectDeps.config.memory?.injection_budget_tokens,
208686
+ commitClusterTrigger: bootProjectDeps.config.commit_cluster_trigger,
208687
+ executeThresholdTokens: bootProjectDeps.config.execute_threshold_tokens,
207932
208688
  dreamer: {
207933
- runnable: isDreamerRunnable(config2),
207934
- scheduleSummary: summarizeDreamSchedule(config2.dreamer)
208689
+ runnable: bootProjectDeps.dreamerEnabled,
208690
+ scheduleSummary: summarizeDreamSchedule(bootProjectDeps.config.dreamer)
208691
+ },
208692
+ resolveStatusDeps: (ctx) => {
208693
+ const current = resolveCurrentProjectDeps(ctx);
208694
+ return {
208695
+ db,
208696
+ projectIdentity: current.projectIdentity,
208697
+ resolveProject: resolveCurrentProject,
208698
+ protectedTags: current.config.protected_tags,
208699
+ executeThresholdPercentage: current.config.execute_threshold_percentage,
208700
+ historyBudgetPercentage: current.config.history_budget_percentage,
208701
+ injectionBudgetTokens: current.config.memory?.injection_budget_tokens,
208702
+ commitClusterTrigger: current.config.commit_cluster_trigger,
208703
+ executeThresholdTokens: current.config.execute_threshold_tokens,
208704
+ dreamer: {
208705
+ runnable: current.dreamerEnabled,
208706
+ scheduleSummary: summarizeDreamSchedule(current.config.dreamer)
208707
+ }
208708
+ };
207935
208709
  }
207936
208710
  });
207937
208711
  info("registered /ctx-status");
@@ -207941,45 +208715,94 @@ async function src_default2(pi) {
207941
208715
  info("registered /ctx-flush");
207942
208716
  registerCtxRecompCommand(pi, {
207943
208717
  db,
207944
- runner: new PiSubagentRunner,
207945
- historianModel: historianConfig?.model,
207946
- historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(historianConfig?.model)),
207947
- historianFallbacks: historianConfig?.fallbackModels,
207948
- historianTimeoutMs: config2.historian_timeout_ms,
207949
- historianThinkingLevel: historianConfig?.thinkingLevel,
207950
- language: config2.language,
207951
- memoryEnabled: config2.memory.enabled,
207952
- autoPromote: config2.memory.auto_promote
208718
+ runner: recompRunner,
208719
+ historianModel: bootProjectDeps.historianConfig?.model,
208720
+ historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(bootProjectDeps.historianConfig?.model)),
208721
+ historianFallbacks: bootProjectDeps.historianConfig?.fallbackModels,
208722
+ historianTimeoutMs: bootProjectDeps.config.historian_timeout_ms,
208723
+ historianThinkingLevel: bootProjectDeps.historianConfig?.thinkingLevel,
208724
+ language: bootProjectDeps.config.language,
208725
+ memoryEnabled: bootProjectDeps.config.memory.enabled,
208726
+ autoPromote: bootProjectDeps.config.memory.auto_promote,
208727
+ resolveRuntimeDeps: (ctx) => {
208728
+ const current = resolveCurrentProjectDeps(ctx);
208729
+ return {
208730
+ db,
208731
+ runner: recompRunner,
208732
+ historianModel: current.historianConfig?.model,
208733
+ historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(current.historianConfig?.model)),
208734
+ historianFallbacks: current.historianConfig?.fallbackModels,
208735
+ historianTimeoutMs: current.config.historian_timeout_ms,
208736
+ historianThinkingLevel: current.historianConfig?.thinkingLevel,
208737
+ language: current.config.language,
208738
+ memoryEnabled: current.config.memory.enabled,
208739
+ autoPromote: current.config.memory.auto_promote
208740
+ };
208741
+ }
207953
208742
  });
207954
208743
  info("registered /ctx-recomp");
207955
208744
  registerCtxWrapupCommand(pi, {
207956
208745
  db,
207957
- runner: new PiSubagentRunner,
207958
- historianModel: historianConfig?.model,
207959
- historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(historianConfig?.model)),
207960
- historianFallbacks: historianConfig?.fallbackModels,
207961
- historianTimeoutMs: config2.historian_timeout_ms,
207962
- historianThinkingLevel: historianConfig?.thinkingLevel,
207963
- language: config2.language,
207964
- memoryEnabled: config2.memory.enabled,
207965
- autoPromote: config2.memory.auto_promote,
207966
- userMemoriesEnabled: userMemoryCollectionEnabled(config2.dreamer),
207967
- executeThresholdPercentage: config2.execute_threshold_percentage,
207968
- executeThresholdTokens: config2.execute_threshold_tokens
208746
+ runner: wrapupRunner,
208747
+ historianModel: bootProjectDeps.historianConfig?.model,
208748
+ historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(bootProjectDeps.historianConfig?.model)),
208749
+ historianFallbacks: bootProjectDeps.historianConfig?.fallbackModels,
208750
+ historianTimeoutMs: bootProjectDeps.config.historian_timeout_ms,
208751
+ historianThinkingLevel: bootProjectDeps.historianConfig?.thinkingLevel,
208752
+ language: bootProjectDeps.config.language,
208753
+ memoryEnabled: bootProjectDeps.config.memory.enabled,
208754
+ autoPromote: bootProjectDeps.config.memory.auto_promote,
208755
+ userMemoriesEnabled: userMemoryCollectionEnabled(bootProjectDeps.config.dreamer),
208756
+ executeThresholdPercentage: bootProjectDeps.config.execute_threshold_percentage,
208757
+ executeThresholdTokens: bootProjectDeps.config.execute_threshold_tokens,
208758
+ resolveRuntimeDeps: (ctx) => {
208759
+ const current = resolveCurrentProjectDeps(ctx);
208760
+ return {
208761
+ db,
208762
+ runner: wrapupRunner,
208763
+ historianModel: current.historianConfig?.model,
208764
+ historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(current.historianConfig?.model)),
208765
+ historianFallbacks: current.historianConfig?.fallbackModels,
208766
+ historianTimeoutMs: current.config.historian_timeout_ms,
208767
+ historianThinkingLevel: current.historianConfig?.thinkingLevel,
208768
+ language: current.config.language,
208769
+ memoryEnabled: current.config.memory.enabled,
208770
+ autoPromote: current.config.memory.auto_promote,
208771
+ userMemoriesEnabled: userMemoryCollectionEnabled(current.config.dreamer),
208772
+ executeThresholdPercentage: current.config.execute_threshold_percentage,
208773
+ executeThresholdTokens: current.config.execute_threshold_tokens
208774
+ };
208775
+ }
207969
208776
  });
207970
208777
  info("registered /ctx-wrapup");
207971
208778
  registerCtxSessionUpgradeCommand(pi, {
207972
208779
  db,
207973
- runner: new PiSubagentRunner,
207974
- historianModel: historianConfig?.model,
207975
- historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(historianConfig?.model)),
207976
- historianFallbacks: historianConfig?.fallbackModels,
207977
- historianTimeoutMs: config2.historian_timeout_ms,
207978
- historianThinkingLevel: historianConfig?.thinkingLevel,
207979
- language: config2.language,
207980
- memoryEnabled: config2.memory.enabled,
207981
- autoPromote: config2.memory.auto_promote,
207982
- userMemoriesEnabled: userMemoryCollectionEnabled(config2.dreamer)
208780
+ runner: upgradeRunner,
208781
+ historianModel: bootProjectDeps.historianConfig?.model,
208782
+ historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(bootProjectDeps.historianConfig?.model)),
208783
+ historianFallbacks: bootProjectDeps.historianConfig?.fallbackModels,
208784
+ historianTimeoutMs: bootProjectDeps.config.historian_timeout_ms,
208785
+ historianThinkingLevel: bootProjectDeps.historianConfig?.thinkingLevel,
208786
+ language: bootProjectDeps.config.language,
208787
+ memoryEnabled: bootProjectDeps.config.memory.enabled,
208788
+ autoPromote: bootProjectDeps.config.memory.auto_promote,
208789
+ userMemoriesEnabled: userMemoryCollectionEnabled(bootProjectDeps.config.dreamer),
208790
+ resolveRuntimeDeps: (ctx) => {
208791
+ const current = resolveCurrentProjectDeps(ctx);
208792
+ return {
208793
+ db,
208794
+ runner: upgradeRunner,
208795
+ historianModel: current.historianConfig?.model,
208796
+ historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(current.historianConfig?.model)),
208797
+ historianFallbacks: current.historianConfig?.fallbackModels,
208798
+ historianTimeoutMs: current.config.historian_timeout_ms,
208799
+ historianThinkingLevel: current.historianConfig?.thinkingLevel,
208800
+ language: current.config.language,
208801
+ memoryEnabled: current.config.memory.enabled,
208802
+ autoPromote: current.config.memory.auto_promote,
208803
+ userMemoriesEnabled: userMemoryCollectionEnabled(current.config.dreamer)
208804
+ };
208805
+ }
207983
208806
  });
207984
208807
  info("registered /ctx-session-upgrade");
207985
208808
  registerCtxDreamCommand(pi, {
@@ -207987,7 +208810,8 @@ async function src_default2(pi) {
207987
208810
  projectDir,
207988
208811
  projectIdentity,
207989
208812
  resolveProject: resolveCurrentProject,
207990
- dreamerEnabled: isDreamerRunnable(config2),
208813
+ dreamerEnabled: bootProjectDeps.dreamerEnabled,
208814
+ resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
207991
208815
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
207992
208816
  });
207993
208817
  info("registered /ctx-dream");
@@ -207995,26 +208819,27 @@ async function src_default2(pi) {
207995
208819
  db,
207996
208820
  projectDir,
207997
208821
  projectIdentity,
207998
- memoryEnabled: config2.memory.enabled,
208822
+ memoryEnabled: bootProjectDeps.config.memory.enabled,
208823
+ resolveMemoryEnabled: (ctx) => resolveCurrentProjectDeps(ctx).config.memory.enabled,
207999
208824
  resolveProject: resolveCurrentProject
208000
208825
  });
208001
208826
  info("registered /ctx-embed");
208002
- const dreamerConfig = resolveDreamerFromConfig(config2);
208827
+ const dreamerConfig = bootProjectDeps.dreamerConfig;
208003
208828
  if (dreamerConfig) {
208004
208829
  registerPiDreamerProject({
208005
208830
  db,
208006
208831
  projectDir,
208007
208832
  projectIdentity,
208008
208833
  config: dreamerConfig,
208009
- embeddingConfig: config2.embedding,
208010
- memoryEnabled: config2.memory.enabled,
208011
- language: config2.language,
208012
- gitCommitIndexing: config2.memory.git_commit_indexing,
208834
+ embeddingConfig: bootProjectDeps.config.embedding,
208835
+ memoryEnabled: bootProjectDeps.config.memory.enabled,
208836
+ language: bootProjectDeps.config.language,
208837
+ gitCommitIndexing: bootProjectDeps.config.memory.git_commit_indexing,
208013
208838
  onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject
208014
208839
  });
208015
208840
  info(`registered dreamer (${summarizeDreamSchedule(dreamerConfig)})`);
208016
208841
  } else {
208017
- info(isDreamerRunnable(config2) ? "registered dreamer: DISABLED (no dreamer config)" : "registered dreamer: DISABLED (dreamer.disable=true or no dreamer config)");
208842
+ info(bootProjectDeps.dreamerEnabled ? "registered dreamer: DISABLED (no dreamer config)" : "registered dreamer: DISABLED (dreamer.disable=true or no dreamer config)");
208018
208843
  }
208019
208844
  pi.on("before_agent_start", async (event, ctx) => {
208020
208845
  try {
@@ -208035,13 +208860,14 @@ async function src_default2(pi) {
208035
208860
  }
208036
208861
  } catch {}
208037
208862
  try {
208038
- const currentProject = resolveCurrentProject(ctx);
208863
+ const effectiveProjectDeps = resolveCurrentProjectDeps(ctx);
208864
+ const currentProject = {
208865
+ projectDir: effectiveProjectDeps.projectDir,
208866
+ projectIdentity: effectiveProjectDeps.projectIdentity
208867
+ };
208868
+ const effectiveConfig = effectiveProjectDeps.config;
208039
208869
  seenDreamerProjectIdentities.add(currentProject.projectIdentity);
208040
- const switchedProject = currentProject.projectDir !== projectDir;
208041
- if (switchedProject)
208042
- ensureConfigLocationsMigrated(currentProject.projectDir);
208043
- const effectiveConfig = switchedProject ? loadPiConfig({ cwd: currentProject.projectDir }).config : config2;
208044
- const effectiveDreamerConfig = switchedProject ? resolveDreamerFromConfig(effectiveConfig) : dreamerConfig;
208870
+ const effectiveDreamerConfig = effectiveProjectDeps.dreamerConfig;
208045
208871
  if (effectiveDreamerConfig) {
208046
208872
  try {
208047
208873
  registerPiDreamerProject({
@@ -208051,13 +208877,14 @@ async function src_default2(pi) {
208051
208877
  config: effectiveDreamerConfig,
208052
208878
  embeddingConfig: effectiveConfig.embedding,
208053
208879
  memoryEnabled: effectiveConfig.memory.enabled,
208880
+ language: effectiveConfig.language,
208054
208881
  gitCommitIndexing: effectiveConfig.memory.git_commit_indexing,
208055
208882
  onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject
208056
208883
  });
208057
208884
  } catch (err5) {
208058
208885
  warn("before_agent_start: registerPiDreamerProject threw:", err5);
208059
208886
  }
208060
- } else if (switchedProject) {
208887
+ } else {
208061
208888
  try {
208062
208889
  unregisterPiDreamerProject({
208063
208890
  projectIdentity: currentProject.projectIdentity
@@ -208084,11 +208911,10 @@ async function src_default2(pi) {
208084
208911
  const smForDrain = sm;
208085
208912
  const canDrain = typeof smForDrain.appendCompaction === "function" && typeof smForDrain.getBranch === "function";
208086
208913
  if (canDrain && getPendingPiCompactionMarkerState(db, sessionId)) {
208087
- signalPiDeferredHistoryRefresh(sessionId);
208088
- signalPiPendingMaterialization(sessionId);
208914
+ signalPiDeferredCompactionMarkerDrain(sessionId);
208089
208915
  }
208090
208916
  } catch {}
208091
- if (ctx.hasUI && historianConfig?.model) {
208917
+ if (ctx.hasUI && effectiveProjectDeps.historianConfig?.model) {
208092
208918
  maybeSendUpgradeReminder({
208093
208919
  client: null,
208094
208920
  db,
@@ -208109,7 +208935,6 @@ async function src_default2(pi) {
208109
208935
  return;
208110
208936
  }
208111
208937
  const isCacheBusting = sessionId ? hasSystemPromptRefresh(sessionId) : true;
208112
- const effectiveDreamerRunnable = switchedProject ? isDreamerRunnable(effectiveConfig) : isDreamerRunnable(config2);
208113
208938
  const block = buildMagicContextBlock({
208114
208939
  db,
208115
208940
  cwd: currentProject.projectDir,
@@ -208118,7 +208943,7 @@ async function src_default2(pi) {
208118
208943
  includeGuidance: true,
208119
208944
  protectedTags: effectiveConfig.protected_tags,
208120
208945
  ctxReduceCallable: true,
208121
- dreamerEnabled: effectiveDreamerRunnable,
208946
+ dreamerEnabled: effectiveProjectDeps.dreamerEnabled,
208122
208947
  temporalAwarenessEnabled: effectiveConfig.temporal_awareness ?? false,
208123
208948
  cavemanTextCompressionEnabled: effectiveConfig.caveman_text_compression?.enabled === true,
208124
208949
  language: effectiveConfig.language,
@@ -208176,6 +209001,10 @@ ${block}` : event.systemPrompt;
208176
209001
  const todoArgs = event.args;
208177
209002
  const todos = todoArgs?.todos;
208178
209003
  const sessionMeta = Array.isArray(todos) ? getOrCreateSessionMeta(db, sessionId) : null;
209004
+ if (todowriteEnabled && Array.isArray(todos)) {
209005
+ setTodoSnapshot(sessionId, todos);
209006
+ todoOverlay?.update(sessionId);
209007
+ }
208179
209008
  if (sessionMeta && !sessionMeta.isSubagent) {
208180
209009
  const normalizedTodos = normalizeTodoStateJson(todos);
208181
209010
  if (normalizedTodos !== null) {
@@ -208264,7 +209093,7 @@ ${block}` : event.systemPrompt;
208264
209093
  db,
208265
209094
  sessionId,
208266
209095
  message: event.message,
208267
- cacheTtlConfig: config2.cache_ttl
209096
+ cacheTtlConfig: resolveCurrentProjectDeps(ctx).config.cache_ttl
208268
209097
  });
208269
209098
  const piUsage = ctx.getContextUsage?.();
208270
209099
  const piContextWindow = piUsage && typeof piUsage.contextWindow === "number" && piUsage.contextWindow > 0 ? piUsage.contextWindow : ctx.model?.contextWindow ?? 0;
@@ -208306,6 +209135,10 @@ ${block}` : event.systemPrompt;
208306
209135
  const normalized = normalizeTodoStateJson(todos);
208307
209136
  if (normalized === null)
208308
209137
  continue;
209138
+ if (todowriteEnabled) {
209139
+ setTodoSnapshot(sessionId, todos);
209140
+ todoOverlay?.update(sessionId);
209141
+ }
208309
209142
  updateSessionMeta(db, sessionId, {
208310
209143
  lastTodoState: normalized
208311
209144
  });
@@ -208397,6 +209230,7 @@ function formatExecuteThresholdForLog(value) {
208397
209230
  return overrides.length > 0 ? `${base} (${overrides.join(", ")})` : base;
208398
209231
  }
208399
209232
  export {
209233
+ signalPiDeferredCompactionMarkerDrain,
208400
209234
  resolveSidekickFromConfig,
208401
209235
  resolveHistorianFromConfig,
208402
209236
  resolveDreamerFromConfig,