@cortexkit/pi-magic-context 0.38.1 → 0.39.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
@@ -134,6 +134,7 @@ import {
134
134
  getAllStatusTagTokenTotalsFlat,
135
135
  getAutoSearchHintDecisions,
136
136
  getCachedAbsoluteMessageCount,
137
+ getChannel1NudgeState,
137
138
  getChannel2NudgeClaim,
138
139
  getChannel2NudgeState,
139
140
  getCompactionModeRecord,
@@ -149,7 +150,6 @@ import {
149
150
  getLastCompartmentEndMessage,
150
151
  getLastCompartmentEndMessageId,
151
152
  getLastIndexedOrdinal,
152
- getLastNudgeLevel,
153
153
  getLastNudgeUndropped,
154
154
  getLatestHistorianInvocationId,
155
155
  getLegacyProtectedTailStartOrdinal,
@@ -343,11 +343,11 @@ import {
343
343
  saveRecompStagingPass,
344
344
  saveSourceContent,
345
345
  scheduleAfterBootQuiet,
346
+ setChannel1NudgeState,
346
347
  setChannel2NudgeState,
347
348
  setCompactionModeRecord,
348
349
  setDeferredExecutePendingIfAbsent,
349
350
  setEmergencyDropSample,
350
- setLastNudgeLevel,
351
351
  setLastNudgeUndropped,
352
352
  setMemoryClassification,
353
353
  setMuralCue,
@@ -361,6 +361,7 @@ import {
361
361
  setSqlitePragmaConfig,
362
362
  setStoragePrivatePermissionEnforcement,
363
363
  setTodoSnapshot,
364
+ shouldUseStickyChannel1Reminder,
364
365
  sourceNameForMemory,
365
366
  stripChannel1ReminderSpans,
366
367
  stripMemoryMuralBlock,
@@ -395,7 +396,7 @@ import {
395
396
  withMigrationLanguageDirective,
396
397
  withRawMessageProvider,
397
398
  withRawSessionMessageCache
398
- } from "./index-8e0zezh7.js";
399
+ } from "./index-qqgk7jmy.js";
399
400
  import {
400
401
  pushNotification
401
402
  } from "./index-hw34xmzk.js";
@@ -417,49 +418,104 @@ import {
417
418
  import { createRequire as createRequire3 } from "node:module";
418
419
  import { join as join5, resolve as resolve2 } from "node:path";
419
420
 
420
- // ../plugin/src/shared/resolve-fallbacks.ts
421
- function resolveFallbackChain(userFallbacks) {
422
- const userList = normalizeUserFallbacks(userFallbacks);
423
- return dedupe(userList.filter(isValidModelSpec));
421
+ // ../plugin/src/shared/model-resolution.ts
422
+ function asRecord(value) {
423
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
424
424
  }
425
- function normalizeUserFallbacks(userFallbacks) {
426
- if (!userFallbacks)
427
- return [];
428
- if (typeof userFallbacks === "string") {
429
- const trimmed = userFallbacks.trim();
430
- return trimmed ? [trimmed] : [];
425
+ function readString(value) {
426
+ if (typeof value !== "string")
427
+ return;
428
+ const trimmed = value.trim();
429
+ return trimmed.length > 0 ? trimmed : undefined;
430
+ }
431
+ function hasOwn(record, key) {
432
+ return Object.hasOwn(record, key);
433
+ }
434
+ function normalizeModelEntry(entry, harness) {
435
+ if (typeof entry === "string") {
436
+ const model2 = readString(entry);
437
+ return model2 ? { model: model2 } : undefined;
431
438
  }
432
- return userFallbacks.map((s) => s.trim()).filter((s) => s.length > 0);
439
+ const objectEntry = asRecord(entry);
440
+ if (!objectEntry)
441
+ return;
442
+ const model = readString(objectEntry.model);
443
+ if (!model)
444
+ return;
445
+ const qualifier = readString(objectEntry[harness === "opencode" ? "variant" : "thinking_level"]);
446
+ return qualifier ? { model, qualifier } : { model };
433
447
  }
434
- function isValidModelSpec(spec) {
435
- const slash = spec.indexOf("/");
436
- return slash > 0 && slash < spec.length - 1;
448
+ function isValidModelReference(entry) {
449
+ const slash = entry.model.indexOf("/");
450
+ return slash > 0 && slash < entry.model.length - 1;
437
451
  }
438
- function dedupe(list) {
439
- const seen = new Set;
440
- const out = [];
441
- for (const item of list) {
442
- if (seen.has(item))
452
+ function sameAttempt(a, b) {
453
+ return a.model === b.model && a.qualifier === b.qualifier;
454
+ }
455
+ function resolveFallbackEntries(value, harness) {
456
+ const values = Array.isArray(value) ? value : value === undefined ? [] : [value];
457
+ const resolved = [];
458
+ for (const value2 of values) {
459
+ const entry = normalizeModelEntry(value2, harness);
460
+ if (!entry || !isValidModelReference(entry))
443
461
  continue;
444
- seen.add(item);
445
- out.push(item);
462
+ if (resolved.some((candidate) => sameAttempt(candidate, entry)))
463
+ continue;
464
+ resolved.push(entry);
446
465
  }
447
- return out;
466
+ return resolved;
448
467
  }
449
- function parseProviderModel(spec) {
450
- const slash = spec.indexOf("/");
451
- if (slash < 1 || slash >= spec.length - 1)
452
- return null;
468
+ function resolvePrimaryEntry(args) {
469
+ const entry = normalizeModelEntry(args.entry, args.harness);
470
+ if (!entry)
471
+ return;
472
+ const qualifier = entry.qualifier ?? readString(args.defaultQualifier);
473
+ return qualifier ? { ...entry, qualifier } : entry;
474
+ }
475
+ function resolveHistorianModel(config, harness) {
476
+ const historian = asRecord(asRecord(config)?.historian);
477
+ const block = asRecord(historian?.[harness]);
478
+ if (!block)
479
+ return { fallbacks: [] };
453
480
  return {
454
- providerID: spec.slice(0, slash).trim(),
455
- modelID: spec.slice(slash + 1).trim()
481
+ primary: resolvePrimaryEntry({
482
+ entry: block.model,
483
+ defaultQualifier: block[harness === "opencode" ? "variant" : "thinking_level"],
484
+ harness
485
+ }),
486
+ fallbacks: resolveFallbackEntries(block.fallback_models, harness)
456
487
  };
457
488
  }
458
- function modelBodyField(spec) {
459
- if (!spec)
460
- return {};
461
- const parsed = parseProviderModel(spec);
462
- return parsed ? { model: parsed } : {};
489
+ function resolveDreamerTaskModel(args) {
490
+ const dreamer = asRecord(asRecord(args.config)?.dreamer);
491
+ const harnessBlock = asRecord(dreamer?.[args.harness]);
492
+ const taskBlock = asRecord(asRecord(harnessBlock?.tasks)?.[args.task]);
493
+ const schedulingTask = asRecord(asRecord(dreamer?.tasks)?.[args.task]);
494
+ if (!harnessBlock) {
495
+ return {
496
+ fallbacks: [],
497
+ schedule: readString(schedulingTask?.schedule),
498
+ timeoutMinutes: typeof taskBlock?.timeout_minutes === "number" ? taskBlock.timeout_minutes : undefined,
499
+ promotionThreshold: typeof schedulingTask?.promotion_threshold === "number" ? schedulingTask.promotion_threshold : undefined
500
+ };
501
+ }
502
+ const taskQualifier = taskBlock?.[args.harness === "opencode" ? "variant" : "thinking_level"];
503
+ const harnessQualifier = harnessBlock[args.harness === "opencode" ? "variant" : "thinking_level"] ?? normalizeModelEntry(harnessBlock.model, args.harness)?.qualifier;
504
+ const taskHasModel = taskBlock !== undefined && hasOwn(taskBlock, "model");
505
+ const usesMuralModel = !taskHasModel && args.task === "compress-cues" && Boolean(readString(args.muralModel));
506
+ const primarySource = taskHasModel ? taskBlock?.model : usesMuralModel ? args.muralModel : harnessBlock.model;
507
+ const fallbackSource = taskBlock !== undefined && hasOwn(taskBlock, "fallback_models") ? taskBlock.fallback_models : harnessBlock.fallback_models;
508
+ return {
509
+ primary: resolvePrimaryEntry({
510
+ entry: primarySource,
511
+ defaultQualifier: usesMuralModel ? harnessQualifier : taskQualifier ?? harnessQualifier,
512
+ harness: args.harness
513
+ }),
514
+ fallbacks: resolveFallbackEntries(fallbackSource, args.harness),
515
+ schedule: readString(schedulingTask?.schedule),
516
+ timeoutMinutes: typeof taskBlock?.timeout_minutes === "number" ? taskBlock.timeout_minutes : undefined,
517
+ promotionThreshold: typeof schedulingTask?.promotion_threshold === "number" ? schedulingTask.promotion_threshold : undefined
518
+ };
463
519
  }
464
520
 
465
521
  // ../plugin/src/features/magic-context/dreamer/task-registry.ts
@@ -528,25 +584,23 @@ function compareTaskOrder(a, b) {
528
584
  }
529
585
 
530
586
  // ../plugin/src/features/magic-context/dreamer/task-config.ts
531
- function buildDreamTaskRuntimeConfigs(dreamer, language) {
532
- const tasks = dreamer.tasks ?? {};
587
+ function buildDreamTaskRuntimeConfigs(dreamer, harness, language, muralModel) {
533
588
  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;
589
+ const resolved = resolveDreamerTaskModel({
590
+ config: { dreamer },
591
+ harness,
592
+ task,
593
+ muralModel
594
+ });
541
595
  return {
542
596
  task,
543
- schedule: t.schedule,
544
- model,
545
- fallbackModels,
546
- thinkingLevel,
597
+ schedule: resolved.schedule ?? "",
598
+ model: resolved.primary,
599
+ fallbackModels: resolved.fallbacks,
600
+ thinkingLevel: harness === "pi" ? resolved.primary?.qualifier : undefined,
547
601
  language,
548
- timeoutMinutes: t.timeout_minutes ?? 20,
549
- promotionThreshold: t.promotion_threshold
602
+ timeoutMinutes: resolved.timeoutMinutes ?? 20,
603
+ promotionThreshold: resolved.promotionThreshold
550
604
  };
551
605
  });
552
606
  }
@@ -1951,6 +2005,59 @@ function shouldKeepSubagents() {
1951
2005
  return keepSubagents;
1952
2006
  }
1953
2007
 
2008
+ // ../plugin/src/shared/resolve-fallbacks.ts
2009
+ function resolveFallbackChain(userFallbacks) {
2010
+ const userList = normalizeUserFallbacks(userFallbacks);
2011
+ return dedupe(userList.filter(isValidModelSpec));
2012
+ }
2013
+ function normalizeUserFallbacks(userFallbacks) {
2014
+ if (!userFallbacks)
2015
+ return [];
2016
+ if (typeof userFallbacks === "string") {
2017
+ const trimmed = userFallbacks.trim();
2018
+ return trimmed ? [trimmed] : [];
2019
+ }
2020
+ return userFallbacks.map((s) => s.trim()).filter((s) => s.length > 0);
2021
+ }
2022
+ function isValidModelSpec(spec) {
2023
+ const slash = spec.indexOf("/");
2024
+ return slash > 0 && slash < spec.length - 1;
2025
+ }
2026
+ function dedupe(list) {
2027
+ const seen = new Set;
2028
+ const out = [];
2029
+ for (const item of list) {
2030
+ if (seen.has(item))
2031
+ continue;
2032
+ seen.add(item);
2033
+ out.push(item);
2034
+ }
2035
+ return out;
2036
+ }
2037
+ function parseProviderModel(spec) {
2038
+ const slash = spec.indexOf("/");
2039
+ if (slash < 1 || slash >= spec.length - 1)
2040
+ return null;
2041
+ return {
2042
+ providerID: spec.slice(0, slash).trim(),
2043
+ modelID: spec.slice(slash + 1).trim()
2044
+ };
2045
+ }
2046
+ function modelBodyField(spec) {
2047
+ const entry = toModelEntry(spec);
2048
+ if (!entry)
2049
+ return {};
2050
+ const parsed = parseProviderModel(entry.model);
2051
+ return parsed ? { model: parsed, ...entry.qualifier ? { variant: entry.qualifier } : {} } : {};
2052
+ }
2053
+ function toModelEntry(spec) {
2054
+ if (typeof spec === "string") {
2055
+ const model = spec.trim();
2056
+ return model ? { model } : undefined;
2057
+ }
2058
+ return spec;
2059
+ }
2060
+
1954
2061
  // src/clone-inheritance.ts
1955
2062
  import { readFile } from "node:fs/promises";
1956
2063
  var CONTENT_ID_SUFFIX = /:(?:p|file)\d+$/;
@@ -2530,14 +2637,35 @@ class PiSubagentRunner {
2530
2637
  return { result: isolatedResult, extensionRetryUsed: true };
2531
2638
  }
2532
2639
  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];
2640
+ const attempts = [];
2641
+ const seenAttempts = new Set;
2642
+ const appendAttempt = (candidate) => {
2643
+ if (!candidate.model)
2644
+ return;
2645
+ const key = `${candidate.model}\x00${candidate.qualifier ?? ""}`;
2646
+ if (seenAttempts.has(key))
2647
+ return;
2648
+ seenAttempts.add(key);
2649
+ attempts.push(candidate);
2650
+ };
2651
+ if (options.model) {
2652
+ appendAttempt({
2653
+ model: options.model,
2654
+ ...options.thinkingLevel ? { qualifier: options.thinkingLevel } : {}
2655
+ });
2656
+ }
2657
+ for (const candidate of options.fallbackModels ?? []) {
2658
+ appendAttempt(typeof candidate === "string" ? { model: candidate } : candidate);
2659
+ }
2660
+ if (attempts.length === 0)
2661
+ attempts.push(undefined);
2535
2662
  let lastResult = null;
2536
2663
  for (let index = 0;index < attempts.length; index += 1) {
2537
- const model = attempts[index];
2664
+ const attempt = attempts[index];
2538
2665
  const attemptOptions = {
2539
2666
  ...options,
2540
- model,
2667
+ model: attempt?.model,
2668
+ thinkingLevel: attempt?.qualifier,
2541
2669
  fallbackModels: undefined
2542
2670
  };
2543
2671
  const result = await this.runOnce(attemptOptions, runMode, index === 0 ? primaryModelRef : undefined);
@@ -4091,15 +4219,18 @@ async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
4091
4219
  log(`[${callContext}] primary (${explicitPrimaryLabel}) failed: ${shortErr(error)}; trying ${fallbacks.length} fallback(s)`);
4092
4220
  }
4093
4221
  for (let i = 0;i < fallbacks.length; i += 1) {
4094
- const parsed = parseProviderModel(fallbacks[i]);
4222
+ const fallback = toModelEntry(fallbacks[i]);
4223
+ const parsed = fallback ? parseProviderModel(fallback.model) : null;
4095
4224
  if (!parsed) {
4096
- log(`[${callContext}] skipping invalid fallback spec: ${fallbacks[i]}`);
4225
+ log(`[${callContext}] skipping invalid fallback spec: ${String(fallbacks[i])}`);
4097
4226
  continue;
4098
4227
  }
4099
4228
  const label = `${parsed.providerID}/${parsed.modelID}`;
4229
+ const { variant: _primaryVariant, ...bodyWithoutPrimaryVariant } = baseBody;
4100
4230
  const attemptArgs = copyPromptArgs(baseArgs, {
4101
- ...baseBody,
4102
- model: parsed
4231
+ ...bodyWithoutPrimaryVariant,
4232
+ model: parsed,
4233
+ ...fallback?.qualifier ? { variant: fallback.qualifier } : {}
4103
4234
  });
4104
4235
  try {
4105
4236
  await attemptOnce(client, attemptArgs, timeoutMs, options.signal, callContext, label);
@@ -4115,7 +4246,7 @@ async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
4115
4246
  }
4116
4247
  }
4117
4248
  }
4118
- log(`[${callContext}] all models exhausted; tried: ${[explicitPrimaryLabel, ...fallbacks].join(", ")}; last error: ${shortErr(lastError)}`);
4249
+ log(`[${callContext}] all models exhausted; tried: ${[explicitPrimaryLabel, ...fallbacks.map((fallback) => toModelEntry(fallback)?.model ?? String(fallback))].join(", ")}; last error: ${shortErr(lastError)}`);
4119
4250
  throw lastError ?? new Error("All fallback models failed");
4120
4251
  }
4121
4252
  async function attemptAndValidate(client, args, timeoutMs, signal, callContext, attempt, options) {
@@ -4153,15 +4284,18 @@ async function promptSyncWithValidatedOutputRetry(client, args, options) {
4153
4284
  log(`[${callContext}] primary (${explicitPrimaryLabel}) failed validation/prompt: ${shortErr(error)}; trying ${fallbacks.length} fallback(s)`);
4154
4285
  }
4155
4286
  for (let i = 0;i < fallbacks.length; i += 1) {
4156
- const parsed = parseProviderModel(fallbacks[i]);
4287
+ const fallback = toModelEntry(fallbacks[i]);
4288
+ const parsed = fallback ? parseProviderModel(fallback.model) : null;
4157
4289
  if (!parsed) {
4158
- log(`[${callContext}] skipping invalid fallback spec: ${fallbacks[i]}`);
4290
+ log(`[${callContext}] skipping invalid fallback spec: ${String(fallbacks[i])}`);
4159
4291
  continue;
4160
4292
  }
4161
4293
  const label = `${parsed.providerID}/${parsed.modelID}`;
4294
+ const { variant: _primaryVariant, ...bodyWithoutPrimaryVariant } = baseBody;
4162
4295
  const attemptArgs = copyPromptArgs(baseArgs, {
4163
- ...baseBody,
4164
- model: parsed
4296
+ ...bodyWithoutPrimaryVariant,
4297
+ model: parsed,
4298
+ ...fallback?.qualifier ? { variant: fallback.qualifier } : {}
4165
4299
  });
4166
4300
  const attempt = {
4167
4301
  label,
@@ -4186,7 +4320,7 @@ async function promptSyncWithValidatedOutputRetry(client, args, options) {
4186
4320
  }
4187
4321
  }
4188
4322
  }
4189
- log(`[${callContext}] all models exhausted; tried: ${[explicitPrimaryLabel, ...fallbacks].join(", ")}; original error: ${shortErr(firstError)}; last error: ${shortErr(lastError)}`);
4323
+ 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
4324
  throw firstError ?? lastError ?? new Error("All fallback models failed validation");
4191
4325
  }
4192
4326
  // ../plugin/src/shared/normalize-sdk-response.ts
@@ -6889,15 +7023,60 @@ function getAsyncModule() {
6889
7023
  })();
6890
7024
  return asyncModulePromise;
6891
7025
  }
7026
+ function acquireSandboxModule(signal) {
7027
+ const modulePromise = getAsyncModule();
7028
+ if (!signal)
7029
+ return modulePromise;
7030
+ if (signal.aborted) {
7031
+ return Promise.reject(signal.reason ?? new Error("smart-note check aborted"));
7032
+ }
7033
+ return new Promise((resolve, reject) => {
7034
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
7035
+ const onAbort = () => {
7036
+ cleanup();
7037
+ reject(signal.reason ?? new Error("smart-note check aborted"));
7038
+ };
7039
+ signal.addEventListener("abort", onAbort, { once: true });
7040
+ modulePromise.then((module) => {
7041
+ cleanup();
7042
+ resolve(module);
7043
+ }, (error) => {
7044
+ cleanup();
7045
+ reject(error);
7046
+ });
7047
+ });
7048
+ }
6892
7049
  var sandboxRunChain = Promise.resolve();
6893
- function withSandboxLock(fn) {
6894
- const run = sandboxRunChain.then(fn, fn);
7050
+ function withSandboxLock(fn, signal, cancelled) {
7051
+ const start = () => signal?.aborted && cancelled ? cancelled() : fn();
7052
+ const run = sandboxRunChain.then(start, start);
6895
7053
  sandboxRunChain = run.then(() => {
6896
7054
  return;
6897
7055
  }, () => {
6898
7056
  return;
6899
7057
  });
6900
- return run;
7058
+ return resolveBeforeAbort(run, signal, cancelled);
7059
+ }
7060
+ function resolveBeforeAbort(run, signal, cancelled) {
7061
+ if (!signal || !cancelled)
7062
+ return run;
7063
+ if (signal.aborted)
7064
+ return Promise.resolve(cancelled());
7065
+ return new Promise((resolve, reject) => {
7066
+ const cleanup = () => signal.removeEventListener("abort", abort);
7067
+ const abort = () => {
7068
+ cleanup();
7069
+ resolve(cancelled());
7070
+ };
7071
+ signal.addEventListener("abort", abort, { once: true });
7072
+ run.then((result) => {
7073
+ cleanup();
7074
+ resolve(result);
7075
+ }, (error) => {
7076
+ cleanup();
7077
+ reject(error);
7078
+ });
7079
+ });
6901
7080
  }
6902
7081
  var DEFAULT_TIMEOUT_MS = 2000;
6903
7082
  var DEFAULT_HEAP_LIMIT_BYTES = 8 * 1024 * 1024;
@@ -6924,9 +7103,17 @@ async function runCompiledSmartNoteCheck(options) {
6924
7103
  if (Buffer.byteLength(options.compiledCheck, "utf8") > MAX_COMPILED_CHECK_BYTES) {
6925
7104
  return failureResult("compiled check exceeds 64 KiB", false);
6926
7105
  }
6927
- return withSandboxLock(() => runCompiledSmartNoteCheckLocked(options));
7106
+ let quickjs;
7107
+ try {
7108
+ quickjs = await acquireSandboxModule(options.signal);
7109
+ } catch (error) {
7110
+ if (options.signal?.aborted)
7111
+ return cancelledResult(options.signal.reason);
7112
+ return failureResult(formatSandboxError(error), false);
7113
+ }
7114
+ return withSandboxLock(() => runCompiledSmartNoteCheckLocked(options, quickjs), options.signal, () => cancelledResult(options.signal?.reason));
6928
7115
  }
6929
- async function runCompiledSmartNoteCheckLocked(options) {
7116
+ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
6930
7117
  if (options.signal?.aborted)
6931
7118
  return cancelledResult(options.signal.reason);
6932
7119
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
@@ -6946,8 +7133,6 @@ async function runCompiledSmartNoteCheckLocked(options) {
6946
7133
  throwIfRunAborted(controller.signal);
6947
7134
  const capabilities = resolveCapabilitiesForRun(options, controller.signal);
6948
7135
  const deadline = Date.now() + timeoutMs;
6949
- const quickjs = await getAsyncModule();
6950
- throwIfRunAborted(controller.signal);
6951
7136
  const context = quickjs.newContext();
6952
7137
  try {
6953
7138
  context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
@@ -7552,7 +7737,7 @@ async function evaluateSmartNotes(args) {
7552
7737
  projectIdentity: args.projectIdentity,
7553
7738
  projectRoot,
7554
7739
  maxChecks: 10,
7555
- sweepBudgetMs: 1e4,
7740
+ sweepBudgetMs: args.sweepBudgetMs ?? 1e4,
7556
7741
  leaseHeld,
7557
7742
  signal: leaseAbortController.signal,
7558
7743
  retinaHandoff: args.retinaHandoff
@@ -8077,19 +8262,26 @@ function parseMapMemoriesManifest(text) {
8077
8262
  if (out.length === 0 && body.trim().length > 0) {
8078
8263
  throw new Error(describeUnrecognizedManifestShape(text, "mappings", "memory"));
8079
8264
  }
8080
- assertNoDuplicateManifestIds(out.map((entry) => entry.id), "mappings");
8081
8265
  return out;
8082
8266
  }
8083
8267
  function validateMapMemoriesManifest(text, expectedIds) {
8084
8268
  const parsed = parseMapMemoriesManifest(text);
8085
8269
  assertParsedManifestNonEmpty(parsed.length, expectedIds.size, text, "mappings", "memory");
8086
- assertManifestCoversExactly(parsed.map((entry) => entry.id), expectedIds, "mappings");
8270
+ assertNoDuplicateManifestIds(parsed.filter((entry) => expectedIds.has(entry.id)).map((entry) => entry.id), "mappings");
8087
8271
  return parsed;
8088
8272
  }
8089
8273
 
8090
8274
  // ../plugin/src/features/magic-context/dreamer/map-memories.ts
8091
8275
  var MAP_BATCH_SIZE = 80;
8276
+ var MAP_BATCH_FLOOR_MS = 240000;
8277
+ var CONSECUTIVE_TIMEOUT_LIMIT2 = 2;
8092
8278
  var MAX_INDEPENDENT_REQUEUE_PER_RUN = MAP_BATCH_SIZE;
8279
+ function computeMapBatchSliceMs(remainingMs, batchesRemaining) {
8280
+ return Math.min(remainingMs, Math.max(MAP_BATCH_FLOOR_MS, Math.floor(remainingMs / batchesRemaining)));
8281
+ }
8282
+ function isTimeoutClassError2(error) {
8283
+ return error instanceof Error && /^prompt timed out after \d+ms$/.test(error.message);
8284
+ }
8093
8285
  function shouldRequeueIndependentMapping(state2, content, repoDir) {
8094
8286
  if (!state2.hasSentinel || state2.files.length > 0)
8095
8287
  return false;
@@ -8137,27 +8329,64 @@ async function mapMemories(args) {
8137
8329
  return result;
8138
8330
  const batches = [];
8139
8331
  for (let i = 0;i < inputs.length; i += MAP_BATCH_SIZE) {
8140
- batches.push(inputs.slice(i, i + MAP_BATCH_SIZE));
8332
+ batches.push({ inputs: inputs.slice(i, i + MAP_BATCH_SIZE), isOmissionRetry: false });
8141
8333
  }
8142
8334
  result.remaining = inputs.length;
8143
8335
  const abortController = new AbortController;
8144
8336
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => abortController.abort(), args.leaseAcquisition);
8145
8337
  try {
8338
+ let consecutiveTimeouts = 0;
8339
+ let timeoutStreakElapsedMs = [];
8146
8340
  for (let i = 0;i < batches.length; i += 1) {
8147
8341
  const remainingMs = Math.max(0, args.deadline - Date.now());
8148
- if (remainingMs <= 0)
8342
+ if (remainingMs <= 0) {
8343
+ result.stopReason = "deadline";
8149
8344
  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);
8345
+ }
8346
+ if (remainingMs < MAP_BATCH_FLOOR_MS) {
8347
+ result.stopReason = "deadline";
8348
+ 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)`);
8349
+ break;
8350
+ }
8351
+ const sliceMs = computeMapBatchSliceMs(remainingMs, batches.length - i);
8352
+ const batch = batches[i];
8353
+ if (!batch)
8354
+ break;
8355
+ const outcome = await mapOneBatch(args, batch.inputs, sliceMs, abortController.signal);
8356
+ const committed = outcome.mapped + outcome.independent;
8357
+ result.mapped += outcome.mapped;
8358
+ result.independent += outcome.independent;
8359
+ result.remaining -= committed;
8360
+ if (committed > 0) {
8361
+ result.batches += 1;
8362
+ args.onProgress?.(result.mapped + result.independent);
8363
+ }
8364
+ if (outcome.requeue?.length) {
8365
+ if (!batch.isOmissionRetry) {
8366
+ batches.splice(i + 1, 0, {
8367
+ inputs: outcome.requeue,
8368
+ isOmissionRetry: true
8369
+ });
8370
+ log(`[dreamer] map-memories: committed ${committed}/${batch.inputs.length} mapping(s); requeueing ${outcome.requeue.length} omitted id(s) in a targeted retry`);
8371
+ } else {
8372
+ log(`[dreamer] map-memories: targeted retry still omitted ${outcome.requeue.length} id(s); leaving them unmapped for the next run`);
8373
+ }
8374
+ }
8375
+ if (outcome.failure?.class === "timeout") {
8376
+ consecutiveTimeouts += 1;
8377
+ timeoutStreakElapsedMs.push(outcome.failure.elapsedMs);
8378
+ if (consecutiveTimeouts >= CONSECUTIVE_TIMEOUT_LIMIT2) {
8379
+ result.stopReason = "timeout-circuit-breaker";
8380
+ 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`);
8381
+ break;
8382
+ }
8383
+ } else {
8384
+ consecutiveTimeouts = 0;
8385
+ timeoutStreakElapsedMs = [];
8386
+ }
8158
8387
  }
8159
8388
  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}`);
8389
+ 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
8390
  return result;
8162
8391
  } finally {
8163
8392
  heartbeat.stop();
@@ -8211,12 +8440,16 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
8211
8440
  const text = extractLatestAssistantText(messages);
8212
8441
  if (!text)
8213
8442
  throw new Error("map-memories returned no output");
8214
- validateMapMemoriesManifest(text, new Set(batch.map((memory) => memory.id)));
8215
- return text;
8443
+ return validateMapMemoriesManifest(text, new Set(batch.map((memory) => memory.id)));
8216
8444
  }
8217
8445
  });
8218
8446
  recordInvocation2(args, startedAt, { status: "completed", messages: run.output });
8219
- return await applyBatchMappings(args, batch, run.validated);
8447
+ const outcome = await applyParsedBatchMappings(args, batch, run.validated);
8448
+ const returnedIds = new Set(run.validated.filter((entry) => batch.some((memory) => memory.id === entry.id)).map((entry) => entry.id));
8449
+ return {
8450
+ ...outcome,
8451
+ requeue: batch.filter((memory) => !returnedIds.has(memory.id))
8452
+ };
8220
8453
  } catch (error) {
8221
8454
  const desc = describeError(error);
8222
8455
  log(`[dreamer] map-memories batch failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
@@ -8225,7 +8458,14 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
8225
8458
  throw error;
8226
8459
  if (signal.aborted)
8227
8460
  throw error;
8228
- return { mapped: 0, independent: 0 };
8461
+ return {
8462
+ mapped: 0,
8463
+ independent: 0,
8464
+ failure: {
8465
+ class: isTimeoutClassError2(error) ? "timeout" : "other",
8466
+ elapsedMs: Date.now() - startedAt
8467
+ }
8468
+ };
8229
8469
  } finally {
8230
8470
  if (agentSessionId && !shouldKeepSubagents()) {
8231
8471
  await args.client.session.delete({
@@ -8237,14 +8477,21 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
8237
8477
  }
8238
8478
  }
8239
8479
  }
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)
8480
+ async function applyParsedBatchMappings(args, batch, parsed) {
8481
+ const batchIds = new Set(batch.map((memory) => memory.id));
8482
+ const valid = parsed.filter((entry) => batchIds.has(entry.id));
8483
+ const unknown = parsed.filter((entry) => !batchIds.has(entry.id));
8484
+ if (unknown.length > 0) {
8485
+ 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(", ")})`);
8486
+ }
8487
+ assertNoDuplicateManifestIds(valid.map((entry) => entry.id), "mappings");
8488
+ if (valid.length * 2 < batch.length) {
8489
+ throw new Error(`mappings manifest covers ${valid.length}/${batch.length} batch ids after filtering unknown entries; rejecting mostly-wrong manifest`);
8490
+ }
8491
+ if (valid.length === 0)
8245
8492
  return { mapped: 0, independent: 0 };
8246
8493
  const planned = [];
8247
- for (const p of parsed) {
8494
+ for (const p of valid) {
8248
8495
  if (p.independent) {
8249
8496
  planned.push({ id: p.id, files: [], independent: true });
8250
8497
  continue;
@@ -9549,14 +9796,13 @@ function parseVerifyManifest(text) {
9549
9796
  if (verifyIds(out).length === 0 && body.trim().length > 0) {
9550
9797
  throw new Error(describeUnrecognizedManifestShape(text, "verify", "verified"));
9551
9798
  }
9552
- assertNoDuplicateManifestIds(verifyIds(out), "verify");
9553
9799
  return out;
9554
9800
  }
9555
9801
  function validateVerifyManifest(text, expectedIds) {
9556
9802
  const parsed = parseVerifyManifest(text);
9557
9803
  const ids = verifyIds(parsed);
9558
9804
  assertParsedManifestNonEmpty(ids.length, expectedIds.size, text, "verify", "verified");
9559
- assertManifestCoversExactly(ids, expectedIds, "verify");
9805
+ assertNoDuplicateManifestIds(ids.filter((id) => expectedIds.has(id)), "verify");
9560
9806
  return parsed;
9561
9807
  }
9562
9808
 
@@ -9709,19 +9955,14 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
9709
9955
  const text = extractLatestAssistantText(messages);
9710
9956
  if (!text)
9711
9957
  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;
9958
+ const providerFailure = providerOutputFailureFromInvalidManifest(messages, text);
9959
+ if (providerFailure)
9960
+ throw providerFailure;
9961
+ return validateVerifyManifest(text, new Set(batch.map((memory) => memory.id)));
9721
9962
  }
9722
9963
  });
9723
9964
  recordInvocation4(args, startedAt, { status: "completed", messages: run.output });
9724
- return await applyVerifyManifest(args, batch, run.validated);
9965
+ return await applyParsedVerifyManifest(args, batch, run.validated);
9725
9966
  } catch (error) {
9726
9967
  const desc = describeError(error);
9727
9968
  const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
@@ -9741,17 +9982,31 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
9741
9982
  }
9742
9983
  }
9743
9984
  }
9744
- async function applyVerifyManifest(args, batch, manifestText) {
9985
+ async function applyParsedVerifyManifest(args, batch, parsed) {
9745
9986
  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");
9987
+ const valid = {
9988
+ verified: parsed.verified.filter((entry) => batchIds.has(entry.id)),
9989
+ updated: parsed.updated.filter((entry) => batchIds.has(entry.id)),
9990
+ archived: parsed.archived.filter((entry) => batchIds.has(entry.id))
9991
+ };
9992
+ const unknown = [...parsed.verified, ...parsed.updated, ...parsed.archived].filter((entry) => !batchIds.has(entry.id));
9993
+ if (unknown.length > 0) {
9994
+ 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(", ")})`);
9995
+ }
9996
+ const validIds = [...valid.verified, ...valid.updated, ...valid.archived].map((entry) => entry.id);
9997
+ assertNoDuplicateManifestIds(validIds, "verify");
9998
+ if (validIds.length * 2 < batch.length) {
9999
+ throw new Error(`verify manifest covers ${validIds.length}/${batch.length} batch ids after filtering unknown entries; rejecting mostly-wrong manifest`);
10000
+ }
10001
+ if (validIds.length === 0)
10002
+ return { verified: 0, updated: 0, archived: 0 };
9748
10003
  const now = Date.now();
9749
10004
  const writes = [];
9750
- for (const v of parsed.verified) {
10005
+ for (const v of valid.verified) {
9751
10006
  const files = await normalizeFiles(args, v.files);
9752
10007
  writes.push({ kind: "verify", id: v.id, files });
9753
10008
  }
9754
- for (const u of parsed.updated) {
10009
+ for (const u of valid.updated) {
9755
10010
  const content = u.content.trim();
9756
10011
  if (!content || content.length > 20000) {
9757
10012
  const files2 = await normalizeFiles(args, u.files);
@@ -9767,7 +10022,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
9767
10022
  hash: computeNormalizedHash(content)
9768
10023
  });
9769
10024
  }
9770
- for (const a of parsed.archived) {
10025
+ for (const a of valid.archived) {
9771
10026
  writes.push({ kind: "archive", id: a.id, reason: a.reason });
9772
10027
  }
9773
10028
  if (writes.length === 0)
@@ -10074,7 +10329,7 @@ function createDreamTaskExecutor(deps) {
10074
10329
  leaseKey,
10075
10330
  deadline,
10076
10331
  leaseAcquisition,
10077
- model: config.model ?? deps.mural.model ?? deps.dreamerModel,
10332
+ model: config.model,
10078
10333
  fallbackModels: config.fallbackModels,
10079
10334
  moduleRoute,
10080
10335
  onProgress: (processed) => reportProgress(processed)
@@ -10123,8 +10378,19 @@ function createDreamTaskExecutor(deps) {
10123
10378
  moduleRoute,
10124
10379
  onProgress: (processed) => reportProgress(processed)
10125
10380
  });
10126
- log(`[dreamer] map-memories: mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining}`);
10381
+ 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
10382
  if (!result.complete) {
10383
+ if (result.stopReason === "timeout-circuit-breaker") {
10384
+ const error2 = `map-memories starvation: timeout circuit breaker stopped the run with ${result.remaining} remain`;
10385
+ recordRun("failed", error2);
10386
+ return { status: "failed", transient: true, error: error2 };
10387
+ }
10388
+ const processed = result.mapped + result.independent;
10389
+ if (processed > 0) {
10390
+ const progress = `map-memories: committed ${processed} mapping(s) (mapped ${result.mapped}, independent ${result.independent}); ${result.remaining} remain`;
10391
+ recordRun("completed", null, { progress });
10392
+ return { status: "completed" };
10393
+ }
10128
10394
  const error = incompleteMessage(result.remaining);
10129
10395
  recordRun("failed", error);
10130
10396
  return { status: "failed", transient: true, error };
@@ -11235,7 +11501,7 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
11235
11501
  }
11236
11502
  try {
11237
11503
  await runCompiledSmartNoteSweep(reg, db);
11238
- const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreamerConfig, reg.language);
11504
+ const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreamerConfig, "opencode", reg.language, reg.mural?.model);
11239
11505
  const executor = createDreamTaskExecutor({
11240
11506
  client: reg.client,
11241
11507
  sessionDirectory: reg.directory,
@@ -11245,7 +11511,6 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
11245
11511
  userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
11246
11512
  ensureProjectRegistered: reg.ensureRegistered,
11247
11513
  language: reg.language,
11248
- dreamerModel: dreamerConfig.model,
11249
11514
  mural: reg.mural,
11250
11515
  memoryInjectionBudgetTokens: reg.memoryInjectionBudgetTokens,
11251
11516
  retinaHandoff: reg.retinaHandoff,
@@ -11631,11 +11896,13 @@ function registerPiDreamerProject(opts) {
11631
11896
  startDreamScheduleTimerFn({
11632
11897
  directory: opts.projectDir,
11633
11898
  projectIdentity: opts.projectIdentity,
11899
+ harness: "pi",
11634
11900
  client,
11635
11901
  dreamerConfig: opts.config,
11636
11902
  language: opts.language,
11637
11903
  gitCommitIndexing: opts.gitCommitIndexing,
11638
11904
  retinaHandoff: opts.retinaHandoff,
11905
+ mural: opts.mural,
11639
11906
  ensureRegistered: ensureProjectRegisteredFromPiDirectory,
11640
11907
  retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({ projectCwd: opts.projectDir }),
11641
11908
  primerRawProviderFactory: createPiPrimerRawProviderFactory()
@@ -11649,7 +11916,7 @@ function registerPiDreamerProject(opts) {
11649
11916
  const runManual = async (task) => runManualDream({
11650
11917
  db: opts.db,
11651
11918
  projectIdentity: opts.projectIdentity,
11652
- tasks: buildDreamTaskRuntimeConfigs(opts.config, opts.language),
11919
+ tasks: buildDreamTaskRuntimeConfigs(opts.config, "pi", opts.language, opts.mural?.model),
11653
11920
  executor: createDreamTaskExecutor({
11654
11921
  client,
11655
11922
  sessionDirectory: opts.projectDir,
@@ -11661,7 +11928,8 @@ function registerPiDreamerProject(opts) {
11661
11928
  userMemoryCollectionEnabled: userMemoryCollectionEnabled(opts.config),
11662
11929
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
11663
11930
  language: opts.language,
11664
- retinaHandoff: opts.retinaHandoff
11931
+ retinaHandoff: opts.retinaHandoff,
11932
+ mural: opts.mural
11665
11933
  }),
11666
11934
  task
11667
11935
  });
@@ -11697,7 +11965,6 @@ async function awaitInFlightDreamers() {
11697
11965
  }
11698
11966
  function createPiDreamerClient(opts) {
11699
11967
  const runner = piSubagentRunnerFactory();
11700
- const model = opts.config.model;
11701
11968
  const session = {
11702
11969
  create: async (args) => {
11703
11970
  const sessionId = `magic-context-pi-dream-${++sessionCounter}`;
@@ -11718,7 +11985,7 @@ function createPiDreamerClient(opts) {
11718
11985
  }
11719
11986
  const userMessage = extractUserMessage(args);
11720
11987
  const systemPrompt = extractSystemPrompt(args);
11721
- const perTaskModel = extractBodyModel(args) ?? model;
11988
+ const perTaskModel = extractBodyModel(args);
11722
11989
  const requestedAgent = extractBodyAgent(args) ?? "magic-context-dreamer";
11723
11990
  const runPromise = runner.run({
11724
11991
  agent: requestedAgent,
@@ -11729,7 +11996,7 @@ function createPiDreamerClient(opts) {
11729
11996
  timeoutMs: 30 * 60 * 1000,
11730
11997
  cwd: dreamSession.directory,
11731
11998
  signal: args.signal ?? undefined,
11732
- thinkingLevel: opts.config.thinking_level
11999
+ thinkingLevel: extractBodyVariant(args)
11733
12000
  });
11734
12001
  inFlightDreams.add(runPromise);
11735
12002
  try {
@@ -11820,6 +12087,13 @@ function extractBodyModel(args) {
11820
12087
  }
11821
12088
  return;
11822
12089
  }
12090
+ function extractBodyVariant(args) {
12091
+ const body = args.body;
12092
+ if (typeof body !== "object" || body === null)
12093
+ return;
12094
+ const variant = body.variant;
12095
+ return typeof variant === "string" && variant.length > 0 ? variant : undefined;
12096
+ }
11823
12097
  function extractBodyAgent(args) {
11824
12098
  const body = args.body;
11825
12099
  if (typeof body !== "object" || body === null)
@@ -12961,12 +13235,15 @@ function writeTransformDecisionRowOnDatabase(db, row, configureBusyTimeout) {
12961
13235
  const systemHashNew = row.materialized ? row.systemHashNew : null;
12962
13236
  const m0ModelKeyPrev = row.materialized ? row.m0ModelKeyPrev : null;
12963
13237
  const m0ModelKeyNew = row.materialized ? row.m0ModelKeyNew : null;
13238
+ const m0ToolSetHashPrev = row.m0ToolSetHashPrev ?? null;
13239
+ const m0ToolSetHashNew = row.m0ToolSetHashNew ?? null;
12964
13240
  db.prepare(`INSERT OR REPLACE INTO transform_decisions (
12965
13241
  session_id, harness, message_id, ts_ms, decision, materialized,
12966
13242
  materialize_reason, system_hash_prev, system_hash_new,
13243
+ m0_tool_set_hash_prev, m0_tool_set_hash_new,
12967
13244
  m0_model_key_prev, m0_model_key_new, emergency, dropped_tokens,
12968
13245
  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)));
13246
+ ) 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
13247
  db.prepare(`DELETE FROM transform_decisions
12971
13248
  WHERE session_id = ? AND harness = ?
12972
13249
  AND rowid NOT IN (
@@ -16022,6 +16299,17 @@ function isSyntheticMessage(message, index, input) {
16022
16299
  const role = typeof message.role === "string" ? message.role : "";
16023
16300
  return role === "system" || role === "custom" || role === "compactionSummary";
16024
16301
  }
16302
+ function countRealPiUserMessages(input) {
16303
+ let count = 0;
16304
+ for (let index = 0;index < input.messages.length; index += 1) {
16305
+ const message = input.messages[index];
16306
+ if (!isRecord3(message) || message.role !== "user")
16307
+ continue;
16308
+ if (!isSyntheticMessage(message, index, input))
16309
+ count += 1;
16310
+ }
16311
+ return count;
16312
+ }
16025
16313
  function imageContentAndTokens(part) {
16026
16314
  const data = typeof part.data === "string" ? part.data : "";
16027
16315
  const mimeType = typeof part.mimeType === "string" ? part.mimeType : "image/png";
@@ -16215,7 +16503,7 @@ function excludedDraft(key, value) {
16215
16503
  tokens: 0
16216
16504
  };
16217
16505
  }
16218
- function finalizeParts(drafts, protectedTags) {
16506
+ function finalizeParts(drafts, protectedTags, pendingDropTagNumbers) {
16219
16507
  const visibleTags = new Map;
16220
16508
  for (const part of drafts) {
16221
16509
  if (part.kind !== "excluded" && part.tag)
@@ -16230,7 +16518,8 @@ function finalizeParts(drafts, protectedTags) {
16230
16518
  const parts = drafts.map((draft) => {
16231
16519
  const protectedPart = draft.tag ? protectedNumbers.has(draft.tag.tagNumber) : false;
16232
16520
  const tokens = Math.max(0, draft.tokens);
16233
- const uTokens = draft.tag && !protectedPart && draft.kind !== "excluded" ? tokens : 0;
16521
+ const queuedForDrop = draft.tag ? pendingDropTagNumbers.has(draft.tag.tagNumber) : false;
16522
+ const uTokens = draft.tag && !protectedPart && !queuedForDrop && draft.kind !== "excluded" ? tokens : 0;
16234
16523
  t += tokens;
16235
16524
  u += uTokens;
16236
16525
  return {
@@ -16241,7 +16530,8 @@ function finalizeParts(drafts, protectedTags) {
16241
16530
  uTokens,
16242
16531
  tagNumber: draft.tag?.tagNumber ?? null,
16243
16532
  tagStatus: draft.tag ? "active" : null,
16244
- protected: protectedPart
16533
+ protected: protectedPart,
16534
+ queuedForDrop
16245
16535
  };
16246
16536
  });
16247
16537
  const clampedT = Math.max(0, t);
@@ -16383,29 +16673,34 @@ function measurePiTailHygiene(input) {
16383
16673
  drafts.push(excludedDraft(`${key}\x00excluded`, part));
16384
16674
  }
16385
16675
  }
16386
- return finalizeParts(drafts, input.protectedTags);
16676
+ return finalizeParts(drafts, input.protectedTags, input.pendingDropTagNumbers ?? new Set);
16387
16677
  }
16388
16678
  function sameMeasuredPrefix(baseline, current) {
16389
16679
  if (current.length < baseline.length)
16390
- return { valid: false, boundaryAdvanceU: 0 };
16680
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16391
16681
  let boundaryAdvanceU = 0;
16682
+ let queuedDropDeltaU = 0;
16392
16683
  for (let index = 0;index < baseline.length; index += 1) {
16393
16684
  const before = baseline[index];
16394
16685
  const after = current[index];
16395
16686
  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 };
16687
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16397
16688
  }
16398
16689
  if (!before.protected && after.protected)
16399
- return { valid: false, boundaryAdvanceU: 0 };
16690
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16400
16691
  if (before.protected && !after.protected) {
16401
16692
  if (after.tagStatus !== "active")
16402
- return { valid: false, boundaryAdvanceU: 0 };
16403
- boundaryAdvanceU += after.tokens;
16693
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16694
+ boundaryAdvanceU += after.uTokens;
16695
+ } else if (before.queuedForDrop !== after.queuedForDrop) {
16696
+ if (before.tagStatus !== "active" || after.tagStatus !== "active")
16697
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16698
+ queuedDropDeltaU += after.uTokens - before.uTokens;
16404
16699
  } else if (before.uTokens !== after.uTokens) {
16405
- return { valid: false, boundaryAdvanceU: 0 };
16700
+ return { valid: false, boundaryAdvanceU: 0, queuedDropDeltaU: 0 };
16406
16701
  }
16407
16702
  }
16408
- return { valid: true, boundaryAdvanceU };
16703
+ return { valid: true, boundaryAdvanceU, queuedDropDeltaU };
16409
16704
  }
16410
16705
  function refreshPiTailHygieneBaseline(input) {
16411
16706
  const measured = measurePiTailHygiene(input);
@@ -16437,7 +16732,7 @@ function refreshPiTailHygieneBaseline(input) {
16437
16732
  };
16438
16733
  }
16439
16734
  let turnDeltaT = 0;
16440
- let turnDeltaU = prefix.boundaryAdvanceU;
16735
+ let turnDeltaU = prefix.boundaryAdvanceU + prefix.queuedDropDeltaU;
16441
16736
  for (let index = input.previous.baselineParts.length;index < measured.parts.length; index += 1) {
16442
16737
  const part = measured.parts[index];
16443
16738
  turnDeltaT += part.tokens;
@@ -16531,20 +16826,38 @@ function maybeChannel1ReminderForToolResult(args) {
16531
16826
  if (deltaTokens === 0)
16532
16827
  return null;
16533
16828
  state2.turnDeltaT += deltaTokens;
16829
+ if (state2.agentDropsAppliedThisPass)
16830
+ return null;
16831
+ const nudgeState = getChannel1NudgeState(db, sessionId);
16534
16832
  const decision = decideChannel1({
16535
16833
  ...state2,
16536
16834
  lastNudgeUndropped: getLastNudgeUndropped(db, sessionId),
16537
- lastNudgeLevel: getLastNudgeLevel(db, sessionId),
16835
+ lastNudgeLevel: nudgeState.level,
16538
16836
  hasRecentReduce: state2.reducedSinceRefresh
16539
16837
  });
16540
16838
  setLastNudgeUndropped(db, sessionId, decision.nextLastNudge);
16541
- setLastNudgeLevel(db, sessionId, decision.nextLastNudgeLevel);
16542
- if (!decision.fire)
16839
+ if (!decision.fire) {
16840
+ setChannel1NudgeState(db, sessionId, {
16841
+ level: decision.nextLastNudgeLevel,
16842
+ ordinal: decision.nextLastNudgeLevel === "" ? 0 : nudgeState.ordinal
16843
+ });
16543
16844
  return null;
16544
- return {
16845
+ }
16846
+ const sticky = shouldUseStickyChannel1Reminder({
16847
+ lastLevel: nudgeState.level,
16848
+ lastOrdinal: nudgeState.ordinal,
16849
+ level: decision.level,
16850
+ currentRealUserTurnCount: state2.realUserTurnCount
16851
+ });
16852
+ const block = {
16545
16853
  type: "text",
16546
- text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state2.oldestReclaimableToolTags)
16854
+ text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state2.usableWindow, state2.oldestReclaimableToolTags, sticky)
16547
16855
  };
16856
+ setChannel1NudgeState(db, sessionId, {
16857
+ level: decision.level,
16858
+ ordinal: state2.realUserTurnCount
16859
+ });
16860
+ return block;
16548
16861
  }
16549
16862
  var CHANNEL2_NUDGE_CUSTOM_TYPE = "magic-context:ceiling-nudge";
16550
16863
  function maybeDeliverChannel2Pi(pi, db, sessionId) {
@@ -16575,7 +16888,7 @@ function maybeDeliverChannel2Pi(pi, db, sessionId) {
16575
16888
  try {
16576
16889
  const message = {
16577
16890
  customType: CHANNEL2_NUDGE_CUSTOM_TYPE,
16578
- content: buildChannel2Reminder(undropped, baseline.oldestReclaimableToolTags),
16891
+ content: buildChannel2Reminder(undropped, baseline.usableWindow, baseline.oldestReclaimableToolTags),
16579
16892
  display: false,
16580
16893
  details: { kind: "channel-2-ceiling-nudge" }
16581
16894
  };
@@ -21017,19 +21330,24 @@ async function runHistorianSubagentWithTransientRetries(args) {
21017
21330
  }
21018
21331
  return historianAbortResult(startedAt);
21019
21332
  }
21020
- function buildHistorianFallbackChain(primaryModel, fallbackModels, fallbackModelId) {
21021
- const seen = new Set;
21022
- if (primaryModel)
21023
- seen.add(primaryModel);
21333
+ function buildHistorianFallbackChain(primaryModel, primaryThinkingLevel, fallbackModels, fallbackModelId) {
21334
+ const seen = new Set([
21335
+ `${primaryModel}\x00${primaryThinkingLevel ?? ""}`
21336
+ ]);
21024
21337
  const chain = [];
21025
21338
  for (const candidate of fallbackModels ?? []) {
21026
- if (!candidate || seen.has(candidate))
21339
+ const entry = typeof candidate === "string" ? { model: candidate } : candidate;
21340
+ const key = `${entry.model}\x00${entry.qualifier ?? ""}`;
21341
+ if (!entry.model || seen.has(key))
21027
21342
  continue;
21028
- seen.add(candidate);
21029
- chain.push({ modelId: candidate, kind: "configured" });
21343
+ seen.add(key);
21344
+ chain.push({ entry, kind: "configured" });
21030
21345
  }
21031
- if (fallbackModelId && !seen.has(fallbackModelId)) {
21032
- chain.push({ modelId: fallbackModelId, kind: "session" });
21346
+ if (fallbackModelId) {
21347
+ const entry = { model: fallbackModelId };
21348
+ const key = `${entry.model}\x00`;
21349
+ if (!seen.has(key))
21350
+ chain.push({ entry, kind: "session" });
21033
21351
  }
21034
21352
  return chain;
21035
21353
  }
@@ -21348,11 +21666,11 @@ ${chunkText}`,
21348
21666
  validatedDraftText = repairResult.assistantText;
21349
21667
  }
21350
21668
  }
21351
- const fallbackChain = buildHistorianFallbackChain(historianModel, fallbackModels, fallbackModelId);
21669
+ const fallbackChain = buildHistorianFallbackChain(historianModel, thinkingLevel, fallbackModels, fallbackModelId);
21352
21670
  if (validatedPass.kind !== "ok" && !(validatedPass.kind === "spawn-failed" && validatedPass.reason === "abort") && fallbackChain.length > 0) {
21353
21671
  for (let i = 0;i < fallbackChain.length; i += 1) {
21354
21672
  const candidate = fallbackChain[i];
21355
- sessionLog(sessionId, `historian: escalating to ${candidate.kind === "session" ? "session-model last resort" : "configured fallback model"} ${candidate.modelId}`);
21673
+ sessionLog(sessionId, `historian: escalating to ${candidate.kind === "session" ? "session-model last resort" : "configured fallback model"} ${candidate.entry.model}`);
21356
21674
  const fbResult = await runHistorianSubagentWithTransientRetries({
21357
21675
  runner,
21358
21676
  sessionId,
@@ -21362,11 +21680,11 @@ ${chunkText}`,
21362
21680
  agent: HISTORIAN_AGENT_NAME,
21363
21681
  systemPrompt: historianSystemPrompt,
21364
21682
  userMessage: prompt,
21365
- model: candidate.modelId,
21683
+ model: candidate.entry.model,
21366
21684
  timeoutMs: historianTimeoutMs,
21367
21685
  cwd: directory,
21368
21686
  signal,
21369
- thinkingLevel,
21687
+ thinkingLevel: candidate.entry.qualifier,
21370
21688
  onProgress: buildProgressLogger("fallback"),
21371
21689
  accountingSessionId: sessionId,
21372
21690
  accountingSubagent: "historian"
@@ -24475,11 +24793,13 @@ function registerPiContextHandler(pi, baseOptions) {
24475
24793
  if (!options.compactionOff && !sessionMetaForCh1.isSubagent) {
24476
24794
  const tags = getTagsBySession(options.db, sessionId);
24477
24795
  const protectedTags = options.protectedTags ?? 20;
24796
+ const pendingDropTagNumbers = new Set(getPendingOps(options.db, sessionId).filter((operation) => operation.operation === "drop").map((operation) => operation.tagId));
24478
24797
  const stableId = (message) => message && typeof message === "object" ? result.postCommitEntryIdByRef.get(message) : undefined;
24479
24798
  const baseline = refreshPiTailHygieneBaseline({
24480
24799
  messages: outputMessages,
24481
24800
  tags,
24482
24801
  protectedTags,
24802
+ pendingDropTagNumbers,
24483
24803
  stableId,
24484
24804
  syntheticLeadingCount: result.syntheticLeadingCount,
24485
24805
  cacheBusting: result.bustedThisPass,
@@ -24490,7 +24810,17 @@ function registerPiContextHandler(pi, baseOptions) {
24490
24810
  const oldestReclaimableToolTags = getOldestActiveUnprotectedToolTags(options.db, sessionId, protectedTags);
24491
24811
  const channelState = {
24492
24812
  ...baseline,
24813
+ usableWindow: usageContextLimit ?? 0,
24814
+ realUserTurnCount: countRealPiUserMessages({
24815
+ messages: outputMessages,
24816
+ tags,
24817
+ protectedTags,
24818
+ pendingDropTagNumbers,
24819
+ stableId,
24820
+ syntheticLeadingCount: result.syntheticLeadingCount
24821
+ }),
24493
24822
  reducedSinceRefresh: false,
24823
+ agentDropsAppliedThisPass: result.agentDropsAppliedThisPass,
24494
24824
  oldestReclaimableToolTags
24495
24825
  };
24496
24826
  setPiChannel1Baseline(sessionId, channelState);
@@ -24515,6 +24845,7 @@ function registerPiContextHandler(pi, baseOptions) {
24515
24845
  messages: outputMessages,
24516
24846
  tags,
24517
24847
  protectedTags,
24848
+ pendingDropTagNumbers,
24518
24849
  stableId,
24519
24850
  syntheticLeadingCount: result.syntheticLeadingCount,
24520
24851
  expectedSignature: baseline.contentSignature
@@ -25044,6 +25375,7 @@ async function runCompactionOffPipeline(args) {
25044
25375
  droppedCount: 0,
25045
25376
  emergency: false,
25046
25377
  bustedThisPass: injectionResult?.m0Materialized === true,
25378
+ agentDropsAppliedThisPass: false,
25047
25379
  targetCount: 0,
25048
25380
  reasoningWatermark: args.sessionMeta.clearedReasoningThroughTag ?? 0,
25049
25381
  activeTags: [],
@@ -25617,6 +25949,7 @@ async function runPipeline(args) {
25617
25949
  droppedCount,
25618
25950
  emergency,
25619
25951
  bustedThisPass,
25952
+ agentDropsAppliedThisPass: pendingOpsDidMutate,
25620
25953
  targetCount: targets.size,
25621
25954
  reasoningWatermark: args.sessionMeta.clearedReasoningThroughTag ?? 0,
25622
25955
  activeTags,
@@ -25909,6 +26242,7 @@ async function runValidatedHistorianPass(args) {
25909
26242
  const firstRun = await runHistorianPrompt({
25910
26243
  ...args,
25911
26244
  dumpLabel: `${args.dumpLabelBase}-initial`,
26245
+ modelOverride: args.model,
25912
26246
  agentId: args.agentId
25913
26247
  });
25914
26248
  if (!firstRun.ok || !firstRun.result) {
@@ -25937,6 +26271,7 @@ async function runValidatedHistorianPass(args) {
25937
26271
  ...args,
25938
26272
  prompt: repairPrompt,
25939
26273
  dumpLabel: `${args.dumpLabelBase}-repair`,
26274
+ modelOverride: args.model,
25940
26275
  agentId: args.agentId
25941
26276
  });
25942
26277
  if (!repairRun.ok || !repairRun.result) {
@@ -25977,7 +26312,8 @@ async function runEditorPassOrFallback(args) {
25977
26312
  timeoutMs: args.timeoutMs,
25978
26313
  dumpLabel: `${args.dumpLabelBase}-editor`,
25979
26314
  agentId: HISTORIAN_EDITOR_AGENT,
25980
- parentInvocationId: args.draftInvocationId ?? null
26315
+ parentInvocationId: args.draftInvocationId ?? null,
26316
+ modelOverride: args.model
25981
26317
  });
25982
26318
  if (!editorRun.ok || !editorRun.result) {
25983
26319
  sessionLog(args.parentSessionId, "historian two-pass: editor call failed", {
@@ -26030,7 +26366,7 @@ async function runHistorianPrompt(args) {
26030
26366
  });
26031
26367
  };
26032
26368
  try {
26033
- sessionLog(parentSessionId, `historian: creating child session (agent=${agentId}, model=${modelOverride ? `${modelOverride.providerID}/${modelOverride.modelID}` : `agent:${agentId}`})`);
26369
+ sessionLog(parentSessionId, `historian: creating child session (agent=${toModelEntry(modelOverride)?.model ?? `agent:${agentId}`})`);
26034
26370
  const createResponse = await createChildSessionWithFence({
26035
26371
  client,
26036
26372
  db,
@@ -26054,7 +26390,7 @@ async function runHistorianPrompt(args) {
26054
26390
  query: { directory: sessionDirectory },
26055
26391
  body: {
26056
26392
  agent: agentId,
26057
- ...modelOverride ? { model: modelOverride } : {},
26393
+ ...modelBodyField(modelOverride),
26058
26394
  parts: [{ type: "text", text: prompt, synthetic: true }]
26059
26395
  }
26060
26396
  }, {
@@ -26124,10 +26460,21 @@ async function runHistorianPrompt(args) {
26124
26460
  async function runFallbackHistorianPass(args) {
26125
26461
  const seen = new Set;
26126
26462
  const chain = [];
26127
- for (const candidate of [...args.fallbackModels ?? [], args.fallbackModelId ?? ""]) {
26128
- if (!candidate || seen.has(candidate))
26463
+ const primary = toModelEntry(args.model);
26464
+ for (const candidateInput of [
26465
+ ...args.fallbackModels ?? [],
26466
+ ...args.fallbackModelId ? [{ model: args.fallbackModelId }] : []
26467
+ ]) {
26468
+ const candidate = toModelEntry(candidateInput);
26469
+ if (!candidate)
26470
+ continue;
26471
+ const key = `${candidate.model}\x00${candidate.qualifier ?? ""}`;
26472
+ if (!candidate.model || seen.has(key))
26473
+ continue;
26474
+ if (primary?.model === candidate.model && primary.qualifier === candidate.qualifier) {
26129
26475
  continue;
26130
- seen.add(candidate);
26476
+ }
26477
+ seen.add(key);
26131
26478
  chain.push(candidate);
26132
26479
  }
26133
26480
  if (chain.length === 0) {
@@ -26135,9 +26482,9 @@ async function runFallbackHistorianPass(args) {
26135
26482
  }
26136
26483
  let lastError = args.error;
26137
26484
  for (let i = 0;i < chain.length; i += 1) {
26138
- const modelId = chain[i];
26139
- const modelOverride = parseModelOverride(modelId);
26140
- if (!modelOverride)
26485
+ const modelOverride = chain[i];
26486
+ const modelId = modelOverride.model;
26487
+ if (!parseModelOverride(modelId))
26141
26488
  continue;
26142
26489
  const isSessionModelLastResort = modelId === args.fallbackModelId && i === chain.length - 1;
26143
26490
  sessionLog(args.parentSessionId, `compartment agent: retrying historian with ${modelId} (${isSessionModelLastResort ? "session-model last resort" : "configured fallback"} ${i + 1}/${chain.length})`);
@@ -26466,6 +26813,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
26466
26813
  sequenceOffset: candidateCompartments.length,
26467
26814
  dumpLabelBase: `recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
26468
26815
  timeoutMs: historianTimeoutMs,
26816
+ model: deps.model,
26469
26817
  fallbackModelId: deps.fallbackModelId,
26470
26818
  fallbackModels: deps.fallbackModels,
26471
26819
  twoPass: deps.historianTwoPass,
@@ -26886,6 +27234,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
26886
27234
  sequenceOffset: candidateCompartments.length,
26887
27235
  dumpLabelBase: `partial-recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
26888
27236
  timeoutMs: historianTimeoutMs,
27237
+ model: deps.model,
26889
27238
  fallbackModelId: deps.fallbackModelId,
26890
27239
  fallbackModels: deps.fallbackModels,
26891
27240
  twoPass: deps.historianTwoPass,
@@ -27077,11 +27426,11 @@ function createPiHistorianClient(args) {
27077
27426
  agent: "magic-context-historian",
27078
27427
  systemPrompt: args.systemPrompt,
27079
27428
  userMessage: promptText,
27080
- model: modelOverride ?? args.model,
27429
+ model: modelOverride?.model ?? args.model,
27081
27430
  fallbackModels: modelOverride ? undefined : args.fallbackModels,
27082
27431
  timeoutMs: args.timeoutMs,
27083
27432
  cwd: args.directory,
27084
- thinkingLevel: args.thinkingLevel,
27433
+ thinkingLevel: modelOverride ? modelOverride.qualifier : args.thinkingLevel,
27085
27434
  accountingSessionId: args.accountingSessionId,
27086
27435
  accountingSubagent: "recomp"
27087
27436
  });
@@ -27129,7 +27478,8 @@ function readBodyModel(body) {
27129
27478
  return;
27130
27479
  const { providerID, modelID } = model;
27131
27480
  if (typeof providerID === "string" && providerID.length > 0 && typeof modelID === "string" && modelID.length > 0) {
27132
- return `${providerID}/${modelID}`;
27481
+ const qualifier = typeof body.variant === "string" && body.variant.length > 0 ? body.variant : undefined;
27482
+ return qualifier ? { model: `${providerID}/${modelID}`, qualifier } : { model: `${providerID}/${modelID}` };
27133
27483
  }
27134
27484
  return;
27135
27485
  }
@@ -27739,13 +28089,17 @@ async function runPiMemoryMigration(deps) {
27739
28089
  const prompt = buildMemoryMigrationPrompt(memories);
27740
28090
  const modelChain = [];
27741
28091
  const seenModels = new Set;
27742
- for (const m of [
27743
- deps.primaryModel ?? deps.model,
27744
- ...deps.fallbackModels ?? []
28092
+ for (const candidate of [
28093
+ {
28094
+ model: deps.primaryModel ?? deps.model,
28095
+ ...deps.primaryModel ? {} : deps.thinkingLevel ? { qualifier: deps.thinkingLevel } : {}
28096
+ },
28097
+ ...(deps.fallbackModels ?? []).map((fallback) => typeof fallback === "string" ? { model: fallback } : fallback)
27745
28098
  ]) {
27746
- if (m && !seenModels.has(m)) {
27747
- seenModels.add(m);
27748
- modelChain.push(m);
28099
+ const key = `${candidate.model}\x00${candidate.qualifier ?? ""}`;
28100
+ if (candidate.model && !seenModels.has(key)) {
28101
+ seenModels.add(key);
28102
+ modelChain.push(candidate);
27749
28103
  }
27750
28104
  }
27751
28105
  let parsed = null;
@@ -27753,17 +28107,17 @@ async function runPiMemoryMigration(deps) {
27753
28107
  for (let i = 0;i < modelChain.length; i += 1) {
27754
28108
  const model = modelChain[i];
27755
28109
  if (i > 0) {
27756
- sessionLog(deps.sessionId, `memory-migration: escalating to configured fallback model ${model} (${i}/${modelChain.length - 1})`);
28110
+ sessionLog(deps.sessionId, `memory-migration: escalating to configured fallback model ${model.model} (${i}/${modelChain.length - 1})`);
27757
28111
  }
27758
28112
  const result = await deps.runner.run({
27759
28113
  agent: "magic-context-historian",
27760
28114
  systemPrompt: withMigrationLanguageDirective(MIGRATION_SYSTEM_PROMPT2, deps.language),
27761
28115
  userMessage: prompt,
27762
- model,
28116
+ model: model.model,
27763
28117
  fallbackModels: undefined,
27764
28118
  timeoutMs: deps.timeoutMs ?? 5 * 60 * 1000,
27765
28119
  cwd: deps.directory,
27766
- thinkingLevel: deps.thinkingLevel,
28120
+ thinkingLevel: model.qualifier,
27767
28121
  accountingSessionId: deps.sessionId,
27768
28122
  accountingSubagent: "recomp"
27769
28123
  });
@@ -28277,7 +28631,7 @@ ${c.content}
28277
28631
  // package.json
28278
28632
  var package_default = {
28279
28633
  name: "@cortexkit/pi-magic-context",
28280
- version: "0.38.1",
28634
+ version: "0.39.0",
28281
28635
  type: "module",
28282
28636
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
28283
28637
  main: "dist/index.js",
@@ -28308,7 +28662,7 @@ var package_default = {
28308
28662
  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
28663
  "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
28664
  typecheck: "tsc -p ../retina-local-fs/tsconfig.build.json && tsc --noEmit",
28311
- test: "bun test",
28665
+ test: "bun install --frozen-lockfile && bun test",
28312
28666
  lint: "biome check src",
28313
28667
  "lint:fix": "biome check --write src",
28314
28668
  format: "biome format --write src",
@@ -29627,12 +29981,13 @@ function resolveHistorianFromConfig(config) {
29627
29981
  const historian = config.historian;
29628
29982
  if (historian?.disable === true)
29629
29983
  return;
29630
- const model = historian?.model?.trim();
29631
- if (!model || model.length === 0)
29984
+ const resolved = resolveHistorianModel(config, "pi");
29985
+ const model = resolved.primary?.model;
29986
+ if (!model)
29632
29987
  return;
29633
29988
  const historianContextLimit = resolveHistorianContextLimit(model);
29634
29989
  const historianChunkTokens = deriveHistorianChunkTokens(historianContextLimit);
29635
- const fallbackModels = resolveFallbackChain(historian?.fallback_models);
29990
+ const fallbackModels = resolved.fallbacks;
29636
29991
  return {
29637
29992
  runner: new PiSubagentRunner,
29638
29993
  model,
@@ -29640,7 +29995,7 @@ function resolveHistorianFromConfig(config) {
29640
29995
  historianChunkTokens,
29641
29996
  timeoutMs: config.historian_timeout_ms,
29642
29997
  twoPass: historian?.two_pass === true,
29643
- thinkingLevel: historian?.thinking_level,
29998
+ thinkingLevel: resolved.primary?.qualifier,
29644
29999
  executeThresholdPercentage: config.execute_threshold_percentage,
29645
30000
  executeThresholdTokens: config.execute_threshold_tokens,
29646
30001
  commitClusterTrigger: config.commit_cluster_trigger,
@@ -30130,6 +30485,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
30130
30485
  embeddingConfig: bootProjectDeps.config.embedding,
30131
30486
  memoryEnabled: bootProjectDeps.config.memory.enabled,
30132
30487
  retinaHandoff: bootProjectDeps.config.smart_notes.retina_handoff,
30488
+ mural: bootProjectDeps.config.mural,
30133
30489
  language: bootProjectDeps.config.language,
30134
30490
  gitCommitIndexing: bootProjectDeps.config.memory.git_commit_indexing,
30135
30491
  onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject