@cortexkit/pi-magic-context 0.38.1 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  DEFAULT_MEMORY_BUDGET_TOKENS,
14
14
  DEFAULT_USER_PROFILE_BUDGET_TOKENS,
15
15
  Database,
16
+ FAIL_CLOSED_DOCTOR_COMMAND,
16
17
  GIT_SWEEP_LEASE_RENEWAL_MS,
17
18
  GLOBAL_USER_PROFILE_PROJECT_PATH,
18
19
  LATEST_SUPPORTED_VERSION,
@@ -86,6 +87,8 @@ import {
86
87
  copySessionStateForClone,
87
88
  cosineSimilarity,
88
89
  countPrimerCandidatesForProject,
90
+ createFailClosedBlockingError,
91
+ createFailClosedController,
89
92
  createPrimer,
90
93
  createPromptSurfaceGuidanceEpochCache,
91
94
  createPromptSurfaceRuntime,
@@ -125,6 +128,7 @@ import {
125
128
  findAdoptableFallbackTags,
126
129
  findLastModelKeyFromBranch,
127
130
  findPiFallbackToolOwnerTags,
131
+ formatFailClosedBlockingMessage,
128
132
  formatWindowDerivationLine,
129
133
  getActivePrimers,
130
134
  getActiveTagsBySession,
@@ -134,6 +138,7 @@ import {
134
138
  getAllStatusTagTokenTotalsFlat,
135
139
  getAutoSearchHintDecisions,
136
140
  getCachedAbsoluteMessageCount,
141
+ getChannel1NudgeState,
137
142
  getChannel2NudgeClaim,
138
143
  getChannel2NudgeState,
139
144
  getCompactionModeRecord,
@@ -149,7 +154,6 @@ import {
149
154
  getLastCompartmentEndMessage,
150
155
  getLastCompartmentEndMessageId,
151
156
  getLastIndexedOrdinal,
152
- getLastNudgeLevel,
153
157
  getLastNudgeUndropped,
154
158
  getLatestHistorianInvocationId,
155
159
  getLegacyProtectedTailStartOrdinal,
@@ -226,6 +230,7 @@ import {
226
230
  hasMuralCueColumns,
227
231
  hasPiFallbackMessageTags,
228
232
  hasPiFallbackToolOwnerTags,
233
+ hasShareabilitySensitiveText,
229
234
  incrementHistorianFailure,
230
235
  indexCommitsForProject,
231
236
  indexMessagesAfterOrdinal,
@@ -241,6 +246,7 @@ import {
241
246
  isCompartmentLeaseHeld,
242
247
  isDreamerRunnable,
243
248
  isEditTool,
249
+ isFailClosedBlockingError,
244
250
  isMessageIndexReconciledThrough,
245
251
  isMessageIndexSourceCurrent,
246
252
  isMidTurn,
@@ -343,11 +349,11 @@ import {
343
349
  saveRecompStagingPass,
344
350
  saveSourceContent,
345
351
  scheduleAfterBootQuiet,
352
+ setChannel1NudgeState,
346
353
  setChannel2NudgeState,
347
354
  setCompactionModeRecord,
348
355
  setDeferredExecutePendingIfAbsent,
349
356
  setEmergencyDropSample,
350
- setLastNudgeLevel,
351
357
  setLastNudgeUndropped,
352
358
  setMemoryClassification,
353
359
  setMuralCue,
@@ -361,6 +367,8 @@ import {
361
367
  setSqlitePragmaConfig,
362
368
  setStoragePrivatePermissionEnforcement,
363
369
  setTodoSnapshot,
370
+ shouldBypassFailClosedBlock,
371
+ shouldUseStickyChannel1Reminder,
364
372
  sourceNameForMemory,
365
373
  stripChannel1ReminderSpans,
366
374
  stripMemoryMuralBlock,
@@ -395,7 +403,7 @@ import {
395
403
  withMigrationLanguageDirective,
396
404
  withRawMessageProvider,
397
405
  withRawSessionMessageCache
398
- } from "./index-8e0zezh7.js";
406
+ } from "./index-kamc8t8p.js";
399
407
  import {
400
408
  pushNotification
401
409
  } from "./index-hw34xmzk.js";
@@ -417,49 +425,104 @@ import {
417
425
  import { createRequire as createRequire3 } from "node:module";
418
426
  import { join as join5, resolve as resolve2 } from "node:path";
419
427
 
420
- // ../plugin/src/shared/resolve-fallbacks.ts
421
- function resolveFallbackChain(userFallbacks) {
422
- const userList = normalizeUserFallbacks(userFallbacks);
423
- return dedupe(userList.filter(isValidModelSpec));
428
+ // ../plugin/src/shared/model-resolution.ts
429
+ function asRecord(value) {
430
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
424
431
  }
425
- function normalizeUserFallbacks(userFallbacks) {
426
- if (!userFallbacks)
427
- return [];
428
- if (typeof userFallbacks === "string") {
429
- const trimmed = userFallbacks.trim();
430
- return trimmed ? [trimmed] : [];
432
+ function readString(value) {
433
+ if (typeof value !== "string")
434
+ return;
435
+ const trimmed = value.trim();
436
+ return trimmed.length > 0 ? trimmed : undefined;
437
+ }
438
+ function hasOwn(record, key) {
439
+ return Object.hasOwn(record, key);
440
+ }
441
+ function normalizeModelEntry(entry, harness) {
442
+ if (typeof entry === "string") {
443
+ const model2 = readString(entry);
444
+ return model2 ? { model: model2 } : undefined;
431
445
  }
432
- return userFallbacks.map((s) => s.trim()).filter((s) => s.length > 0);
446
+ const objectEntry = asRecord(entry);
447
+ if (!objectEntry)
448
+ return;
449
+ const model = readString(objectEntry.model);
450
+ if (!model)
451
+ return;
452
+ const qualifier = readString(objectEntry[harness === "opencode" ? "variant" : "thinking_level"]);
453
+ return qualifier ? { model, qualifier } : { model };
433
454
  }
434
- function isValidModelSpec(spec) {
435
- const slash = spec.indexOf("/");
436
- return slash > 0 && slash < spec.length - 1;
455
+ function isValidModelReference(entry) {
456
+ const slash = entry.model.indexOf("/");
457
+ return slash > 0 && slash < entry.model.length - 1;
437
458
  }
438
- function dedupe(list) {
439
- const seen = new Set;
440
- const out = [];
441
- for (const item of list) {
442
- if (seen.has(item))
459
+ function sameAttempt(a, b) {
460
+ return a.model === b.model && a.qualifier === b.qualifier;
461
+ }
462
+ function resolveFallbackEntries(value, harness) {
463
+ const values = Array.isArray(value) ? value : value === undefined ? [] : [value];
464
+ const resolved = [];
465
+ for (const value2 of values) {
466
+ const entry = normalizeModelEntry(value2, harness);
467
+ if (!entry || !isValidModelReference(entry))
443
468
  continue;
444
- seen.add(item);
445
- out.push(item);
469
+ if (resolved.some((candidate) => sameAttempt(candidate, entry)))
470
+ continue;
471
+ resolved.push(entry);
446
472
  }
447
- return out;
473
+ return resolved;
448
474
  }
449
- function parseProviderModel(spec) {
450
- const slash = spec.indexOf("/");
451
- if (slash < 1 || slash >= spec.length - 1)
452
- return null;
475
+ function resolvePrimaryEntry(args) {
476
+ const entry = normalizeModelEntry(args.entry, args.harness);
477
+ if (!entry)
478
+ return;
479
+ const qualifier = entry.qualifier ?? readString(args.defaultQualifier);
480
+ return qualifier ? { ...entry, qualifier } : entry;
481
+ }
482
+ function resolveHistorianModel(config, harness) {
483
+ const historian = asRecord(asRecord(config)?.historian);
484
+ const block = asRecord(historian?.[harness]);
485
+ if (!block)
486
+ return { fallbacks: [] };
453
487
  return {
454
- providerID: spec.slice(0, slash).trim(),
455
- modelID: spec.slice(slash + 1).trim()
488
+ primary: resolvePrimaryEntry({
489
+ entry: block.model,
490
+ defaultQualifier: block[harness === "opencode" ? "variant" : "thinking_level"],
491
+ harness
492
+ }),
493
+ fallbacks: resolveFallbackEntries(block.fallback_models, harness)
456
494
  };
457
495
  }
458
- function modelBodyField(spec) {
459
- if (!spec)
460
- return {};
461
- const parsed = parseProviderModel(spec);
462
- return parsed ? { model: parsed } : {};
496
+ function resolveDreamerTaskModel(args) {
497
+ const dreamer = asRecord(asRecord(args.config)?.dreamer);
498
+ const harnessBlock = asRecord(dreamer?.[args.harness]);
499
+ const taskBlock = asRecord(asRecord(harnessBlock?.tasks)?.[args.task]);
500
+ const schedulingTask = asRecord(asRecord(dreamer?.tasks)?.[args.task]);
501
+ if (!harnessBlock) {
502
+ return {
503
+ fallbacks: [],
504
+ schedule: readString(schedulingTask?.schedule),
505
+ timeoutMinutes: typeof taskBlock?.timeout_minutes === "number" ? taskBlock.timeout_minutes : undefined,
506
+ promotionThreshold: typeof schedulingTask?.promotion_threshold === "number" ? schedulingTask.promotion_threshold : undefined
507
+ };
508
+ }
509
+ const taskQualifier = taskBlock?.[args.harness === "opencode" ? "variant" : "thinking_level"];
510
+ const harnessQualifier = harnessBlock[args.harness === "opencode" ? "variant" : "thinking_level"] ?? normalizeModelEntry(harnessBlock.model, args.harness)?.qualifier;
511
+ const taskHasModel = taskBlock !== undefined && hasOwn(taskBlock, "model");
512
+ const usesMuralModel = !taskHasModel && args.task === "compress-cues" && Boolean(readString(args.muralModel));
513
+ const primarySource = taskHasModel ? taskBlock?.model : usesMuralModel ? args.muralModel : harnessBlock.model;
514
+ const fallbackSource = taskBlock !== undefined && hasOwn(taskBlock, "fallback_models") ? taskBlock.fallback_models : harnessBlock.fallback_models;
515
+ return {
516
+ primary: resolvePrimaryEntry({
517
+ entry: primarySource,
518
+ defaultQualifier: usesMuralModel ? harnessQualifier : taskQualifier ?? harnessQualifier,
519
+ harness: args.harness
520
+ }),
521
+ fallbacks: resolveFallbackEntries(fallbackSource, args.harness),
522
+ schedule: readString(schedulingTask?.schedule),
523
+ timeoutMinutes: typeof taskBlock?.timeout_minutes === "number" ? taskBlock.timeout_minutes : undefined,
524
+ promotionThreshold: typeof schedulingTask?.promotion_threshold === "number" ? schedulingTask.promotion_threshold : undefined
525
+ };
463
526
  }
464
527
 
465
528
  // ../plugin/src/features/magic-context/dreamer/task-registry.ts
@@ -528,25 +591,23 @@ function compareTaskOrder(a, b) {
528
591
  }
529
592
 
530
593
  // ../plugin/src/features/magic-context/dreamer/task-config.ts
531
- function buildDreamTaskRuntimeConfigs(dreamer, language) {
532
- const tasks = dreamer.tasks ?? {};
594
+ function buildDreamTaskRuntimeConfigs(dreamer, harness, language, muralModel) {
533
595
  return CANONICAL_DREAM_TASKS.map((task) => {
534
- const t = tasks[task] ?? {
535
- schedule: "",
536
- timeout_minutes: 20
537
- };
538
- const model = task === "compress-cues" ? t.model : t.model ?? dreamer.model;
539
- const fallbackModels = resolveFallbackChain(t.fallback_models ?? dreamer.fallback_models);
540
- const thinkingLevel = t.thinking_level ?? dreamer.thinking_level;
596
+ const resolved = resolveDreamerTaskModel({
597
+ config: { dreamer },
598
+ harness,
599
+ task,
600
+ muralModel
601
+ });
541
602
  return {
542
603
  task,
543
- schedule: t.schedule,
544
- model,
545
- fallbackModels,
546
- thinkingLevel,
604
+ schedule: resolved.schedule ?? "",
605
+ model: resolved.primary,
606
+ fallbackModels: resolved.fallbacks,
607
+ thinkingLevel: harness === "pi" ? resolved.primary?.qualifier : undefined,
547
608
  language,
548
- timeoutMinutes: t.timeout_minutes ?? 20,
549
- promotionThreshold: t.promotion_threshold
609
+ timeoutMinutes: resolved.timeoutMinutes ?? 20,
610
+ promotionThreshold: resolved.promotionThreshold
550
611
  };
551
612
  });
552
613
  }
@@ -566,170 +627,6 @@ function summarizeDreamSchedule(dreamer) {
566
627
  return enabled.map((t) => `${t} ${dreamer?.tasks[t]?.schedule}`).join(", ");
567
628
  }
568
629
 
569
- // ../plugin/src/features/magic-context/fail-closed-block.ts
570
- var FAIL_CLOSED_DOCTOR_COMMAND = "npx @cortexkit/magic-context@latest doctor";
571
- var FAIL_CLOSED_REPROBE_EVERY_N = 5;
572
-
573
- class FailClosedBlockingError extends Error {
574
- code = "FAIL_CLOSED_BLOCKING";
575
- reason;
576
- constructor(message, reason, options) {
577
- super(message, options);
578
- this.name = "FailClosedBlockingError";
579
- this.reason = reason;
580
- }
581
- }
582
- var OPENCODE_INTERNAL_AGENT_NAMES = new Set(["title", "summary", "compaction"]);
583
- function isMagicContextHiddenAgentName(agent) {
584
- if (agent === "sidekick" || agent === "smart-note-compiler" || agent.startsWith("smart-note-")) {
585
- return true;
586
- }
587
- if (agent === "historian" || agent.startsWith("historian-"))
588
- return true;
589
- if (agent === "dreamer" || agent.startsWith("dreamer-"))
590
- return true;
591
- return false;
592
- }
593
- var MAX_FORMATTED_BLOCKING_PROCESSES = 8;
594
- function normalizeFailClosedProcessKind(process2) {
595
- switch (process2.kind) {
596
- case "OpenCode server":
597
- case "OpenCode instance (TUI/CLI)":
598
- case "Pi":
599
- case "process":
600
- return process2.kind;
601
- }
602
- switch (process2.harness?.trim().toLowerCase()) {
603
- case "opencode server":
604
- return "OpenCode server";
605
- case "opencode instance (tui/cli)":
606
- case "opencode instance":
607
- return "OpenCode instance (TUI/CLI)";
608
- case "pi":
609
- case "pi harness":
610
- case "omp":
611
- return "Pi";
612
- default:
613
- return "process";
614
- }
615
- }
616
- function formatFailClosedBlockingProcesses(processes) {
617
- const uniqueProcesses = new Map;
618
- for (const process2 of processes) {
619
- if (!Number.isInteger(process2.pid) || process2.pid <= 0)
620
- continue;
621
- const kind = normalizeFailClosedProcessKind(process2);
622
- uniqueProcesses.set(`${kind}\x00${process2.pid}`, { kind, pid: process2.pid });
623
- }
624
- const entries = [...uniqueProcesses.values()];
625
- const visible = entries.slice(0, MAX_FORMATTED_BLOCKING_PROCESSES);
626
- const rendered = visible.map(({ kind, pid }) => `${kind} (PID ${pid})`);
627
- const omitted = entries.length - visible.length;
628
- if (omitted > 0)
629
- rendered.push(`${omitted} more blocking process(es)`);
630
- if (rendered.length === 0)
631
- return "a live process";
632
- if (rendered.length === 1)
633
- return rendered[0];
634
- const last = rendered.pop();
635
- return `${rendered.join(", ")}, and ${last}`;
636
- }
637
- function formatFailClosedBlockingMessage(reason) {
638
- if (reason.kind === "migration_guard") {
639
- if (reason.unreadableFile) {
640
- const arm = reason.unreadableArm ?? "io";
641
- const recovery = arm === "io" ? `If none of these processes are running, it is safe to delete ${reason.unreadableFile} and retry.` : `The file may be a recent incomplete write; retry after the file is older than the ten-minute grace window, or stop the relevant process before deleting it.`;
642
- return [
643
- `Magic Context cannot migrate the shared database because RPC discovery file ${reason.unreadableFile} is uncertain (${arm} arm), so the absence of a live process cannot be proven.`,
644
- recovery,
645
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
646
- ].join(" ");
647
- }
648
- return [
649
- `Magic Context cannot migrate the shared database because ${formatFailClosedBlockingProcesses(reason.blockingProcesses)} may be running an older Magic Context build that would fail against the migrated database.`,
650
- "Restart the blocking process (it will pick up the new build and migrate on start), or shut it down and retry.",
651
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
652
- ].join(" ");
653
- }
654
- if (reason.kind === "schema_fence") {
655
- return [
656
- `Magic Context cannot operate: this Magic Context build is older than the database; upgrade/restart this harness (upstream migration lane v${reason.persistedVersion}, build supports through v${reason.supportedVersion}).`,
657
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
658
- ].join(" ");
659
- }
660
- const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
661
- return [
662
- `Magic Context cannot operate: persistent storage failed (${cause}).`,
663
- "The plugin will not silently degrade to native compaction while enabled.",
664
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
665
- ].join(" ");
666
- }
667
- function createFailClosedBlockingError(reason, options) {
668
- return new FailClosedBlockingError(formatFailClosedBlockingMessage(reason), reason, options);
669
- }
670
- function isFailClosedBlockingError(error) {
671
- return error instanceof FailClosedBlockingError || typeof error === "object" && error !== null && error.name === "FailClosedBlockingError" && error.code === "FAIL_CLOSED_BLOCKING";
672
- }
673
- function shouldBypassFailClosedBlock(input) {
674
- if (input.isPiSubagentEnv === true)
675
- return true;
676
- if (input.isInternalChildSession === true)
677
- return true;
678
- const agent = typeof input.agent === "string" ? input.agent.trim() : "";
679
- if (agent.length === 0)
680
- return false;
681
- if (OPENCODE_INTERNAL_AGENT_NAMES.has(agent))
682
- return true;
683
- if (isMagicContextHiddenAgentName(agent))
684
- return true;
685
- return false;
686
- }
687
- function createFailClosedController(options) {
688
- const reprobeEveryN = Math.max(1, options?.reprobeEveryN ?? FAIL_CLOSED_REPROBE_EVERY_N);
689
- let reason = null;
690
- let blockedPassCount = 0;
691
- return {
692
- arm(next) {
693
- reason = next;
694
- blockedPassCount = 0;
695
- },
696
- clear() {
697
- reason = null;
698
- blockedPassCount = 0;
699
- },
700
- isArmed() {
701
- return reason !== null;
702
- },
703
- getReason() {
704
- return reason;
705
- },
706
- async enforce(input) {
707
- if (!reason)
708
- return;
709
- if (!input.blockingEnabled)
710
- return;
711
- if (input.exempt)
712
- return;
713
- blockedPassCount += 1;
714
- const shouldReprobe = typeof input.tryReopen === "function" && (blockedPassCount === 1 || blockedPassCount % reprobeEveryN === 0);
715
- if (shouldReprobe) {
716
- try {
717
- const healed = await input.tryReopen?.();
718
- if (healed) {
719
- reason = null;
720
- blockedPassCount = 0;
721
- return;
722
- }
723
- } catch {}
724
- }
725
- const blockedReason = reason;
726
- if (!blockedReason)
727
- return;
728
- throw createFailClosedBlockingError(blockedReason);
729
- }
730
- };
731
- }
732
-
733
630
  // ../plugin/src/features/magic-context/message-index-async.ts
734
631
  function isDatabaseLockedError(error) {
735
632
  if (!error || typeof error !== "object")
@@ -1951,6 +1848,59 @@ function shouldKeepSubagents() {
1951
1848
  return keepSubagents;
1952
1849
  }
1953
1850
 
1851
+ // ../plugin/src/shared/resolve-fallbacks.ts
1852
+ function resolveFallbackChain(userFallbacks) {
1853
+ const userList = normalizeUserFallbacks(userFallbacks);
1854
+ return dedupe(userList.filter(isValidModelSpec));
1855
+ }
1856
+ function normalizeUserFallbacks(userFallbacks) {
1857
+ if (!userFallbacks)
1858
+ return [];
1859
+ if (typeof userFallbacks === "string") {
1860
+ const trimmed = userFallbacks.trim();
1861
+ return trimmed ? [trimmed] : [];
1862
+ }
1863
+ return userFallbacks.map((s) => s.trim()).filter((s) => s.length > 0);
1864
+ }
1865
+ function isValidModelSpec(spec) {
1866
+ const slash = spec.indexOf("/");
1867
+ return slash > 0 && slash < spec.length - 1;
1868
+ }
1869
+ function dedupe(list) {
1870
+ const seen = new Set;
1871
+ const out = [];
1872
+ for (const item of list) {
1873
+ if (seen.has(item))
1874
+ continue;
1875
+ seen.add(item);
1876
+ out.push(item);
1877
+ }
1878
+ return out;
1879
+ }
1880
+ function parseProviderModel(spec) {
1881
+ const slash = spec.indexOf("/");
1882
+ if (slash < 1 || slash >= spec.length - 1)
1883
+ return null;
1884
+ return {
1885
+ providerID: spec.slice(0, slash).trim(),
1886
+ modelID: spec.slice(slash + 1).trim()
1887
+ };
1888
+ }
1889
+ function modelBodyField(spec) {
1890
+ const entry = toModelEntry(spec);
1891
+ if (!entry)
1892
+ return {};
1893
+ const parsed = parseProviderModel(entry.model);
1894
+ return parsed ? { model: parsed, ...entry.qualifier ? { variant: entry.qualifier } : {} } : {};
1895
+ }
1896
+ function toModelEntry(spec) {
1897
+ if (typeof spec === "string") {
1898
+ const model = spec.trim();
1899
+ return model ? { model } : undefined;
1900
+ }
1901
+ return spec;
1902
+ }
1903
+
1954
1904
  // src/clone-inheritance.ts
1955
1905
  import { readFile } from "node:fs/promises";
1956
1906
  var CONTENT_ID_SUFFIX = /:(?:p|file)\d+$/;
@@ -2530,14 +2480,35 @@ class PiSubagentRunner {
2530
2480
  return { result: isolatedResult, extensionRetryUsed: true };
2531
2481
  }
2532
2482
  async runModelChain(options, runMode, primaryModelRef) {
2533
- const models = [options.model, ...options.fallbackModels ?? []].filter((model) => typeof model === "string" && model.length > 0);
2534
- const attempts = models.length > 0 ? models : [undefined];
2483
+ const attempts = [];
2484
+ const seenAttempts = new Set;
2485
+ const appendAttempt = (candidate) => {
2486
+ if (!candidate.model)
2487
+ return;
2488
+ const key = `${candidate.model}\x00${candidate.qualifier ?? ""}`;
2489
+ if (seenAttempts.has(key))
2490
+ return;
2491
+ seenAttempts.add(key);
2492
+ attempts.push(candidate);
2493
+ };
2494
+ if (options.model) {
2495
+ appendAttempt({
2496
+ model: options.model,
2497
+ ...options.thinkingLevel ? { qualifier: options.thinkingLevel } : {}
2498
+ });
2499
+ }
2500
+ for (const candidate of options.fallbackModels ?? []) {
2501
+ appendAttempt(typeof candidate === "string" ? { model: candidate } : candidate);
2502
+ }
2503
+ if (attempts.length === 0)
2504
+ attempts.push(undefined);
2535
2505
  let lastResult = null;
2536
2506
  for (let index = 0;index < attempts.length; index += 1) {
2537
- const model = attempts[index];
2507
+ const attempt = attempts[index];
2538
2508
  const attemptOptions = {
2539
2509
  ...options,
2540
- model,
2510
+ model: attempt?.model,
2511
+ thinkingLevel: attempt?.qualifier,
2541
2512
  fallbackModels: undefined
2542
2513
  };
2543
2514
  const result = await this.runOnce(attemptOptions, runMode, index === 0 ? primaryModelRef : undefined);
@@ -4091,15 +4062,18 @@ async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
4091
4062
  log(`[${callContext}] primary (${explicitPrimaryLabel}) failed: ${shortErr(error)}; trying ${fallbacks.length} fallback(s)`);
4092
4063
  }
4093
4064
  for (let i = 0;i < fallbacks.length; i += 1) {
4094
- const parsed = parseProviderModel(fallbacks[i]);
4065
+ const fallback = toModelEntry(fallbacks[i]);
4066
+ const parsed = fallback ? parseProviderModel(fallback.model) : null;
4095
4067
  if (!parsed) {
4096
- log(`[${callContext}] skipping invalid fallback spec: ${fallbacks[i]}`);
4068
+ log(`[${callContext}] skipping invalid fallback spec: ${String(fallbacks[i])}`);
4097
4069
  continue;
4098
4070
  }
4099
4071
  const label = `${parsed.providerID}/${parsed.modelID}`;
4072
+ const { variant: _primaryVariant, ...bodyWithoutPrimaryVariant } = baseBody;
4100
4073
  const attemptArgs = copyPromptArgs(baseArgs, {
4101
- ...baseBody,
4102
- model: parsed
4074
+ ...bodyWithoutPrimaryVariant,
4075
+ model: parsed,
4076
+ ...fallback?.qualifier ? { variant: fallback.qualifier } : {}
4103
4077
  });
4104
4078
  try {
4105
4079
  await attemptOnce(client, attemptArgs, timeoutMs, options.signal, callContext, label);
@@ -4115,7 +4089,7 @@ async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
4115
4089
  }
4116
4090
  }
4117
4091
  }
4118
- log(`[${callContext}] all models exhausted; tried: ${[explicitPrimaryLabel, ...fallbacks].join(", ")}; last error: ${shortErr(lastError)}`);
4092
+ log(`[${callContext}] all models exhausted; tried: ${[explicitPrimaryLabel, ...fallbacks.map((fallback) => toModelEntry(fallback)?.model ?? String(fallback))].join(", ")}; last error: ${shortErr(lastError)}`);
4119
4093
  throw lastError ?? new Error("All fallback models failed");
4120
4094
  }
4121
4095
  async function attemptAndValidate(client, args, timeoutMs, signal, callContext, attempt, options) {
@@ -4153,15 +4127,18 @@ async function promptSyncWithValidatedOutputRetry(client, args, options) {
4153
4127
  log(`[${callContext}] primary (${explicitPrimaryLabel}) failed validation/prompt: ${shortErr(error)}; trying ${fallbacks.length} fallback(s)`);
4154
4128
  }
4155
4129
  for (let i = 0;i < fallbacks.length; i += 1) {
4156
- const parsed = parseProviderModel(fallbacks[i]);
4130
+ const fallback = toModelEntry(fallbacks[i]);
4131
+ const parsed = fallback ? parseProviderModel(fallback.model) : null;
4157
4132
  if (!parsed) {
4158
- log(`[${callContext}] skipping invalid fallback spec: ${fallbacks[i]}`);
4133
+ log(`[${callContext}] skipping invalid fallback spec: ${String(fallbacks[i])}`);
4159
4134
  continue;
4160
4135
  }
4161
4136
  const label = `${parsed.providerID}/${parsed.modelID}`;
4137
+ const { variant: _primaryVariant, ...bodyWithoutPrimaryVariant } = baseBody;
4162
4138
  const attemptArgs = copyPromptArgs(baseArgs, {
4163
- ...baseBody,
4164
- model: parsed
4139
+ ...bodyWithoutPrimaryVariant,
4140
+ model: parsed,
4141
+ ...fallback?.qualifier ? { variant: fallback.qualifier } : {}
4165
4142
  });
4166
4143
  const attempt = {
4167
4144
  label,
@@ -4186,7 +4163,7 @@ async function promptSyncWithValidatedOutputRetry(client, args, options) {
4186
4163
  }
4187
4164
  }
4188
4165
  }
4189
- log(`[${callContext}] all models exhausted; tried: ${[explicitPrimaryLabel, ...fallbacks].join(", ")}; original error: ${shortErr(firstError)}; last error: ${shortErr(lastError)}`);
4166
+ log(`[${callContext}] all models exhausted; tried: ${[explicitPrimaryLabel, ...fallbacks.map((fallback) => toModelEntry(fallback)?.model ?? String(fallback))].join(", ")}; original error: ${shortErr(firstError)}; last error: ${shortErr(lastError)}`);
4190
4167
  throw firstError ?? lastError ?? new Error("All fallback models failed validation");
4191
4168
  }
4192
4169
  // ../plugin/src/shared/normalize-sdk-response.ts
@@ -5694,153 +5671,6 @@ function isRustAuthorityDrainingError(error) {
5694
5671
  return error instanceof Error && error.message.includes("authority_draining");
5695
5672
  }
5696
5673
 
5697
- // ../plugin/src/shared/redaction.ts
5698
- import { homedir as homedir2, userInfo } from "node:os";
5699
- function escapeRegex(value) {
5700
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5701
- }
5702
- var SECRET_WORDS = [
5703
- "key",
5704
- "token",
5705
- "secret",
5706
- "password",
5707
- "auth",
5708
- "authorization",
5709
- "bearer",
5710
- "credential"
5711
- ];
5712
- var SECRET_SEGMENT_PATTERN = new RegExp(`^(?:${SECRET_WORDS.map((w) => `${w}s?`).join("|")})$`, "i");
5713
- var TRAILING_DESCRIPTORS = new Set(["id", "ids", "value", "values", "header", "headers"]);
5714
- function redactionTypeForKey(key) {
5715
- const normalized = key.trim().toLowerCase().replace(/[^a-z0-9_.-]+/g, "_");
5716
- const suffix = normalized.split(".").filter(Boolean).at(-1) ?? normalized;
5717
- return suffix || "secret";
5718
- }
5719
- function isNonSecretScalarValue(value) {
5720
- const v = value.trim();
5721
- if (v === "true" || v === "false" || v === "null" || v === "undefined")
5722
- return true;
5723
- return /^[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(v);
5724
- }
5725
- var SECRET_QUALIFIERS = new Set([
5726
- "api",
5727
- "access",
5728
- "private",
5729
- "client",
5730
- "auth",
5731
- "authorization",
5732
- "secret",
5733
- "bearer",
5734
- "session",
5735
- "refresh",
5736
- "service",
5737
- "x",
5738
- "openai",
5739
- "anthropic",
5740
- "google",
5741
- "github",
5742
- "huggingface",
5743
- "aws",
5744
- "azure"
5745
- ]);
5746
- function sanitizePathString(value) {
5747
- const home = homedir2();
5748
- const username = userInfo().username;
5749
- let sanitized = value;
5750
- if (home) {
5751
- sanitized = sanitized.replace(new RegExp(escapeRegex(home), "g"), "~");
5752
- }
5753
- sanitized = sanitized.replace(/\/Users\/[^/]+\//g, "/Users/<USER>/");
5754
- sanitized = sanitized.replace(/\/home\/[^/]+\//g, "/home/<USER>/");
5755
- sanitized = sanitized.replace(/C:\\Users\\[^\\]+\\/g, "C:\\Users\\<USER>\\");
5756
- if (username) {
5757
- sanitized = sanitized.replace(new RegExp(escapeRegex(username), "g"), "<USER>");
5758
- }
5759
- return sanitized;
5760
- }
5761
- var SECRET_TEXT_PATTERNS = [
5762
- {
5763
- pattern: /\bsk-ant-(?:api03-)?[A-Za-z0-9_-]{32,}/g,
5764
- replacement: "<ANTHROPIC_API_KEY_REDACTED>"
5765
- },
5766
- {
5767
- pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{32,}/g,
5768
- replacement: "<OPENAI_API_KEY_REDACTED>"
5769
- },
5770
- {
5771
- pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}/g,
5772
- replacement: "<GITHUB_PAT_REDACTED>"
5773
- },
5774
- {
5775
- pattern: /\b(?:gh[opsu]|ghr)_[A-Za-z0-9]{30,}/g,
5776
- replacement: "<GITHUB_TOKEN_REDACTED>"
5777
- },
5778
- {
5779
- pattern: /\bhf_[A-Za-z0-9]{30,}/g,
5780
- replacement: "<HUGGINGFACE_TOKEN_REDACTED>"
5781
- },
5782
- {
5783
- pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
5784
- replacement: "<AWS_ACCESS_KEY_ID_REDACTED>"
5785
- },
5786
- {
5787
- pattern: /\bxox[abprsuvc]-[A-Za-z0-9-]{10,}/g,
5788
- replacement: "<SLACK_TOKEN_REDACTED>"
5789
- },
5790
- {
5791
- pattern: /\bAIza[A-Za-z0-9_-]{35}\b/g,
5792
- replacement: "<GOOGLE_API_KEY_REDACTED>"
5793
- },
5794
- {
5795
- pattern: /\b(Authorization\s*:\s*Bearer\s+)([A-Za-z0-9._~+/=-]{8,})/gi,
5796
- replacement: (_full, prefix) => `${prefix}<REDACTED:bearer>`
5797
- },
5798
- {
5799
- pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
5800
- replacement: "<JWT_REDACTED>"
5801
- },
5802
- {
5803
- pattern: /(["'])([^"']*(?:key|token|secret|password|auth|bearer|credential)[^"']*)\1(\s*:\s*)(["'])([^"']*)\4/gi,
5804
- replacement: (full, quote, key, separator, valueQuote, value) => isNonSecretScalarValue(value) ? full : `${quote}${key}${quote}${separator}${valueQuote}<REDACTED:${redactionTypeForKey(key)}>${valueQuote}`
5805
- },
5806
- {
5807
- pattern: /\b([A-Za-z0-9_.-]*(?:key|token|secret|password|auth|bearer|credential)[A-Za-z0-9_.-]*)\s*=\s*([^\s'"`]+)/gi,
5808
- replacement: (full, key, value) => isNonSecretScalarValue(value) ? full : `${key}=<REDACTED:${redactionTypeForKey(key)}>`
5809
- }
5810
- ];
5811
- function redactSecretText(value) {
5812
- let redacted = value;
5813
- for (const { pattern, replacement } of SECRET_TEXT_PATTERNS) {
5814
- if (typeof replacement === "string") {
5815
- redacted = redacted.replace(pattern, replacement);
5816
- } else {
5817
- redacted = redacted.replace(pattern, replacement);
5818
- }
5819
- }
5820
- return redacted;
5821
- }
5822
- function sanitizeDiagnosticText(value) {
5823
- return redactSecretText(sanitizePathString(value));
5824
- }
5825
- var SHAREABILITY_SENSITIVE_PATTERNS = [
5826
- /\bC:\/Users\/[^/\s]+/i,
5827
- /(?:^|\s)~\/[^\s]+/,
5828
- /\b(?:api[_-]?key|secret|token|password|passwd|pwd|client[_-]?secret|access[_-]?key)\b\s*[:=]\s*\S+/i,
5829
- /\b(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(?::\d+)?\b/i,
5830
- /\b(?:10|127)\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/,
5831
- /\b192\.168\.\d{1,3}\.\d{1,3}\b/,
5832
- /\b172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}\b/
5833
- ];
5834
- function hasShareabilitySensitiveText(text) {
5835
- try {
5836
- if (sanitizeDiagnosticText(text) !== text)
5837
- return true;
5838
- return SHAREABILITY_SENSITIVE_PATTERNS.some((pattern) => pattern.test(text));
5839
- } catch {
5840
- return true;
5841
- }
5842
- }
5843
-
5844
5674
  // ../plugin/src/features/magic-context/dreamer/classify-prompt.ts
5845
5675
  var SCORING_GUIDANCE = `### How to score importance (1-100)
5846
5676
  Importance decides which memories survive when the injected memory block is over budget: high scores stay in context, low scores drop first. So the score is only useful if it **discriminates** — if most memories land in the same band, you have not classified them, you have just labelled them.
@@ -6889,15 +6719,60 @@ function getAsyncModule() {
6889
6719
  })();
6890
6720
  return asyncModulePromise;
6891
6721
  }
6722
+ function acquireSandboxModule(signal) {
6723
+ const modulePromise = getAsyncModule();
6724
+ if (!signal)
6725
+ return modulePromise;
6726
+ if (signal.aborted) {
6727
+ return Promise.reject(signal.reason ?? new Error("smart-note check aborted"));
6728
+ }
6729
+ return new Promise((resolve, reject) => {
6730
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
6731
+ const onAbort = () => {
6732
+ cleanup();
6733
+ reject(signal.reason ?? new Error("smart-note check aborted"));
6734
+ };
6735
+ signal.addEventListener("abort", onAbort, { once: true });
6736
+ modulePromise.then((module) => {
6737
+ cleanup();
6738
+ resolve(module);
6739
+ }, (error) => {
6740
+ cleanup();
6741
+ reject(error);
6742
+ });
6743
+ });
6744
+ }
6892
6745
  var sandboxRunChain = Promise.resolve();
6893
- function withSandboxLock(fn) {
6894
- const run = sandboxRunChain.then(fn, fn);
6746
+ function withSandboxLock(fn, signal, cancelled) {
6747
+ const start = () => signal?.aborted && cancelled ? cancelled() : fn();
6748
+ const run = sandboxRunChain.then(start, start);
6895
6749
  sandboxRunChain = run.then(() => {
6896
6750
  return;
6897
6751
  }, () => {
6898
6752
  return;
6899
6753
  });
6900
- return run;
6754
+ return resolveBeforeAbort(run, signal, cancelled);
6755
+ }
6756
+ function resolveBeforeAbort(run, signal, cancelled) {
6757
+ if (!signal || !cancelled)
6758
+ return run;
6759
+ if (signal.aborted)
6760
+ return Promise.resolve(cancelled());
6761
+ return new Promise((resolve, reject) => {
6762
+ const cleanup = () => signal.removeEventListener("abort", abort);
6763
+ const abort = () => {
6764
+ cleanup();
6765
+ resolve(cancelled());
6766
+ };
6767
+ signal.addEventListener("abort", abort, { once: true });
6768
+ run.then((result) => {
6769
+ cleanup();
6770
+ resolve(result);
6771
+ }, (error) => {
6772
+ cleanup();
6773
+ reject(error);
6774
+ });
6775
+ });
6901
6776
  }
6902
6777
  var DEFAULT_TIMEOUT_MS = 2000;
6903
6778
  var DEFAULT_HEAP_LIMIT_BYTES = 8 * 1024 * 1024;
@@ -6924,9 +6799,17 @@ async function runCompiledSmartNoteCheck(options) {
6924
6799
  if (Buffer.byteLength(options.compiledCheck, "utf8") > MAX_COMPILED_CHECK_BYTES) {
6925
6800
  return failureResult("compiled check exceeds 64 KiB", false);
6926
6801
  }
6927
- return withSandboxLock(() => runCompiledSmartNoteCheckLocked(options));
6802
+ let quickjs;
6803
+ try {
6804
+ quickjs = await acquireSandboxModule(options.signal);
6805
+ } catch (error) {
6806
+ if (options.signal?.aborted)
6807
+ return cancelledResult(options.signal.reason);
6808
+ return failureResult(formatSandboxError(error), false);
6809
+ }
6810
+ return withSandboxLock(() => runCompiledSmartNoteCheckLocked(options, quickjs), options.signal, () => cancelledResult(options.signal?.reason));
6928
6811
  }
6929
- async function runCompiledSmartNoteCheckLocked(options) {
6812
+ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
6930
6813
  if (options.signal?.aborted)
6931
6814
  return cancelledResult(options.signal.reason);
6932
6815
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
@@ -6946,8 +6829,6 @@ async function runCompiledSmartNoteCheckLocked(options) {
6946
6829
  throwIfRunAborted(controller.signal);
6947
6830
  const capabilities = resolveCapabilitiesForRun(options, controller.signal);
6948
6831
  const deadline = Date.now() + timeoutMs;
6949
- const quickjs = await getAsyncModule();
6950
- throwIfRunAborted(controller.signal);
6951
6832
  const context = quickjs.newContext();
6952
6833
  try {
6953
6834
  context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
@@ -7552,7 +7433,7 @@ async function evaluateSmartNotes(args) {
7552
7433
  projectIdentity: args.projectIdentity,
7553
7434
  projectRoot,
7554
7435
  maxChecks: 10,
7555
- sweepBudgetMs: 1e4,
7436
+ sweepBudgetMs: args.sweepBudgetMs ?? 1e4,
7556
7437
  leaseHeld,
7557
7438
  signal: leaseAbortController.signal,
7558
7439
  retinaHandoff: args.retinaHandoff
@@ -8077,19 +7958,26 @@ function parseMapMemoriesManifest(text) {
8077
7958
  if (out.length === 0 && body.trim().length > 0) {
8078
7959
  throw new Error(describeUnrecognizedManifestShape(text, "mappings", "memory"));
8079
7960
  }
8080
- assertNoDuplicateManifestIds(out.map((entry) => entry.id), "mappings");
8081
7961
  return out;
8082
7962
  }
8083
7963
  function validateMapMemoriesManifest(text, expectedIds) {
8084
7964
  const parsed = parseMapMemoriesManifest(text);
8085
7965
  assertParsedManifestNonEmpty(parsed.length, expectedIds.size, text, "mappings", "memory");
8086
- assertManifestCoversExactly(parsed.map((entry) => entry.id), expectedIds, "mappings");
7966
+ assertNoDuplicateManifestIds(parsed.filter((entry) => expectedIds.has(entry.id)).map((entry) => entry.id), "mappings");
8087
7967
  return parsed;
8088
7968
  }
8089
7969
 
8090
7970
  // ../plugin/src/features/magic-context/dreamer/map-memories.ts
8091
7971
  var MAP_BATCH_SIZE = 80;
7972
+ var MAP_BATCH_FLOOR_MS = 240000;
7973
+ var CONSECUTIVE_TIMEOUT_LIMIT2 = 2;
8092
7974
  var MAX_INDEPENDENT_REQUEUE_PER_RUN = MAP_BATCH_SIZE;
7975
+ function computeMapBatchSliceMs(remainingMs, batchesRemaining) {
7976
+ return Math.min(remainingMs, Math.max(MAP_BATCH_FLOOR_MS, Math.floor(remainingMs / batchesRemaining)));
7977
+ }
7978
+ function isTimeoutClassError2(error) {
7979
+ return error instanceof Error && /^prompt timed out after \d+ms$/.test(error.message);
7980
+ }
8093
7981
  function shouldRequeueIndependentMapping(state2, content, repoDir) {
8094
7982
  if (!state2.hasSentinel || state2.files.length > 0)
8095
7983
  return false;
@@ -8137,27 +8025,64 @@ async function mapMemories(args) {
8137
8025
  return result;
8138
8026
  const batches = [];
8139
8027
  for (let i = 0;i < inputs.length; i += MAP_BATCH_SIZE) {
8140
- batches.push(inputs.slice(i, i + MAP_BATCH_SIZE));
8028
+ batches.push({ inputs: inputs.slice(i, i + MAP_BATCH_SIZE), isOmissionRetry: false });
8141
8029
  }
8142
8030
  result.remaining = inputs.length;
8143
8031
  const abortController = new AbortController;
8144
8032
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => abortController.abort(), args.leaseAcquisition);
8145
8033
  try {
8034
+ let consecutiveTimeouts = 0;
8035
+ let timeoutStreakElapsedMs = [];
8146
8036
  for (let i = 0;i < batches.length; i += 1) {
8147
8037
  const remainingMs = Math.max(0, args.deadline - Date.now());
8148
- if (remainingMs <= 0)
8038
+ if (remainingMs <= 0) {
8039
+ result.stopReason = "deadline";
8149
8040
  break;
8150
- const batchesRemaining = batches.length - i;
8151
- const sliceMs = Math.max(1, Math.floor(remainingMs / batchesRemaining));
8152
- const counts = await mapOneBatch(args, batches[i], sliceMs, abortController.signal);
8153
- result.mapped += counts.mapped;
8154
- result.independent += counts.independent;
8155
- result.remaining -= counts.mapped + counts.independent;
8156
- result.batches += 1;
8157
- args.onProgress?.(result.mapped + result.independent);
8041
+ }
8042
+ if (remainingMs < MAP_BATCH_FLOOR_MS) {
8043
+ result.stopReason = "deadline";
8044
+ log(`[dreamer] map-memories: stopping before batch ${i + 1}/${batches.length} — remaining budget ${remainingMs}ms is below the ${MAP_BATCH_FLOOR_MS}ms batch floor; banking ${result.mapped + result.independent} mapping(s)`);
8045
+ break;
8046
+ }
8047
+ const sliceMs = computeMapBatchSliceMs(remainingMs, batches.length - i);
8048
+ const batch = batches[i];
8049
+ if (!batch)
8050
+ break;
8051
+ const outcome = await mapOneBatch(args, batch.inputs, sliceMs, abortController.signal);
8052
+ const committed = outcome.mapped + outcome.independent;
8053
+ result.mapped += outcome.mapped;
8054
+ result.independent += outcome.independent;
8055
+ result.remaining -= committed;
8056
+ if (committed > 0) {
8057
+ result.batches += 1;
8058
+ args.onProgress?.(result.mapped + result.independent);
8059
+ }
8060
+ if (outcome.requeue?.length) {
8061
+ if (!batch.isOmissionRetry) {
8062
+ batches.splice(i + 1, 0, {
8063
+ inputs: outcome.requeue,
8064
+ isOmissionRetry: true
8065
+ });
8066
+ log(`[dreamer] map-memories: committed ${committed}/${batch.inputs.length} mapping(s); requeueing ${outcome.requeue.length} omitted id(s) in a targeted retry`);
8067
+ } else {
8068
+ log(`[dreamer] map-memories: targeted retry still omitted ${outcome.requeue.length} id(s); leaving them unmapped for the next run`);
8069
+ }
8070
+ }
8071
+ if (outcome.failure?.class === "timeout") {
8072
+ consecutiveTimeouts += 1;
8073
+ timeoutStreakElapsedMs.push(outcome.failure.elapsedMs);
8074
+ if (consecutiveTimeouts >= CONSECUTIVE_TIMEOUT_LIMIT2) {
8075
+ result.stopReason = "timeout-circuit-breaker";
8076
+ log(`[dreamer] map-memories starvation: circuit breaker tripped after ${consecutiveTimeouts} consecutive batch timeouts (model too slow for its time slice); per-batch elapsed [${timeoutStreakElapsedMs.join("ms, ")}ms] vs ${sliceMs}ms slice; stopping with ${result.remaining} mapping(s) remaining`);
8077
+ break;
8078
+ }
8079
+ } else {
8080
+ consecutiveTimeouts = 0;
8081
+ timeoutStreakElapsedMs = [];
8082
+ }
8158
8083
  }
8159
8084
  result.complete = result.remaining === 0;
8160
- log(`[dreamer] map-memories: mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}`);
8085
+ log(`[dreamer] map-memories: committed=${result.mapped + result.independent} mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}${result.stopReason ? ` stop_reason=${result.stopReason}` : ""}`);
8161
8086
  return result;
8162
8087
  } finally {
8163
8088
  heartbeat.stop();
@@ -8211,12 +8136,16 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
8211
8136
  const text = extractLatestAssistantText(messages);
8212
8137
  if (!text)
8213
8138
  throw new Error("map-memories returned no output");
8214
- validateMapMemoriesManifest(text, new Set(batch.map((memory) => memory.id)));
8215
- return text;
8139
+ return validateMapMemoriesManifest(text, new Set(batch.map((memory) => memory.id)));
8216
8140
  }
8217
8141
  });
8218
8142
  recordInvocation2(args, startedAt, { status: "completed", messages: run.output });
8219
- return await applyBatchMappings(args, batch, run.validated);
8143
+ const outcome = await applyParsedBatchMappings(args, batch, run.validated);
8144
+ const returnedIds = new Set(run.validated.filter((entry) => batch.some((memory) => memory.id === entry.id)).map((entry) => entry.id));
8145
+ return {
8146
+ ...outcome,
8147
+ requeue: batch.filter((memory) => !returnedIds.has(memory.id))
8148
+ };
8220
8149
  } catch (error) {
8221
8150
  const desc = describeError(error);
8222
8151
  log(`[dreamer] map-memories batch failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
@@ -8225,7 +8154,14 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
8225
8154
  throw error;
8226
8155
  if (signal.aborted)
8227
8156
  throw error;
8228
- return { mapped: 0, independent: 0 };
8157
+ return {
8158
+ mapped: 0,
8159
+ independent: 0,
8160
+ failure: {
8161
+ class: isTimeoutClassError2(error) ? "timeout" : "other",
8162
+ elapsedMs: Date.now() - startedAt
8163
+ }
8164
+ };
8229
8165
  } finally {
8230
8166
  if (agentSessionId && !shouldKeepSubagents()) {
8231
8167
  await args.client.session.delete({
@@ -8237,14 +8173,21 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
8237
8173
  }
8238
8174
  }
8239
8175
  }
8240
- async function applyBatchMappings(args, batch, manifestText) {
8241
- const batchIds = new Set(batch.map((m) => m.id));
8242
- const parsed = parseMapMemoriesManifest(manifestText);
8243
- assertManifestCoversExactly(parsed.map((entry) => entry.id), batchIds, "mappings");
8244
- if (parsed.length === 0)
8176
+ async function applyParsedBatchMappings(args, batch, parsed) {
8177
+ const batchIds = new Set(batch.map((memory) => memory.id));
8178
+ const valid = parsed.filter((entry) => batchIds.has(entry.id));
8179
+ const unknown = parsed.filter((entry) => !batchIds.has(entry.id));
8180
+ if (unknown.length > 0) {
8181
+ log(`[dreamer] map-memories warning: dropping ${unknown.length} unknown mapping entr${unknown.length === 1 ? "y" : "ies"} outside the current batch (${unknown.map((entry) => entry.id).join(", ")})`);
8182
+ }
8183
+ assertNoDuplicateManifestIds(valid.map((entry) => entry.id), "mappings");
8184
+ if (valid.length * 2 < batch.length) {
8185
+ throw new Error(`mappings manifest covers ${valid.length}/${batch.length} batch ids after filtering unknown entries; rejecting mostly-wrong manifest`);
8186
+ }
8187
+ if (valid.length === 0)
8245
8188
  return { mapped: 0, independent: 0 };
8246
8189
  const planned = [];
8247
- for (const p of parsed) {
8190
+ for (const p of valid) {
8248
8191
  if (p.independent) {
8249
8192
  planned.push({ id: p.id, files: [], independent: true });
8250
8193
  continue;
@@ -9549,14 +9492,13 @@ function parseVerifyManifest(text) {
9549
9492
  if (verifyIds(out).length === 0 && body.trim().length > 0) {
9550
9493
  throw new Error(describeUnrecognizedManifestShape(text, "verify", "verified"));
9551
9494
  }
9552
- assertNoDuplicateManifestIds(verifyIds(out), "verify");
9553
9495
  return out;
9554
9496
  }
9555
9497
  function validateVerifyManifest(text, expectedIds) {
9556
9498
  const parsed = parseVerifyManifest(text);
9557
9499
  const ids = verifyIds(parsed);
9558
9500
  assertParsedManifestNonEmpty(ids.length, expectedIds.size, text, "verify", "verified");
9559
- assertManifestCoversExactly(ids, expectedIds, "verify");
9501
+ assertNoDuplicateManifestIds(ids.filter((id) => expectedIds.has(id)), "verify");
9560
9502
  return parsed;
9561
9503
  }
9562
9504
 
@@ -9709,19 +9651,14 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
9709
9651
  const text = extractLatestAssistantText(messages);
9710
9652
  if (!text)
9711
9653
  throw new Error("verify returned no output");
9712
- try {
9713
- validateVerifyManifest(text, new Set(batch.map((memory) => memory.id)));
9714
- } catch (error) {
9715
- const providerFailure = providerOutputFailureFromInvalidManifest(messages, text);
9716
- if (providerFailure)
9717
- throw providerFailure;
9718
- throw error;
9719
- }
9720
- return text;
9654
+ const providerFailure = providerOutputFailureFromInvalidManifest(messages, text);
9655
+ if (providerFailure)
9656
+ throw providerFailure;
9657
+ return validateVerifyManifest(text, new Set(batch.map((memory) => memory.id)));
9721
9658
  }
9722
9659
  });
9723
9660
  recordInvocation4(args, startedAt, { status: "completed", messages: run.output });
9724
- return await applyVerifyManifest(args, batch, run.validated);
9661
+ return await applyParsedVerifyManifest(args, batch, run.validated);
9725
9662
  } catch (error) {
9726
9663
  const desc = describeError(error);
9727
9664
  const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
@@ -9741,17 +9678,31 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
9741
9678
  }
9742
9679
  }
9743
9680
  }
9744
- async function applyVerifyManifest(args, batch, manifestText) {
9681
+ async function applyParsedVerifyManifest(args, batch, parsed) {
9745
9682
  const batchIds = new Set(batch.map((m) => m.id));
9746
- const parsed = parseVerifyManifest(manifestText);
9747
- assertManifestCoversExactly([...parsed.verified, ...parsed.updated, ...parsed.archived].map((entry) => entry.id), batchIds, "verify");
9683
+ const valid = {
9684
+ verified: parsed.verified.filter((entry) => batchIds.has(entry.id)),
9685
+ updated: parsed.updated.filter((entry) => batchIds.has(entry.id)),
9686
+ archived: parsed.archived.filter((entry) => batchIds.has(entry.id))
9687
+ };
9688
+ const unknown = [...parsed.verified, ...parsed.updated, ...parsed.archived].filter((entry) => !batchIds.has(entry.id));
9689
+ if (unknown.length > 0) {
9690
+ log(`[dreamer] verify warning: dropping ${unknown.length} unknown verification entr${unknown.length === 1 ? "y" : "ies"} outside the current batch (${unknown.map((entry) => entry.id).join(", ")})`);
9691
+ }
9692
+ const validIds = [...valid.verified, ...valid.updated, ...valid.archived].map((entry) => entry.id);
9693
+ assertNoDuplicateManifestIds(validIds, "verify");
9694
+ if (validIds.length * 2 < batch.length) {
9695
+ throw new Error(`verify manifest covers ${validIds.length}/${batch.length} batch ids after filtering unknown entries; rejecting mostly-wrong manifest`);
9696
+ }
9697
+ if (validIds.length === 0)
9698
+ return { verified: 0, updated: 0, archived: 0 };
9748
9699
  const now = Date.now();
9749
9700
  const writes = [];
9750
- for (const v of parsed.verified) {
9701
+ for (const v of valid.verified) {
9751
9702
  const files = await normalizeFiles(args, v.files);
9752
9703
  writes.push({ kind: "verify", id: v.id, files });
9753
9704
  }
9754
- for (const u of parsed.updated) {
9705
+ for (const u of valid.updated) {
9755
9706
  const content = u.content.trim();
9756
9707
  if (!content || content.length > 20000) {
9757
9708
  const files2 = await normalizeFiles(args, u.files);
@@ -9767,7 +9718,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
9767
9718
  hash: computeNormalizedHash(content)
9768
9719
  });
9769
9720
  }
9770
- for (const a of parsed.archived) {
9721
+ for (const a of valid.archived) {
9771
9722
  writes.push({ kind: "archive", id: a.id, reason: a.reason });
9772
9723
  }
9773
9724
  if (writes.length === 0)
@@ -10074,7 +10025,7 @@ function createDreamTaskExecutor(deps) {
10074
10025
  leaseKey,
10075
10026
  deadline,
10076
10027
  leaseAcquisition,
10077
- model: config.model ?? deps.mural.model ?? deps.dreamerModel,
10028
+ model: config.model,
10078
10029
  fallbackModels: config.fallbackModels,
10079
10030
  moduleRoute,
10080
10031
  onProgress: (processed) => reportProgress(processed)
@@ -10123,8 +10074,19 @@ function createDreamTaskExecutor(deps) {
10123
10074
  moduleRoute,
10124
10075
  onProgress: (processed) => reportProgress(processed)
10125
10076
  });
10126
- log(`[dreamer] map-memories: mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining}`);
10077
+ log(`[dreamer] map-memories: committed=${result.mapped + result.independent} mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}${result.stopReason ? ` stop_reason=${result.stopReason}` : ""}`);
10127
10078
  if (!result.complete) {
10079
+ if (result.stopReason === "timeout-circuit-breaker") {
10080
+ const error2 = `map-memories starvation: timeout circuit breaker stopped the run with ${result.remaining} remain`;
10081
+ recordRun("failed", error2);
10082
+ return { status: "failed", transient: true, error: error2 };
10083
+ }
10084
+ const processed = result.mapped + result.independent;
10085
+ if (processed > 0) {
10086
+ const progress = `map-memories: committed ${processed} mapping(s) (mapped ${result.mapped}, independent ${result.independent}); ${result.remaining} remain`;
10087
+ recordRun("completed", null, { progress });
10088
+ return { status: "completed" };
10089
+ }
10128
10090
  const error = incompleteMessage(result.remaining);
10129
10091
  recordRun("failed", error);
10130
10092
  return { status: "failed", transient: true, error };
@@ -11235,7 +11197,7 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
11235
11197
  }
11236
11198
  try {
11237
11199
  await runCompiledSmartNoteSweep(reg, db);
11238
- const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreamerConfig, reg.language);
11200
+ const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreamerConfig, "opencode", reg.language, reg.mural?.model);
11239
11201
  const executor = createDreamTaskExecutor({
11240
11202
  client: reg.client,
11241
11203
  sessionDirectory: reg.directory,
@@ -11245,7 +11207,6 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
11245
11207
  userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
11246
11208
  ensureProjectRegistered: reg.ensureRegistered,
11247
11209
  language: reg.language,
11248
- dreamerModel: dreamerConfig.model,
11249
11210
  mural: reg.mural,
11250
11211
  memoryInjectionBudgetTokens: reg.memoryInjectionBudgetTokens,
11251
11212
  retinaHandoff: reg.retinaHandoff,
@@ -11631,11 +11592,13 @@ function registerPiDreamerProject(opts) {
11631
11592
  startDreamScheduleTimerFn({
11632
11593
  directory: opts.projectDir,
11633
11594
  projectIdentity: opts.projectIdentity,
11595
+ harness: "pi",
11634
11596
  client,
11635
11597
  dreamerConfig: opts.config,
11636
11598
  language: opts.language,
11637
11599
  gitCommitIndexing: opts.gitCommitIndexing,
11638
11600
  retinaHandoff: opts.retinaHandoff,
11601
+ mural: opts.mural,
11639
11602
  ensureRegistered: ensureProjectRegisteredFromPiDirectory,
11640
11603
  retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({ projectCwd: opts.projectDir }),
11641
11604
  primerRawProviderFactory: createPiPrimerRawProviderFactory()
@@ -11649,7 +11612,7 @@ function registerPiDreamerProject(opts) {
11649
11612
  const runManual = async (task) => runManualDream({
11650
11613
  db: opts.db,
11651
11614
  projectIdentity: opts.projectIdentity,
11652
- tasks: buildDreamTaskRuntimeConfigs(opts.config, opts.language),
11615
+ tasks: buildDreamTaskRuntimeConfigs(opts.config, "pi", opts.language, opts.mural?.model),
11653
11616
  executor: createDreamTaskExecutor({
11654
11617
  client,
11655
11618
  sessionDirectory: opts.projectDir,
@@ -11661,7 +11624,8 @@ function registerPiDreamerProject(opts) {
11661
11624
  userMemoryCollectionEnabled: userMemoryCollectionEnabled(opts.config),
11662
11625
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
11663
11626
  language: opts.language,
11664
- retinaHandoff: opts.retinaHandoff
11627
+ retinaHandoff: opts.retinaHandoff,
11628
+ mural: opts.mural
11665
11629
  }),
11666
11630
  task
11667
11631
  });
@@ -11697,7 +11661,6 @@ async function awaitInFlightDreamers() {
11697
11661
  }
11698
11662
  function createPiDreamerClient(opts) {
11699
11663
  const runner = piSubagentRunnerFactory();
11700
- const model = opts.config.model;
11701
11664
  const session = {
11702
11665
  create: async (args) => {
11703
11666
  const sessionId = `magic-context-pi-dream-${++sessionCounter}`;
@@ -11718,7 +11681,7 @@ function createPiDreamerClient(opts) {
11718
11681
  }
11719
11682
  const userMessage = extractUserMessage(args);
11720
11683
  const systemPrompt = extractSystemPrompt(args);
11721
- const perTaskModel = extractBodyModel(args) ?? model;
11684
+ const perTaskModel = extractBodyModel(args);
11722
11685
  const requestedAgent = extractBodyAgent(args) ?? "magic-context-dreamer";
11723
11686
  const runPromise = runner.run({
11724
11687
  agent: requestedAgent,
@@ -11729,7 +11692,7 @@ function createPiDreamerClient(opts) {
11729
11692
  timeoutMs: 30 * 60 * 1000,
11730
11693
  cwd: dreamSession.directory,
11731
11694
  signal: args.signal ?? undefined,
11732
- thinkingLevel: opts.config.thinking_level
11695
+ thinkingLevel: extractBodyVariant(args)
11733
11696
  });
11734
11697
  inFlightDreams.add(runPromise);
11735
11698
  try {
@@ -11820,6 +11783,13 @@ function extractBodyModel(args) {
11820
11783
  }
11821
11784
  return;
11822
11785
  }
11786
+ function extractBodyVariant(args) {
11787
+ const body = args.body;
11788
+ if (typeof body !== "object" || body === null)
11789
+ return;
11790
+ const variant = body.variant;
11791
+ return typeof variant === "string" && variant.length > 0 ? variant : undefined;
11792
+ }
11823
11793
  function extractBodyAgent(args) {
11824
11794
  const body = args.body;
11825
11795
  if (typeof body !== "object" || body === null)
@@ -12019,6 +11989,28 @@ var embedPauseBySession = new Set;
12019
11989
  var embedRunStateBySession = new Map;
12020
11990
  var autoEmbedAttemptedBySession = new Set;
12021
11991
 
11992
+ // ../plugin/src/hooks/magic-context/format-embed-failure.ts
11993
+ function formatEmbedFailureSummary(embedded, remaining, failure) {
11994
+ const compartment = `compartment${embedded === 1 ? "" : "s"}`;
11995
+ if (!failure) {
11996
+ return `Embedded ${embedded} ${compartment}; ${remaining} could not be embedded (the provider returned no result). Run /ctx-embed start again to retry them.`;
11997
+ }
11998
+ const retry = " Run /ctx-embed start again to retry them.";
11999
+ switch (failure.class) {
12000
+ case "substitution_rejected":
12001
+ return `Embedded ${embedded} ${compartment}; ${remaining} rejected: ${failure.reason}. Fix: set embedding.model to the served spelling.`;
12002
+ case "http_error": {
12003
+ const fix = failure.reason.startsWith("HTTP 401") || failure.reason.startsWith("HTTP 403") ? " Fix: check embedding.api_key." : failure.reason.startsWith("HTTP 402") ? " Fix: check provider quota or billing." : failure.reason.startsWith("HTTP 404") ? " Fix: check embedding.endpoint and embedding.model." : failure.retryable ? retry : " Fix: check embedding.endpoint, embedding.model, and credentials.";
12004
+ return `Embedded ${embedded} ${compartment}; ${remaining} failed: ${failure.reason}.${fix}`;
12005
+ }
12006
+ case "invalid_envelope":
12007
+ return `Embedded ${embedded} ${compartment}; ${remaining} failed: ${failure.reason}. Fix: configure an OpenAI-compatible embedding endpoint that returns data[].embedding.`;
12008
+ case "empty_result":
12009
+ case "transport_error":
12010
+ return `Embedded ${embedded} ${compartment}; ${remaining} failed: ${failure.reason}.${failure.retryable ? retry : ""}`;
12011
+ }
12012
+ }
12013
+
12022
12014
  // ../plugin/src/hooks/magic-context/format-embed-status.ts
12023
12015
  function formatEmbedStatusText(coverage, drain) {
12024
12016
  if (!coverage.enabled) {
@@ -12168,7 +12160,7 @@ Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`,
12168
12160
  return {
12169
12161
  text: `## /ctx-embed
12170
12162
 
12171
- Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"}; ${outcome.remaining} could not be embedded (the provider returned no result). Run /ctx-embed start again to retry them.`,
12163
+ ${formatEmbedFailureSummary(outcome.embedded, outcome.remaining, outcome.failure)}`,
12172
12164
  level: "info"
12173
12165
  };
12174
12166
  default:
@@ -12961,12 +12953,15 @@ function writeTransformDecisionRowOnDatabase(db, row, configureBusyTimeout) {
12961
12953
  const systemHashNew = row.materialized ? row.systemHashNew : null;
12962
12954
  const m0ModelKeyPrev = row.materialized ? row.m0ModelKeyPrev : null;
12963
12955
  const m0ModelKeyNew = row.materialized ? row.m0ModelKeyNew : null;
12956
+ const m0ToolSetHashPrev = row.m0ToolSetHashPrev ?? null;
12957
+ const m0ToolSetHashNew = row.m0ToolSetHashNew ?? null;
12964
12958
  db.prepare(`INSERT OR REPLACE INTO transform_decisions (
12965
12959
  session_id, harness, message_id, ts_ms, decision, materialized,
12966
12960
  materialize_reason, system_hash_prev, system_hash_new,
12961
+ m0_tool_set_hash_prev, m0_tool_set_hash_new,
12967
12962
  m0_model_key_prev, m0_model_key_new, emergency, dropped_tokens,
12968
12963
  dropped_count, input_tokens
12969
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(row.sessionId, row.harness, row.messageId, row.tsMs, row.decision, row.materialized ? 1 : 0, row.materializeReason, systemHashPrev, systemHashNew, m0ModelKeyPrev, m0ModelKeyNew, row.emergency ? 1 : 0, Math.max(0, Math.floor(row.droppedTokens)), Math.max(0, Math.floor(row.droppedCount)), Math.max(0, Math.floor(row.inputTokens)));
12964
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(row.sessionId, row.harness, row.messageId, row.tsMs, row.decision, row.materialized ? 1 : 0, row.materializeReason, systemHashPrev, systemHashNew, m0ToolSetHashPrev, m0ToolSetHashNew, m0ModelKeyPrev, m0ModelKeyNew, row.emergency ? 1 : 0, Math.max(0, Math.floor(row.droppedTokens)), Math.max(0, Math.floor(row.droppedCount)), Math.max(0, Math.floor(row.inputTokens)));
12970
12965
  db.prepare(`DELETE FROM transform_decisions
12971
12966
  WHERE session_id = ? AND harness = ?
12972
12967
  AND rowid NOT IN (
@@ -16022,6 +16017,17 @@ function isSyntheticMessage(message, index, input) {
16022
16017
  const role = typeof message.role === "string" ? message.role : "";
16023
16018
  return role === "system" || role === "custom" || role === "compactionSummary";
16024
16019
  }
16020
+ function countRealPiUserMessages(input) {
16021
+ let count = 0;
16022
+ for (let index = 0;index < input.messages.length; index += 1) {
16023
+ const message = input.messages[index];
16024
+ if (!isRecord3(message) || message.role !== "user")
16025
+ continue;
16026
+ if (!isSyntheticMessage(message, index, input))
16027
+ count += 1;
16028
+ }
16029
+ return count;
16030
+ }
16025
16031
  function imageContentAndTokens(part) {
16026
16032
  const data = typeof part.data === "string" ? part.data : "";
16027
16033
  const mimeType = typeof part.mimeType === "string" ? part.mimeType : "image/png";
@@ -16215,7 +16221,7 @@ function excludedDraft(key, value) {
16215
16221
  tokens: 0
16216
16222
  };
16217
16223
  }
16218
- function finalizeParts(drafts, protectedTags) {
16224
+ function finalizeParts(drafts, protectedTags, pendingDropTagNumbers) {
16219
16225
  const visibleTags = new Map;
16220
16226
  for (const part of drafts) {
16221
16227
  if (part.kind !== "excluded" && part.tag)
@@ -16230,7 +16236,8 @@ function finalizeParts(drafts, protectedTags) {
16230
16236
  const parts = drafts.map((draft) => {
16231
16237
  const protectedPart = draft.tag ? protectedNumbers.has(draft.tag.tagNumber) : false;
16232
16238
  const tokens = Math.max(0, draft.tokens);
16233
- const uTokens = draft.tag && !protectedPart && draft.kind !== "excluded" ? tokens : 0;
16239
+ const queuedForDrop = draft.tag ? pendingDropTagNumbers.has(draft.tag.tagNumber) : false;
16240
+ const uTokens = draft.tag && !protectedPart && !queuedForDrop && draft.kind !== "excluded" ? tokens : 0;
16234
16241
  t += tokens;
16235
16242
  u += uTokens;
16236
16243
  return {
@@ -16241,7 +16248,8 @@ function finalizeParts(drafts, protectedTags) {
16241
16248
  uTokens,
16242
16249
  tagNumber: draft.tag?.tagNumber ?? null,
16243
16250
  tagStatus: draft.tag ? "active" : null,
16244
- protected: protectedPart
16251
+ protected: protectedPart,
16252
+ queuedForDrop
16245
16253
  };
16246
16254
  });
16247
16255
  const clampedT = Math.max(0, t);
@@ -16383,29 +16391,34 @@ function measurePiTailHygiene(input) {
16383
16391
  drafts.push(excludedDraft(`${key}\x00excluded`, part));
16384
16392
  }
16385
16393
  }
16386
- return finalizeParts(drafts, input.protectedTags);
16394
+ return finalizeParts(drafts, input.protectedTags, input.pendingDropTagNumbers ?? new Set);
16387
16395
  }
16388
16396
  function sameMeasuredPrefix(baseline, current) {
16389
16397
  if (current.length < baseline.length)
16390
- return { valid: false, boundaryAdvanceU: 0 };
16398
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16391
16399
  let boundaryAdvanceU = 0;
16400
+ let queuedDropDeltaU = 0;
16392
16401
  for (let index = 0;index < baseline.length; index += 1) {
16393
16402
  const before = baseline[index];
16394
16403
  const after = current[index];
16395
16404
  if (before.key !== after.key || before.contentHash !== after.contentHash || before.kind !== after.kind || before.tokens !== after.tokens || before.tagNumber !== after.tagNumber || before.tagStatus !== after.tagStatus) {
16396
- return { valid: false, boundaryAdvanceU: 0 };
16405
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16397
16406
  }
16398
16407
  if (!before.protected && after.protected)
16399
- return { valid: false, boundaryAdvanceU: 0 };
16408
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16400
16409
  if (before.protected && !after.protected) {
16401
16410
  if (after.tagStatus !== "active")
16402
- return { valid: false, boundaryAdvanceU: 0 };
16403
- boundaryAdvanceU += after.tokens;
16411
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16412
+ boundaryAdvanceU += after.uTokens;
16413
+ } else if (before.queuedForDrop !== after.queuedForDrop) {
16414
+ if (before.tagStatus !== "active" || after.tagStatus !== "active")
16415
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16416
+ queuedDropDeltaU += after.uTokens - before.uTokens;
16404
16417
  } else if (before.uTokens !== after.uTokens) {
16405
- return { valid: false, boundaryAdvanceU: 0 };
16418
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16406
16419
  }
16407
16420
  }
16408
- return { valid: true, boundaryAdvanceU };
16421
+ return { valid: true, boundaryAdvanceU, queuedDropDeltaU };
16409
16422
  }
16410
16423
  function refreshPiTailHygieneBaseline(input) {
16411
16424
  const measured = measurePiTailHygiene(input);
@@ -16437,7 +16450,7 @@ function refreshPiTailHygieneBaseline(input) {
16437
16450
  };
16438
16451
  }
16439
16452
  let turnDeltaT = 0;
16440
- let turnDeltaU = prefix.boundaryAdvanceU;
16453
+ let turnDeltaU = prefix.boundaryAdvanceU + prefix.queuedDropDeltaU;
16441
16454
  for (let index = input.previous.baselineParts.length;index < measured.parts.length; index += 1) {
16442
16455
  const part = measured.parts[index];
16443
16456
  turnDeltaT += part.tokens;
@@ -16531,20 +16544,38 @@ function maybeChannel1ReminderForToolResult(args) {
16531
16544
  if (deltaTokens === 0)
16532
16545
  return null;
16533
16546
  state2.turnDeltaT += deltaTokens;
16547
+ if (state2.agentDropsAppliedThisPass)
16548
+ return null;
16549
+ const nudgeState = getChannel1NudgeState(db, sessionId);
16534
16550
  const decision = decideChannel1({
16535
16551
  ...state2,
16536
16552
  lastNudgeUndropped: getLastNudgeUndropped(db, sessionId),
16537
- lastNudgeLevel: getLastNudgeLevel(db, sessionId),
16553
+ lastNudgeLevel: nudgeState.level,
16538
16554
  hasRecentReduce: state2.reducedSinceRefresh
16539
16555
  });
16540
16556
  setLastNudgeUndropped(db, sessionId, decision.nextLastNudge);
16541
- setLastNudgeLevel(db, sessionId, decision.nextLastNudgeLevel);
16542
- if (!decision.fire)
16557
+ if (!decision.fire) {
16558
+ setChannel1NudgeState(db, sessionId, {
16559
+ level: decision.nextLastNudgeLevel,
16560
+ ordinal: decision.nextLastNudgeLevel === "" ? 0 : nudgeState.ordinal
16561
+ });
16543
16562
  return null;
16544
- return {
16563
+ }
16564
+ const sticky = shouldUseStickyChannel1Reminder({
16565
+ lastLevel: nudgeState.level,
16566
+ lastOrdinal: nudgeState.ordinal,
16567
+ level: decision.level,
16568
+ currentRealUserTurnCount: state2.realUserTurnCount
16569
+ });
16570
+ const block = {
16545
16571
  type: "text",
16546
- text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state2.oldestReclaimableToolTags)
16572
+ text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state2.usableWindow, state2.oldestReclaimableToolTags, sticky)
16547
16573
  };
16574
+ setChannel1NudgeState(db, sessionId, {
16575
+ level: decision.level,
16576
+ ordinal: state2.realUserTurnCount
16577
+ });
16578
+ return block;
16548
16579
  }
16549
16580
  var CHANNEL2_NUDGE_CUSTOM_TYPE = "magic-context:ceiling-nudge";
16550
16581
  function maybeDeliverChannel2Pi(pi, db, sessionId) {
@@ -16575,7 +16606,7 @@ function maybeDeliverChannel2Pi(pi, db, sessionId) {
16575
16606
  try {
16576
16607
  const message = {
16577
16608
  customType: CHANNEL2_NUDGE_CUSTOM_TYPE,
16578
- content: buildChannel2Reminder(undropped, baseline.oldestReclaimableToolTags),
16609
+ content: buildChannel2Reminder(undropped, baseline.usableWindow, baseline.oldestReclaimableToolTags),
16579
16610
  display: false,
16580
16611
  details: { kind: "channel-2-ceiling-nudge" }
16581
16612
  };
@@ -21017,19 +21048,24 @@ async function runHistorianSubagentWithTransientRetries(args) {
21017
21048
  }
21018
21049
  return historianAbortResult(startedAt);
21019
21050
  }
21020
- function buildHistorianFallbackChain(primaryModel, fallbackModels, fallbackModelId) {
21021
- const seen = new Set;
21022
- if (primaryModel)
21023
- seen.add(primaryModel);
21051
+ function buildHistorianFallbackChain(primaryModel, primaryThinkingLevel, fallbackModels, fallbackModelId) {
21052
+ const seen = new Set([
21053
+ `${primaryModel}\x00${primaryThinkingLevel ?? ""}`
21054
+ ]);
21024
21055
  const chain = [];
21025
21056
  for (const candidate of fallbackModels ?? []) {
21026
- if (!candidate || seen.has(candidate))
21057
+ const entry = typeof candidate === "string" ? { model: candidate } : candidate;
21058
+ const key = `${entry.model}\x00${entry.qualifier ?? ""}`;
21059
+ if (!entry.model || seen.has(key))
21027
21060
  continue;
21028
- seen.add(candidate);
21029
- chain.push({ modelId: candidate, kind: "configured" });
21061
+ seen.add(key);
21062
+ chain.push({ entry, kind: "configured" });
21030
21063
  }
21031
- if (fallbackModelId && !seen.has(fallbackModelId)) {
21032
- chain.push({ modelId: fallbackModelId, kind: "session" });
21064
+ if (fallbackModelId) {
21065
+ const entry = { model: fallbackModelId };
21066
+ const key = `${entry.model}\x00`;
21067
+ if (!seen.has(key))
21068
+ chain.push({ entry, kind: "session" });
21033
21069
  }
21034
21070
  return chain;
21035
21071
  }
@@ -21348,11 +21384,11 @@ ${chunkText}`,
21348
21384
  validatedDraftText = repairResult.assistantText;
21349
21385
  }
21350
21386
  }
21351
- const fallbackChain = buildHistorianFallbackChain(historianModel, fallbackModels, fallbackModelId);
21387
+ const fallbackChain = buildHistorianFallbackChain(historianModel, thinkingLevel, fallbackModels, fallbackModelId);
21352
21388
  if (validatedPass.kind !== "ok" && !(validatedPass.kind === "spawn-failed" && validatedPass.reason === "abort") && fallbackChain.length > 0) {
21353
21389
  for (let i = 0;i < fallbackChain.length; i += 1) {
21354
21390
  const candidate = fallbackChain[i];
21355
- sessionLog(sessionId, `historian: escalating to ${candidate.kind === "session" ? "session-model last resort" : "configured fallback model"} ${candidate.modelId}`);
21391
+ sessionLog(sessionId, `historian: escalating to ${candidate.kind === "session" ? "session-model last resort" : "configured fallback model"} ${candidate.entry.model}`);
21356
21392
  const fbResult = await runHistorianSubagentWithTransientRetries({
21357
21393
  runner,
21358
21394
  sessionId,
@@ -21362,11 +21398,11 @@ ${chunkText}`,
21362
21398
  agent: HISTORIAN_AGENT_NAME,
21363
21399
  systemPrompt: historianSystemPrompt,
21364
21400
  userMessage: prompt,
21365
- model: candidate.modelId,
21401
+ model: candidate.entry.model,
21366
21402
  timeoutMs: historianTimeoutMs,
21367
21403
  cwd: directory,
21368
21404
  signal,
21369
- thinkingLevel,
21405
+ thinkingLevel: candidate.entry.qualifier,
21370
21406
  onProgress: buildProgressLogger("fallback"),
21371
21407
  accountingSessionId: sessionId,
21372
21408
  accountingSubagent: "historian"
@@ -24475,11 +24511,13 @@ function registerPiContextHandler(pi, baseOptions) {
24475
24511
  if (!options.compactionOff && !sessionMetaForCh1.isSubagent) {
24476
24512
  const tags = getTagsBySession(options.db, sessionId);
24477
24513
  const protectedTags = options.protectedTags ?? 20;
24514
+ const pendingDropTagNumbers = new Set(getPendingOps(options.db, sessionId).filter((operation) => operation.operation === "drop").map((operation) => operation.tagId));
24478
24515
  const stableId = (message) => message && typeof message === "object" ? result.postCommitEntryIdByRef.get(message) : undefined;
24479
24516
  const baseline = refreshPiTailHygieneBaseline({
24480
24517
  messages: outputMessages,
24481
24518
  tags,
24482
24519
  protectedTags,
24520
+ pendingDropTagNumbers,
24483
24521
  stableId,
24484
24522
  syntheticLeadingCount: result.syntheticLeadingCount,
24485
24523
  cacheBusting: result.bustedThisPass,
@@ -24490,7 +24528,17 @@ function registerPiContextHandler(pi, baseOptions) {
24490
24528
  const oldestReclaimableToolTags = getOldestActiveUnprotectedToolTags(options.db, sessionId, protectedTags);
24491
24529
  const channelState = {
24492
24530
  ...baseline,
24531
+ usableWindow: usageContextLimit ?? 0,
24532
+ realUserTurnCount: countRealPiUserMessages({
24533
+ messages: outputMessages,
24534
+ tags,
24535
+ protectedTags,
24536
+ pendingDropTagNumbers,
24537
+ stableId,
24538
+ syntheticLeadingCount: result.syntheticLeadingCount
24539
+ }),
24493
24540
  reducedSinceRefresh: false,
24541
+ agentDropsAppliedThisPass: result.agentDropsAppliedThisPass,
24494
24542
  oldestReclaimableToolTags
24495
24543
  };
24496
24544
  setPiChannel1Baseline(sessionId, channelState);
@@ -24515,6 +24563,7 @@ function registerPiContextHandler(pi, baseOptions) {
24515
24563
  messages: outputMessages,
24516
24564
  tags,
24517
24565
  protectedTags,
24566
+ pendingDropTagNumbers,
24518
24567
  stableId,
24519
24568
  syntheticLeadingCount: result.syntheticLeadingCount,
24520
24569
  expectedSignature: baseline.contentSignature
@@ -25044,6 +25093,7 @@ async function runCompactionOffPipeline(args) {
25044
25093
  droppedCount: 0,
25045
25094
  emergency: false,
25046
25095
  bustedThisPass: injectionResult?.m0Materialized === true,
25096
+ agentDropsAppliedThisPass: false,
25047
25097
  targetCount: 0,
25048
25098
  reasoningWatermark: args.sessionMeta.clearedReasoningThroughTag ?? 0,
25049
25099
  activeTags: [],
@@ -25617,6 +25667,7 @@ async function runPipeline(args) {
25617
25667
  droppedCount,
25618
25668
  emergency,
25619
25669
  bustedThisPass,
25670
+ agentDropsAppliedThisPass: pendingOpsDidMutate,
25620
25671
  targetCount: targets.size,
25621
25672
  reasoningWatermark: args.sessionMeta.clearedReasoningThroughTag ?? 0,
25622
25673
  activeTags,
@@ -25909,6 +25960,7 @@ async function runValidatedHistorianPass(args) {
25909
25960
  const firstRun = await runHistorianPrompt({
25910
25961
  ...args,
25911
25962
  dumpLabel: `${args.dumpLabelBase}-initial`,
25963
+ modelOverride: args.model,
25912
25964
  agentId: args.agentId
25913
25965
  });
25914
25966
  if (!firstRun.ok || !firstRun.result) {
@@ -25937,6 +25989,7 @@ async function runValidatedHistorianPass(args) {
25937
25989
  ...args,
25938
25990
  prompt: repairPrompt,
25939
25991
  dumpLabel: `${args.dumpLabelBase}-repair`,
25992
+ modelOverride: args.model,
25940
25993
  agentId: args.agentId
25941
25994
  });
25942
25995
  if (!repairRun.ok || !repairRun.result) {
@@ -25977,7 +26030,8 @@ async function runEditorPassOrFallback(args) {
25977
26030
  timeoutMs: args.timeoutMs,
25978
26031
  dumpLabel: `${args.dumpLabelBase}-editor`,
25979
26032
  agentId: HISTORIAN_EDITOR_AGENT,
25980
- parentInvocationId: args.draftInvocationId ?? null
26033
+ parentInvocationId: args.draftInvocationId ?? null,
26034
+ modelOverride: args.model
25981
26035
  });
25982
26036
  if (!editorRun.ok || !editorRun.result) {
25983
26037
  sessionLog(args.parentSessionId, "historian two-pass: editor call failed", {
@@ -26030,7 +26084,7 @@ async function runHistorianPrompt(args) {
26030
26084
  });
26031
26085
  };
26032
26086
  try {
26033
- sessionLog(parentSessionId, `historian: creating child session (agent=${agentId}, model=${modelOverride ? `${modelOverride.providerID}/${modelOverride.modelID}` : `agent:${agentId}`})`);
26087
+ sessionLog(parentSessionId, `historian: creating child session (agent=${toModelEntry(modelOverride)?.model ?? `agent:${agentId}`})`);
26034
26088
  const createResponse = await createChildSessionWithFence({
26035
26089
  client,
26036
26090
  db,
@@ -26054,7 +26108,7 @@ async function runHistorianPrompt(args) {
26054
26108
  query: { directory: sessionDirectory },
26055
26109
  body: {
26056
26110
  agent: agentId,
26057
- ...modelOverride ? { model: modelOverride } : {},
26111
+ ...modelBodyField(modelOverride),
26058
26112
  parts: [{ type: "text", text: prompt, synthetic: true }]
26059
26113
  }
26060
26114
  }, {
@@ -26124,10 +26178,21 @@ async function runHistorianPrompt(args) {
26124
26178
  async function runFallbackHistorianPass(args) {
26125
26179
  const seen = new Set;
26126
26180
  const chain = [];
26127
- for (const candidate of [...args.fallbackModels ?? [], args.fallbackModelId ?? ""]) {
26128
- if (!candidate || seen.has(candidate))
26181
+ const primary = toModelEntry(args.model);
26182
+ for (const candidateInput of [
26183
+ ...args.fallbackModels ?? [],
26184
+ ...args.fallbackModelId ? [{ model: args.fallbackModelId }] : []
26185
+ ]) {
26186
+ const candidate = toModelEntry(candidateInput);
26187
+ if (!candidate)
26129
26188
  continue;
26130
- seen.add(candidate);
26189
+ const key = `${candidate.model}\x00${candidate.qualifier ?? ""}`;
26190
+ if (!candidate.model || seen.has(key))
26191
+ continue;
26192
+ if (primary?.model === candidate.model && primary.qualifier === candidate.qualifier) {
26193
+ continue;
26194
+ }
26195
+ seen.add(key);
26131
26196
  chain.push(candidate);
26132
26197
  }
26133
26198
  if (chain.length === 0) {
@@ -26135,9 +26200,9 @@ async function runFallbackHistorianPass(args) {
26135
26200
  }
26136
26201
  let lastError = args.error;
26137
26202
  for (let i = 0;i < chain.length; i += 1) {
26138
- const modelId = chain[i];
26139
- const modelOverride = parseModelOverride(modelId);
26140
- if (!modelOverride)
26203
+ const modelOverride = chain[i];
26204
+ const modelId = modelOverride.model;
26205
+ if (!parseModelOverride(modelId))
26141
26206
  continue;
26142
26207
  const isSessionModelLastResort = modelId === args.fallbackModelId && i === chain.length - 1;
26143
26208
  sessionLog(args.parentSessionId, `compartment agent: retrying historian with ${modelId} (${isSessionModelLastResort ? "session-model last resort" : "configured fallback"} ${i + 1}/${chain.length})`);
@@ -26466,6 +26531,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
26466
26531
  sequenceOffset: candidateCompartments.length,
26467
26532
  dumpLabelBase: `recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
26468
26533
  timeoutMs: historianTimeoutMs,
26534
+ model: deps.model,
26469
26535
  fallbackModelId: deps.fallbackModelId,
26470
26536
  fallbackModels: deps.fallbackModels,
26471
26537
  twoPass: deps.historianTwoPass,
@@ -26886,6 +26952,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
26886
26952
  sequenceOffset: candidateCompartments.length,
26887
26953
  dumpLabelBase: `partial-recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
26888
26954
  timeoutMs: historianTimeoutMs,
26955
+ model: deps.model,
26889
26956
  fallbackModelId: deps.fallbackModelId,
26890
26957
  fallbackModels: deps.fallbackModels,
26891
26958
  twoPass: deps.historianTwoPass,
@@ -27077,11 +27144,11 @@ function createPiHistorianClient(args) {
27077
27144
  agent: "magic-context-historian",
27078
27145
  systemPrompt: args.systemPrompt,
27079
27146
  userMessage: promptText,
27080
- model: modelOverride ?? args.model,
27147
+ model: modelOverride?.model ?? args.model,
27081
27148
  fallbackModels: modelOverride ? undefined : args.fallbackModels,
27082
27149
  timeoutMs: args.timeoutMs,
27083
27150
  cwd: args.directory,
27084
- thinkingLevel: args.thinkingLevel,
27151
+ thinkingLevel: modelOverride ? modelOverride.qualifier : args.thinkingLevel,
27085
27152
  accountingSessionId: args.accountingSessionId,
27086
27153
  accountingSubagent: "recomp"
27087
27154
  });
@@ -27129,7 +27196,8 @@ function readBodyModel(body) {
27129
27196
  return;
27130
27197
  const { providerID, modelID } = model;
27131
27198
  if (typeof providerID === "string" && providerID.length > 0 && typeof modelID === "string" && modelID.length > 0) {
27132
- return `${providerID}/${modelID}`;
27199
+ const qualifier = typeof body.variant === "string" && body.variant.length > 0 ? body.variant : undefined;
27200
+ return qualifier ? { model: `${providerID}/${modelID}`, qualifier } : { model: `${providerID}/${modelID}` };
27133
27201
  }
27134
27202
  return;
27135
27203
  }
@@ -27739,13 +27807,17 @@ async function runPiMemoryMigration(deps) {
27739
27807
  const prompt = buildMemoryMigrationPrompt(memories);
27740
27808
  const modelChain = [];
27741
27809
  const seenModels = new Set;
27742
- for (const m of [
27743
- deps.primaryModel ?? deps.model,
27744
- ...deps.fallbackModels ?? []
27810
+ for (const candidate of [
27811
+ {
27812
+ model: deps.primaryModel ?? deps.model,
27813
+ ...deps.primaryModel ? {} : deps.thinkingLevel ? { qualifier: deps.thinkingLevel } : {}
27814
+ },
27815
+ ...(deps.fallbackModels ?? []).map((fallback) => typeof fallback === "string" ? { model: fallback } : fallback)
27745
27816
  ]) {
27746
- if (m && !seenModels.has(m)) {
27747
- seenModels.add(m);
27748
- modelChain.push(m);
27817
+ const key = `${candidate.model}\x00${candidate.qualifier ?? ""}`;
27818
+ if (candidate.model && !seenModels.has(key)) {
27819
+ seenModels.add(key);
27820
+ modelChain.push(candidate);
27749
27821
  }
27750
27822
  }
27751
27823
  let parsed = null;
@@ -27753,17 +27825,17 @@ async function runPiMemoryMigration(deps) {
27753
27825
  for (let i = 0;i < modelChain.length; i += 1) {
27754
27826
  const model = modelChain[i];
27755
27827
  if (i > 0) {
27756
- sessionLog(deps.sessionId, `memory-migration: escalating to configured fallback model ${model} (${i}/${modelChain.length - 1})`);
27828
+ sessionLog(deps.sessionId, `memory-migration: escalating to configured fallback model ${model.model} (${i}/${modelChain.length - 1})`);
27757
27829
  }
27758
27830
  const result = await deps.runner.run({
27759
27831
  agent: "magic-context-historian",
27760
27832
  systemPrompt: withMigrationLanguageDirective(MIGRATION_SYSTEM_PROMPT2, deps.language),
27761
27833
  userMessage: prompt,
27762
- model,
27834
+ model: model.model,
27763
27835
  fallbackModels: undefined,
27764
27836
  timeoutMs: deps.timeoutMs ?? 5 * 60 * 1000,
27765
27837
  cwd: deps.directory,
27766
- thinkingLevel: deps.thinkingLevel,
27838
+ thinkingLevel: model.qualifier,
27767
27839
  accountingSessionId: deps.sessionId,
27768
27840
  accountingSubagent: "recomp"
27769
27841
  });
@@ -28277,7 +28349,7 @@ ${c.content}
28277
28349
  // package.json
28278
28350
  var package_default = {
28279
28351
  name: "@cortexkit/pi-magic-context",
28280
- version: "0.38.1",
28352
+ version: "0.40.0",
28281
28353
  type: "module",
28282
28354
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
28283
28355
  main: "dist/index.js",
@@ -28308,7 +28380,7 @@ var package_default = {
28308
28380
  build: "tsc -p ../retina-local-fs/tsconfig.build.json && rm -f dist/index.js dist/subagent-entry.js dist/*-*.js && bun build src/index.ts src/subagent-entry.ts --outdir dist --target node --format esm --splitting --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external @huggingface/transformers --external node:sqlite",
28309
28381
  "build:e2e-argv": "bun build src/subagent-runner.ts --outfile ../../tests/docker/.generated/subagent-runner-e2e.mjs --target node --format esm --external node:sqlite",
28310
28382
  typecheck: "tsc -p ../retina-local-fs/tsconfig.build.json && tsc --noEmit",
28311
- test: "bun test",
28383
+ test: "bun install --frozen-lockfile && bun test",
28312
28384
  lint: "biome check src",
28313
28385
  "lint:fix": "biome check --write src",
28314
28386
  format: "biome format --write src",
@@ -28463,6 +28535,7 @@ function renderInner(s, theme, innerWidth) {
28463
28535
  lines.push("* Conversation includes model Reasoning; hygiene excludes it.");
28464
28536
  lines.push("");
28465
28537
  lines.push(`Counts: ${s.compartmentCount} compartments · ${s.memoryCount} memories (${s.memoryBlockCount} injected) · ${s.sessionNoteCount + s.readySmartNoteCount} notes`);
28538
+ lines.push(`Active profile: ${s.activeProfile ?? "none"}`);
28466
28539
  lines.push(`Historian: ${s.historianRunning ? theme.fg("warning", "running") : theme.fg("accent", "idle")}${s.historianFailureCount > 0 ? ` · ${theme.fg("error", `last failure ${s.historianLastFailureAt ? relTime(s.historianLastFailureAt) : "unknown"}`)}` : ""}`);
28467
28540
  if (s.recompInFlight) {
28468
28541
  lines.push(`Upgrade: ${theme.fg("warning", "recomp/upgrade running…")}`);
@@ -28593,6 +28666,7 @@ ${safeStringify(tool.parameters)}`);
28593
28666
  const compressionBudget = contextLimit > 0 ? Math.floor(contextLimit * (Math.min(threshold.percentage, 80) / 100) * historyBudgetPercentage) : null;
28594
28667
  return {
28595
28668
  sessionId,
28669
+ activeProfile: deps.activeProfile ?? null,
28596
28670
  usagePercentage,
28597
28671
  inputTokens,
28598
28672
  systemPromptTokens,
@@ -28825,7 +28899,14 @@ No active Pi session is available.`,
28825
28899
  backlog: getDreamTaskBacklogs(currentDeps.db, currentDeps.projectIdentity, CANONICAL_DREAM_TASKS)
28826
28900
  }, windowGeometry, resolveTailHygieneStatus(getPiChannel1Baseline(sessionId)));
28827
28901
  const details = buildStatusDetails(currentDeps, sessionId);
28828
- sendCtxStatusMessage(pi, { title: "/ctx-status", text: statusText, level: "info" }, details);
28902
+ const profileStatus = currentDeps.activeProfile ?? "none";
28903
+ sendCtxStatusMessage(pi, {
28904
+ title: "/ctx-status",
28905
+ text: `${statusText}
28906
+
28907
+ Active profile: ${profileStatus}`,
28908
+ level: "info"
28909
+ }, details);
28829
28910
  } catch (error) {
28830
28911
  sendCtxStatusMessage(pi, {
28831
28912
  title: "/ctx-status",
@@ -28849,6 +28930,7 @@ function buildStatusDetails(deps, sessionId) {
28849
28930
  return {
28850
28931
  sessionId,
28851
28932
  projectIdentity: deps.projectIdentity,
28933
+ activeProfile: deps.activeProfile ?? null,
28852
28934
  activeTags: activeTags.length,
28853
28935
  droppedTags: droppedTags.length,
28854
28936
  totalBytes,
@@ -29627,12 +29709,13 @@ function resolveHistorianFromConfig(config) {
29627
29709
  const historian = config.historian;
29628
29710
  if (historian?.disable === true)
29629
29711
  return;
29630
- const model = historian?.model?.trim();
29631
- if (!model || model.length === 0)
29712
+ const resolved = resolveHistorianModel(config, "pi");
29713
+ const model = resolved.primary?.model;
29714
+ if (!model)
29632
29715
  return;
29633
29716
  const historianContextLimit = resolveHistorianContextLimit(model);
29634
29717
  const historianChunkTokens = deriveHistorianChunkTokens(historianContextLimit);
29635
- const fallbackModels = resolveFallbackChain(historian?.fallback_models);
29718
+ const fallbackModels = resolved.fallbacks;
29636
29719
  return {
29637
29720
  runner: new PiSubagentRunner,
29638
29721
  model,
@@ -29640,7 +29723,7 @@ function resolveHistorianFromConfig(config) {
29640
29723
  historianChunkTokens,
29641
29724
  timeoutMs: config.historian_timeout_ms,
29642
29725
  twoPass: historian?.two_pass === true,
29643
- thinkingLevel: historian?.thinking_level,
29726
+ thinkingLevel: resolved.primary?.qualifier,
29644
29727
  executeThresholdPercentage: config.execute_threshold_percentage,
29645
29728
  executeThresholdTokens: config.execute_threshold_tokens,
29646
29729
  commitClusterTrigger: config.commit_cluster_trigger,
@@ -29966,6 +30049,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29966
30049
  runnable: bootProjectDeps.dreamerEnabled,
29967
30050
  scheduleSummary: summarizeDreamSchedule(bootProjectDeps.config.dreamer)
29968
30051
  },
30052
+ activeProfile: bootProjectDeps.config.profile,
29969
30053
  resolveStatusDeps: (ctx) => {
29970
30054
  const current = resolveCurrentProjectDeps(ctx);
29971
30055
  return {
@@ -29980,7 +30064,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29980
30064
  dreamer: {
29981
30065
  runnable: current.dreamerEnabled,
29982
30066
  scheduleSummary: summarizeDreamSchedule(current.config.dreamer)
29983
- }
30067
+ },
30068
+ activeProfile: current.config.profile
29984
30069
  };
29985
30070
  }
29986
30071
  });
@@ -30130,6 +30215,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
30130
30215
  embeddingConfig: bootProjectDeps.config.embedding,
30131
30216
  memoryEnabled: bootProjectDeps.config.memory.enabled,
30132
30217
  retinaHandoff: bootProjectDeps.config.smart_notes.retina_handoff,
30218
+ mural: bootProjectDeps.config.mural,
30133
30219
  language: bootProjectDeps.config.language,
30134
30220
  gitCommitIndexing: bootProjectDeps.config.memory.git_commit_indexing,
30135
30221
  onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject