@cortexkit/pi-magic-context 0.43.2 → 0.44.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
@@ -9,7 +9,7 @@ import {
9
9
  log,
10
10
  sessionLog,
11
11
  flushLogger
12
- } from "./index-bfd52y8p.js";
12
+ } from "./index-tfzybajk.js";
13
13
  import {
14
14
  COMPACTION_ENABLED_PATH,
15
15
  isDreamerRunnable,
@@ -100,7 +100,9 @@ import {
100
100
  escapeXmlContent,
101
101
  getModuleNoteEvaluationBridge,
102
102
  getContextStoreUuid,
103
+ getAuthorityManagedMarker,
103
104
  drainMirrorPages,
105
+ V2_MEMORY_CATEGORIES,
104
106
  parseCompartmentOutput,
105
107
  resolveWorkspaceShareCategories,
106
108
  resolveWorkspaceIdentitySet,
@@ -172,8 +174,8 @@ import {
172
174
  clearDetectedContextLimit,
173
175
  getStrippedPlaceholderIds,
174
176
  applyStrippedPlaceholderDelta,
175
- NEWEST_REASONING_BEARING_ASSISTANT,
176
177
  THINKING_BINDING_RECOVERY_FROZEN_PREFIX,
178
+ THINKING_BINDING_STRIP_ORDER_END_MARKER,
177
179
  thinkingBindingRecoveryFrozenId,
178
180
  getThinkingBindingRecoveryTarget,
179
181
  armThinkingBindingRecovery,
@@ -402,7 +404,7 @@ import {
402
404
  registerTodoStateLifecycle,
403
405
  syncCtxMemoryToolEnabled,
404
406
  registerMagicContextTools
405
- } from "./index-bkrh6jhp.js";
407
+ } from "./index-8ytss1hp.js";
406
408
  import {
407
409
  CTX_REDUCE_KEEP,
408
410
  newestCtxReduceTagNumbers,
@@ -497,6 +499,8 @@ import {
497
499
  localBudget,
498
500
  calibrationForModelKey,
499
501
  historyLocalBudget,
502
+ SKELETON_REAL_INPUT_MAX_BYTES,
503
+ toolInputStringBytes,
500
504
  setRawMessageProvider2,
501
505
  withRawMessageProvider2,
502
506
  cleanUserText2,
@@ -512,14 +516,14 @@ import {
512
516
  getRawSessionTagKeysThrough2,
513
517
  getLegacyProtectedTailStartOrdinal2,
514
518
  readSessionChunk2
515
- } from "./index-mea04n1j.js";
519
+ } from "./index-2kj55q4e.js";
516
520
  import {
517
521
  pushNotification2
518
522
  } from "./index-b3eqj1g6.js";
519
- import"./index-5v60m046.js";
523
+ import"./index-jscq7067.js";
520
524
  import {
521
525
  logSlowWriteTransaction
522
- } from "./index-17102c1m.js";
526
+ } from "./index-mmkdxa5j.js";
523
527
  import {
524
528
  __toESM
525
529
  } from "./index-22jkk6wg.js";
@@ -527,7 +531,7 @@ import {
527
531
  // src/index.ts
528
532
  import { AsyncLocalStorage } from "node:async_hooks";
529
533
  import { createRequire as createRequire2 } from "node:module";
530
- import { join as join8, resolve as resolve3 } from "node:path";
534
+ import { join as join8, resolve as resolve4 } from "node:path";
531
535
 
532
536
  // ../plugin/src/config/live-snapshot.ts
533
537
  import { createHash } from "node:crypto";
@@ -1141,7 +1145,9 @@ var OVERFLOW_PATTERNS = [
1141
1145
  /prompt too long; exceeded (?:max )?context length/i,
1142
1146
  /too large for model with \d+ maximum context length/i,
1143
1147
  /model_context_window_exceeded/i,
1144
- /context size has been exceeded/i
1148
+ /context size has been exceeded/i,
1149
+ /prepared prompt exceeds engine max_context/i,
1150
+ /prompt exceeds (?:the )?.{0,32}\bmax_context\b/i
1145
1151
  ];
1146
1152
  var LIMIT_EXTRACTION_PATTERNS = [
1147
1153
  { pattern: /maximum prompt length is (\d+)/i, provenance: "prompt_only" },
@@ -1162,11 +1168,15 @@ var LIMIT_EXTRACTION_PATTERNS = [
1162
1168
  pattern: />\s*(\d+)\s*(?:tokens?\s*)?(?:maximum|max|limit)\b/i,
1163
1169
  provenance: "prompt_only"
1164
1170
  },
1171
+ { pattern: /prepared prompt exceeds engine max_context\s+(\d+)/i, provenance: "unknown" },
1172
+ { pattern: /prompt exceeds (?:the )?.{0,32}\bmax_context\s+(\d+)/i, provenance: "unknown" },
1165
1173
  { pattern: /max(?:imum)?.*context.*?(\d+)/i, provenance: "unknown" }
1166
1174
  ];
1167
1175
  var MIN_PLAUSIBLE_LIMIT = 1024;
1168
1176
  var MAX_PLAUSIBLE_LIMIT = 1e7;
1169
1177
  var THINKING_BINDING_MISMATCH_PATTERN = /bound to a different conversation/i;
1178
+ var THINKING_BINDING_FAILING_PATH_PATTERN = /^\s*(messages\.\d+\.content\.\d+):/;
1179
+ var THINKING_BINDING_FIRST_CHANGED_PATTERN = /first at `?(messages\.\d+\.content\.\d+)`?/;
1170
1180
  function extractErrorMessage(error) {
1171
1181
  if (!error)
1172
1182
  return "";
@@ -1195,29 +1205,6 @@ function extractErrorMessage(error) {
1195
1205
  }
1196
1206
  return String(error);
1197
1207
  }
1198
- function extractThinkingBindingMessageId(error) {
1199
- if (!error || typeof error !== "object")
1200
- return;
1201
- const seen = new Set;
1202
- const queue = [error];
1203
- while (queue.length > 0) {
1204
- const current = queue.shift();
1205
- if (!current || seen.has(current))
1206
- continue;
1207
- seen.add(current);
1208
- const record = current;
1209
- for (const key of ["message_id", "messageID", "messageId"]) {
1210
- const value = record[key];
1211
- if (typeof value === "string" && value.length > 0)
1212
- return value;
1213
- }
1214
- for (const value of Object.values(record)) {
1215
- if (value && typeof value === "object")
1216
- queue.push(value);
1217
- }
1218
- }
1219
- return;
1220
- }
1221
1208
  function extractExplicitHttpStatus(error) {
1222
1209
  if (!error || typeof error !== "object")
1223
1210
  return;
@@ -1247,13 +1234,20 @@ function detectThinkingBindingMismatch(error) {
1247
1234
  if (explicitStatus !== undefined && explicitStatus !== 400 || !THINKING_BINDING_MISMATCH_PATTERN.test(message)) {
1248
1235
  return { isBindingMismatch: false };
1249
1236
  }
1250
- const messageId = extractThinkingBindingMessageId(error);
1237
+ const failingBlockPath = THINKING_BINDING_FAILING_PATH_PATTERN.exec(message)?.[1];
1238
+ const firstChangedPath = THINKING_BINDING_FIRST_CHANGED_PATTERN.exec(message)?.[1];
1251
1239
  return {
1252
1240
  isBindingMismatch: true,
1253
1241
  matchedPattern: "bound to a different conversation",
1254
- ...messageId ? { messageId } : {}
1242
+ ...failingBlockPath ? { failingBlockPath } : {},
1243
+ ...firstChangedPath ? { firstChangedPath } : {}
1255
1244
  };
1256
1245
  }
1246
+ function isPrefixBoundThinkingModel(providerID, modelID) {
1247
+ if (providerID?.toLowerCase() !== "anthropic" || !modelID)
1248
+ return false;
1249
+ return isFable51ThinkingBindingModel(providerID, modelID) || /(?:^|[-_.])opus[-_.]?5[-_.]5(?:$|[-_.])/i.test(modelID);
1250
+ }
1257
1251
  function isFable51ThinkingBindingModel(providerID, modelID) {
1258
1252
  if (providerID?.toLowerCase() !== "anthropic" || !modelID)
1259
1253
  return false;
@@ -1586,6 +1580,31 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1586
1580
  return result;
1587
1581
  }
1588
1582
 
1583
+ // ../plugin/src/features/magic-context/storage-unavailable-reason.ts
1584
+ function describeStorageUnavailability(fallbackCause) {
1585
+ const migration = getMigrationOnOpenRefusal();
1586
+ const blockingProcesses = migration?.blockingProcesses ?? migration?.serverPids.map((pid) => ({ kind: "process", pid })) ?? [];
1587
+ if (migration && (blockingProcesses.length > 0 || migration.unreadableFile)) {
1588
+ return {
1589
+ kind: "migration_guard",
1590
+ persistedVersion: migration.persistedVersion,
1591
+ supportedVersion: migration.supportedVersion,
1592
+ blockingProcesses,
1593
+ ...migration.unreadableFile ? { unreadableFile: migration.unreadableFile } : {},
1594
+ ...migration.unreadableArm ? { unreadableArm: migration.unreadableArm } : {}
1595
+ };
1596
+ }
1597
+ const fence = getSchemaFenceRejection();
1598
+ if (fence) {
1599
+ return {
1600
+ kind: "schema_fence",
1601
+ persistedVersion: fence.persistedVersion,
1602
+ supportedVersion: fence.supportedVersion
1603
+ };
1604
+ }
1605
+ return { kind: "storage_failure", cause: fallbackCause };
1606
+ }
1607
+
1589
1608
  // ../plugin/src/features/magic-context/v22-deferred-backfill.ts
1590
1609
  import { createHash as createHash2 } from "node:crypto";
1591
1610
  import { realpathSync } from "node:fs";
@@ -2802,6 +2821,11 @@ var USER_FACING_FAILURES = {
2802
2821
  sentence: "Memory maintenance could not reach its model.",
2803
2822
  action: "Check the model connection, then run /ctx-dream again."
2804
2823
  },
2824
+ dream_step_limit: {
2825
+ code: "MC-D10",
2826
+ sentence: "Memory maintenance stopped at its hidden agent step limit.",
2827
+ action: "This task needs less work per run; changing the model connection will not help."
2828
+ },
2805
2829
  dream_empty_completion: {
2806
2830
  code: "MC-D03",
2807
2831
  sentence: "Memory maintenance received no usable response.",
@@ -2967,6 +2991,11 @@ var USER_FACING_FAILURES = {
2967
2991
  sentence: "Magic Context is temporarily unavailable.",
2968
2992
  action: "Retry in a moment."
2969
2993
  },
2994
+ store_ahead_of_binary: {
2995
+ code: "MC-C13",
2996
+ sentence: "Magic Context refused to start: its store (store.db) was migrated by a newer ck-mc build than the one running.",
2997
+ action: "Update ck-mc, or roll back by restoring ck-mc together with context.db and store.db from the same backup."
2998
+ },
2970
2999
  compaction_marker_missing: {
2971
3000
  code: "MC-C11",
2972
3001
  sentence: "The history boundary marker is missing from the OpenCode store, so requests carry the full session.",
@@ -2982,11 +3011,6 @@ var USER_FACING_FAILURES = {
2982
3011
  sentence: "Memory authority is inconsistent between the host and module.",
2983
3012
  action: "Run `ck doctor drain-authority` before changing Rust mode."
2984
3013
  },
2985
- rust_mode_unsupported: {
2986
- code: "MC-S06",
2987
- sentence: "Experimental Rust transform mode is not available on this OpenCode host.",
2988
- action: 'Magic Context is running its TypeScript mode instead; set `transform_mode` to "ts" to stop requesting Rust.'
2989
- },
2990
3014
  dreamer_task_failing: {
2991
3015
  code: "MC-S05",
2992
3016
  sentence: "A background maintenance task keeps failing on its schedule.",
@@ -3018,6 +3042,7 @@ function renderCapabilityRefusal(capability) {
3018
3042
  var DREAM_FAILURE_KEYS = {
3019
3043
  provider_timeout: "dream_provider_timeout",
3020
3044
  provider_error: "dream_provider_error",
3045
+ step_limit: "dream_step_limit",
3021
3046
  empty_completion: "dream_empty_completion",
3022
3047
  no_models: "dream_no_models",
3023
3048
  child_aborted: "dream_child_aborted",
@@ -3047,6 +3072,10 @@ function renderEmbeddingFailure(failureClass, style = "markdown") {
3047
3072
  return renderUserFacingFailure(EMBEDDING_FAILURE_KEYS[failureClass], style);
3048
3073
  }
3049
3074
 
3075
+ // src/dreamer/index.ts
3076
+ import { homedir as homedir2 } from "node:os";
3077
+ import { parse, resolve as resolve3 } from "node:path";
3078
+
3050
3079
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
3051
3080
  function openOpenCodeDb() {
3052
3081
  const resolution = resolveOpenCodeDbPath();
@@ -3204,6 +3233,37 @@ class HiddenCompletionRefusal extends Error {
3204
3233
  this.name = "HiddenCompletionRefusal";
3205
3234
  }
3206
3235
  }
3236
+ // ../plugin/src/v2/hooks/hidden-child.ts
3237
+ var HIDDEN_HISTORIAN_AGENT = "historian";
3238
+ var HIDDEN_DREAMER_AGENT = "dreamer-classifier";
3239
+ var HIDDEN_CURATE_AGENT = DREAMER_AGENT;
3240
+ var READ_TOOLS = ["read", "grep", "glob"];
3241
+ var AGENT_TOOLS = {
3242
+ [HIDDEN_HISTORIAN_AGENT]: [],
3243
+ [HIDDEN_DREAMER_AGENT]: [],
3244
+ [HIDDEN_CURATE_AGENT]: ["ctx_memory", "ctx_memory_list"],
3245
+ [DREAMER_MEMORY_MAPPER_AGENT]: READ_TOOLS,
3246
+ [DREAMER_PRIMER_INVESTIGATOR_AGENT]: [...READ_TOOLS, "ctx_search"],
3247
+ [DREAMER_RETROSPECTIVE_AGENT]: ["ctx_search"]
3248
+ };
3249
+ var AGENT_STEPS = {
3250
+ [HIDDEN_CURATE_AGENT]: 150,
3251
+ [DREAMER_MEMORY_MAPPER_AGENT]: 60,
3252
+ [DREAMER_DOCS_AGENT]: 60,
3253
+ [DREAMER_PRIMER_INVESTIGATOR_AGENT]: 40,
3254
+ [DREAMER_RETROSPECTIVE_AGENT]: 40
3255
+ };
3256
+ class HiddenAgentStepLimit extends Error {
3257
+ agent;
3258
+ cap;
3259
+ constructor(agent, cap) {
3260
+ super(`Hidden agent ${agent} exceeded its ${cap}-step limit`);
3261
+ this.name = "HiddenAgentStepLimit";
3262
+ this.agent = agent;
3263
+ this.cap = cap;
3264
+ }
3265
+ }
3266
+
3207
3267
  // ../plugin/src/shared/assistant-message-extractor.ts
3208
3268
  function asSessionMessage(value) {
3209
3269
  if (!isRecord(value))
@@ -3302,6 +3362,14 @@ function copyPromptArgs(args, body) {
3302
3362
  return { ...args, body: { ...body } };
3303
3363
  }
3304
3364
  var promptFailureDetails = new WeakMap;
3365
+ function isHostTimeoutError(error) {
3366
+ return error !== null && typeof error === "object" && error.name === "TimeoutError";
3367
+ }
3368
+ function isPromptTimeoutError(error) {
3369
+ if (isHostTimeoutError(error))
3370
+ return true;
3371
+ return error instanceof Error && /^prompt timed out after \d+ms$/.test(error.message);
3372
+ }
3305
3373
  function getPromptFailureDetail(error) {
3306
3374
  return error !== null && typeof error === "object" ? promptFailureDetails.get(error) ?? null : null;
3307
3375
  }
@@ -3358,9 +3426,13 @@ function parseModelSuggestion(error) {
3358
3426
  suggestion: suggestionMatch[1].trim()
3359
3427
  };
3360
3428
  }
3429
+ function externalAbortMessage(signal) {
3430
+ const reason = signal.reason;
3431
+ return `prompt aborted by external signal${reason instanceof Error && /^lease_(?:lost|expired):/.test(reason.message) ? `: ${reason.message}` : ""}`;
3432
+ }
3361
3433
  async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3362
3434
  if (signal?.aborted) {
3363
- throw new Error("prompt aborted by external signal");
3435
+ throw new Error(externalAbortMessage(signal));
3364
3436
  }
3365
3437
  const controller = new AbortController;
3366
3438
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -3380,7 +3452,7 @@ async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3380
3452
  if (!transport || transport.childSessionId) {
3381
3453
  await abortChildRun(client, transport?.childSessionId ?? args.path.id);
3382
3454
  }
3383
- throw new Error("prompt aborted by external signal");
3455
+ throw new Error(externalAbortMessage(signal));
3384
3456
  }
3385
3457
  if (controller.signal.aborted) {
3386
3458
  if (!transport || transport.childSessionId) {
@@ -3388,6 +3460,11 @@ async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3388
3460
  }
3389
3461
  throw new Error(`prompt timed out after ${timeoutMs}ms`);
3390
3462
  }
3463
+ if (isHostTimeoutError(error)) {
3464
+ if (!transport || transport.childSessionId) {
3465
+ await abortChildRun(client, transport?.childSessionId ?? args.path.id);
3466
+ }
3467
+ }
3391
3468
  throw error;
3392
3469
  } finally {
3393
3470
  clearTimeout(timeout);
@@ -3397,7 +3474,7 @@ async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3397
3474
  if (!transport || transport.childSessionId) {
3398
3475
  await abortChildRun(client, transport?.childSessionId ?? args.path.id);
3399
3476
  }
3400
- throw new Error(signal?.aborted ? "prompt aborted by external signal" : `prompt timed out after ${timeoutMs}ms`);
3477
+ throw new Error(signal?.aborted ? externalAbortMessage(signal) : `prompt timed out after ${timeoutMs}ms`);
3401
3478
  }
3402
3479
  }
3403
3480
  async function abortChildRun(client, sessionId) {
@@ -3413,7 +3490,7 @@ async function abortChildRun(client, sessionId) {
3413
3490
  }
3414
3491
  }
3415
3492
  function isNonRetryable(error, externalSignal) {
3416
- if (externalSignal?.aborted)
3493
+ if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit)
3417
3494
  return true;
3418
3495
  if (error instanceof Error) {
3419
3496
  if (error.name === "AbortError")
@@ -3423,6 +3500,8 @@ function isNonRetryable(error, externalSignal) {
3423
3500
  if (/^prompt timed out after \d+ms$/.test(error.message))
3424
3501
  return true;
3425
3502
  }
3503
+ if (isHostTimeoutError(error))
3504
+ return true;
3426
3505
  if (detectOverflow(error).isOverflow)
3427
3506
  return true;
3428
3507
  return false;
@@ -3435,11 +3514,14 @@ function shortErr(error) {
3435
3514
  }
3436
3515
  function classifyPromptFailure(error, phase, externalSignal) {
3437
3516
  const message = extractMessage(error);
3517
+ if (error instanceof HiddenAgentStepLimit)
3518
+ return "step_limit";
3438
3519
  if (externalSignal?.aborted || message === "prompt aborted by external signal") {
3439
3520
  return "child_aborted";
3440
3521
  }
3441
- if (/^prompt timed out after \d+ms$/.test(message))
3522
+ if (/^prompt timed out after \d+ms$/.test(message) || isHostTimeoutError(error)) {
3442
3523
  return "provider_timeout";
3524
+ }
3443
3525
  if (phase === "validation") {
3444
3526
  if (/returned no (?:assistant )?output|no assistant output/i.test(message)) {
3445
3527
  return "empty_completion";
@@ -3656,7 +3738,10 @@ async function promptSyncWithValidatedOutputRetry(client, args, options) {
3656
3738
  }
3657
3739
  }
3658
3740
  log(`[${callContext}] all models exhausted; tried: ${failedAttempts.map((failure) => failure.attempt.label).join(", ")}; original error: ${shortErr(firstError)}; last error: ${shortErr(lastError)}`);
3659
- throwWithPromptFailure(firstError ?? lastError ?? new Error("All fallback models failed validation"), failedAttempts, args, timeoutMs, options.transport);
3741
+ const cause = lastError ?? firstError;
3742
+ const exhausted = cause instanceof Error ? cause : new Error(String(cause ?? "All fallback models failed validation"));
3743
+ exhausted.message = `All models exhausted (${failedAttempts.map((failure) => failure.attempt.label).join(", ")}): ${exhausted.message}`;
3744
+ throwWithPromptFailure(exhausted, failedAttempts, args, timeoutMs, options.transport);
3660
3745
  }
3661
3746
  // ../plugin/src/shared/normalize-sdk-response.ts
3662
3747
  function normalizeSDKResponse(response, fallback, options) {
@@ -3681,6 +3766,149 @@ function normalizeSDKResponse(response, fallback, options) {
3681
3766
  }
3682
3767
  return fallback;
3683
3768
  }
3769
+ // ../plugin/src/shared/prompt-async-transport.ts
3770
+ var DEFAULT_POLL_INTERVAL_MS = 1000;
3771
+ var DEFAULT_START_GRACE_MS = 30000;
3772
+ var MESSAGE_WINDOW = 20;
3773
+ function sessionApi(client) {
3774
+ return client?.session;
3775
+ }
3776
+ function supportsPromptAsync(client) {
3777
+ const session = sessionApi(client);
3778
+ return typeof session?.promptAsync === "function" && typeof session?.status === "function" && typeof session?.messages === "function";
3779
+ }
3780
+ function createPromptAsyncTransport(client, childSessionId, options = {}) {
3781
+ if (!supportsPromptAsync(client))
3782
+ return;
3783
+ return Object.assign((request) => promptAsyncAndWaitForIdle(client, request, options), { childSessionId });
3784
+ }
3785
+ function infoOf(message) {
3786
+ const info = message?.info;
3787
+ return info && typeof info === "object" ? info : {};
3788
+ }
3789
+ function messageId(message) {
3790
+ const id = infoOf(message).id;
3791
+ return typeof id === "string" ? id : null;
3792
+ }
3793
+ function isSettledAssistant(message) {
3794
+ const info = infoOf(message);
3795
+ if (info.role !== "assistant")
3796
+ return false;
3797
+ return info.time?.completed != null || info.error != null || info.finish != null;
3798
+ }
3799
+ function unwrapData(response) {
3800
+ if (response && typeof response === "object" && "data" in response) {
3801
+ return response.data;
3802
+ }
3803
+ return response;
3804
+ }
3805
+ function describeRejection(error) {
3806
+ if (error instanceof Error)
3807
+ return error.message;
3808
+ try {
3809
+ return JSON.stringify(error);
3810
+ } catch {
3811
+ return String(error);
3812
+ }
3813
+ }
3814
+ function abortError(signal) {
3815
+ return signal.reason ?? new Error("prompt_async wait aborted");
3816
+ }
3817
+ function sleep(ms, signal) {
3818
+ return new Promise((resolve, reject) => {
3819
+ if (signal?.aborted) {
3820
+ reject(abortError(signal));
3821
+ return;
3822
+ }
3823
+ const onAbort = () => {
3824
+ clearTimeout(timer);
3825
+ reject(abortError(signal));
3826
+ };
3827
+ const timer = setTimeout(() => {
3828
+ signal?.removeEventListener("abort", onAbort);
3829
+ resolve();
3830
+ }, ms);
3831
+ signal?.addEventListener("abort", onAbort, { once: true });
3832
+ });
3833
+ }
3834
+ async function readMessages(session, sessionId, directory, signal) {
3835
+ const response = await session.messages?.({
3836
+ path: { id: sessionId },
3837
+ query: { ...directory ? { directory } : {}, limit: MESSAGE_WINDOW },
3838
+ ...signal ? { signal } : {}
3839
+ });
3840
+ const data = unwrapData(response);
3841
+ return Array.isArray(data) ? data : [];
3842
+ }
3843
+ async function readStatus(session, sessionId, directory, signal) {
3844
+ try {
3845
+ const response = await session.status?.({
3846
+ ...directory ? { query: { directory } } : {},
3847
+ ...signal ? { signal } : {}
3848
+ });
3849
+ if (response && typeof response === "object" && "error" in response) {
3850
+ if (response.error)
3851
+ return null;
3852
+ }
3853
+ const data = unwrapData(response);
3854
+ if (!data || typeof data !== "object")
3855
+ return null;
3856
+ const entry = data[sessionId];
3857
+ const type = entry?.type;
3858
+ return typeof type === "string" ? type : "idle";
3859
+ } catch (error) {
3860
+ if (signal?.aborted)
3861
+ throw error;
3862
+ return null;
3863
+ }
3864
+ }
3865
+ async function promptAsyncAndWaitForIdle(client, request, options = {}) {
3866
+ const session = sessionApi(client);
3867
+ if (!session || !supportsPromptAsync(client)) {
3868
+ throw new Error("prompt_async transport is unavailable on this client");
3869
+ }
3870
+ const sessionId = request.path.id;
3871
+ const directory = request.query?.directory;
3872
+ const dir = typeof directory === "string" ? directory : undefined;
3873
+ const signal = request.signal;
3874
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
3875
+ const startGraceMs = options.startGraceMs ?? DEFAULT_START_GRACE_MS;
3876
+ const baseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((id) => id !== null));
3877
+ const sent = await session.promptAsync?.(request);
3878
+ if (sent && typeof sent === "object" && "error" in sent) {
3879
+ const rejection = sent.error;
3880
+ if (rejection)
3881
+ throw new Error(`prompt_async was rejected: ${describeRejection(rejection)}`);
3882
+ }
3883
+ const sentAt = Date.now();
3884
+ let sawBusy = false;
3885
+ for (;; ) {
3886
+ await sleep(pollIntervalMs, signal);
3887
+ const status = await readStatus(session, sessionId, dir, signal);
3888
+ if (status === "busy" || status === "retry") {
3889
+ sawBusy = true;
3890
+ continue;
3891
+ }
3892
+ if (status === null)
3893
+ continue;
3894
+ const messages = await readMessages(session, sessionId, dir, signal);
3895
+ const last = messages.at(-1);
3896
+ const lastId = messageId(last);
3897
+ if (last && lastId !== null && !baseline.has(lastId) && isSettledAssistant(last))
3898
+ return;
3899
+ const fresh = messages.some((message) => {
3900
+ const id = messageId(message);
3901
+ return id !== null && !baseline.has(id);
3902
+ });
3903
+ if (sawBusy && fresh)
3904
+ return;
3905
+ if (Date.now() - sentAt >= startGraceMs) {
3906
+ if (fresh)
3907
+ return;
3908
+ throw new Error(`prompt_async did not start a run in child session ${sessionId} within ${startGraceMs}ms`);
3909
+ }
3910
+ }
3911
+ }
3684
3912
  // ../plugin/src/shared/summarize-child-stderr.ts
3685
3913
  var MAX_CHILD_STDERR_SUMMARY_LENGTH = 500;
3686
3914
  var CHILD_ERROR_LINE_PATTERN = /^(?:[A-Za-z]*Error|SqliteError|Error)\b[^\n]*/;
@@ -3820,6 +4048,9 @@ function failedInvocationStatus(error) {
3820
4048
  const message = describeError(error).brief;
3821
4049
  if (/timed out after \d+ms|prompt timed out/i.test(message))
3822
4050
  return "timed_out";
4051
+ if (error !== null && typeof error === "object" && error.name === "TimeoutError") {
4052
+ return "timed_out";
4053
+ }
3823
4054
  if (/length-capped|no (?:assistant )?output|empty (?:assistant )?(?:output|text)/i.test(message)) {
3824
4055
  return "empty";
3825
4056
  }
@@ -4881,7 +5112,7 @@ function shouldDiscardLastHistorianCompartment(compartments, chunk) {
4881
5112
  function validateHistorianOutput(text, sessionId, chunk, _priorCompartments, sequenceOffset) {
4882
5113
  const parseStarted = startHistorianPublishStage(sessionId, "parse", `response_chars=${text.length}`);
4883
5114
  const parsed = parseCompartmentOutput(text);
4884
- finishHistorianPublishStage(sessionId, "parse", parseStarted, "completed", `compartments=${parsed.compartments.length}`);
5115
+ finishHistorianPublishStage(sessionId, "parse", parseStarted, "completed", `compartments=${parsed.compartments.length} dropped_fact_blocks=${parsed.droppedFactBlocks} dropped_facts=${parsed.droppedFacts}`);
4885
5116
  const validationStarted = startHistorianPublishStage(sessionId, "validate");
4886
5117
  const finish = (result) => {
4887
5118
  finishHistorianPublishStage(sessionId, "validate", validationStarted, result.ok ? "completed" : "failed", result.ok ? `compartments=${result.compartments.length}` : `reason=${JSON.stringify(result.error)}`);
@@ -4920,6 +5151,7 @@ function validateHistorianOutput(text, sessionId, chunk, _priorCompartments, seq
4920
5151
  ok: true,
4921
5152
  compartments: mapped.compartments,
4922
5153
  facts: parsed.facts,
5154
+ ...parsed.droppedFactBlocks > 0 ? { droppedFactBlocks: parsed.droppedFactBlocks, droppedFacts: parsed.droppedFacts } : {},
4923
5155
  userObservations: parsed.userObservations.length > 0 ? parsed.userObservations : undefined,
4924
5156
  primerCandidates: parsed.primerCandidates.length > 0 ? parsed.primerCandidates.slice(0, 1) : undefined,
4925
5157
  events: parsed.events.length > 0 ? parsed.events : undefined
@@ -5172,6 +5404,7 @@ function resolveHiddenCompletionExecutor(executor, client, db, directory, entryP
5172
5404
  return createV1HiddenCompletionExecutor(client, db, directory);
5173
5405
  }
5174
5406
  function createV1HiddenCompletionExecutor(client, db, directory) {
5407
+ const asyncChildren = new Set;
5175
5408
  return {
5176
5409
  capabilities: { tools: true, harness: "opencode" },
5177
5410
  async open(run) {
@@ -5188,11 +5421,17 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5188
5421
  preferResponseOnMissingData: true
5189
5422
  });
5190
5423
  const id = typeof created?.id === "string" ? created.id : "";
5424
+ if (id && run.kind === "dreamer-task")
5425
+ asyncChildren.add(id);
5191
5426
  return { id, childSessionId: id || undefined };
5192
5427
  },
5193
- async attempt(_handle, request) {
5428
+ async attempt(handle, request) {
5194
5429
  if (!client)
5195
5430
  throw new Error("Hidden completion client is unavailable");
5431
+ if (asyncChildren.has(handle.id) && supportsPromptAsync(client)) {
5432
+ await promptAsyncAndWaitForIdle(client, request);
5433
+ return;
5434
+ }
5196
5435
  await client.session.prompt(request);
5197
5436
  },
5198
5437
  async collect(handle, limit) {
@@ -5220,6 +5459,8 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5220
5459
  };
5221
5460
  },
5222
5461
  async close(handle, settlement) {
5462
+ if (handle?.id)
5463
+ asyncChildren.delete(handle.id);
5223
5464
  if (!client)
5224
5465
  return;
5225
5466
  await teardownChildSession({
@@ -5231,6 +5472,9 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5231
5472
  }
5232
5473
  };
5233
5474
  }
5475
+ function historianReasoningBudgetDiagnostic(outputTokens) {
5476
+ return `historian ran out of output budget while reasoning (length-capped at ${outputTokens} tokens, no text) — set historian.maxTokens or route historian.model to a low-reasoning lane/variant`;
5477
+ }
5234
5478
  async function runValidatedHistorianPass(args) {
5235
5479
  const firstRun = await runHistorianPrompt({
5236
5480
  ...args,
@@ -5457,14 +5701,14 @@ async function runHistorianPrompt(args) {
5457
5701
  }
5458
5702
  const backoffMs = getHistorianRetryBackoffMs(retryIndex);
5459
5703
  sessionLog(parentSessionId, `historian retry ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES} after ${backoffMs}ms: ${errorMsg}`);
5460
- await sleep(backoffMs);
5704
+ await sleep2(backoffMs);
5461
5705
  }
5462
5706
  }
5463
5707
  completion = await executor.collect(handle, 50);
5464
5708
  const lengthCapped = completion.lengthCapped;
5465
5709
  const textResult = completion.text;
5466
5710
  const reasoningResult = textResult ? null : completion.reasoning;
5467
- const emptyError = !textResult && reasoningResult && lengthCapped ? `historian output length-capped at ${completion.usage.output} tokens (all reasoning, no text) — set historian.maxTokens or route historian.model to a low-reasoning lane/variant` : !textResult && !reasoningResult ? "Historian returned no assistant output." : !textResult ? "Historian returned reasoning but no assistant text." : lengthCapped ? "Historian returned length-capped output." : null;
5711
+ const emptyError = !textResult && reasoningResult && lengthCapped ? historianReasoningBudgetDiagnostic(completion.usage.output) : !textResult && !reasoningResult ? "Historian returned no assistant output." : !textResult ? "Historian returned reasoning but no assistant text." : lengthCapped ? "Historian returned length-capped output." : null;
5468
5712
  const invocationId = recordInvocation({
5469
5713
  status: emptyError ? "empty" : "completed",
5470
5714
  messages: completion.messages,
@@ -5594,7 +5838,7 @@ function isTransientHistorianPromptError(message) {
5594
5838
  "overloaded"
5595
5839
  ].some((token) => normalized.includes(token));
5596
5840
  }
5597
- function sleep(ms) {
5841
+ function sleep2(ms) {
5598
5842
  return new Promise((resolve) => {
5599
5843
  setTimeout(resolve, ms);
5600
5844
  });
@@ -5791,13 +6035,25 @@ function runLeaseGuardedWrite(db, holderId, leaseKey, fn, slowWriteThresholdMs)
5791
6035
  return fn();
5792
6036
  }, `lease-guarded-write:${leaseKey}`, slowWriteThresholdMs);
5793
6037
  }
5794
- var LEASE_HEARTBEAT_INTERVAL_MS = 60 * 1000;
6038
+ var LEASE_HEARTBEAT_INTERVAL_MS = 40 * 1000;
6039
+ function reacquireOwnedLease(db, holderId, leaseKey, generation) {
6040
+ const keys = rowKeys(leaseKey);
6041
+ return runImmediate(db, () => {
6042
+ if (getLeaseHolder(db, leaseKey) !== holderId || getLeaseGeneration(db, leaseKey) !== generation) {
6043
+ return false;
6044
+ }
6045
+ const now = Date.now();
6046
+ setDreamState(db, keys.heartbeat, String(now));
6047
+ setDreamState(db, keys.expiry, String(now + LEASE_DURATION_MS));
6048
+ return true;
6049
+ });
6050
+ }
5795
6051
  function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisition = LEASE_HEARTBEAT_INTERVAL_MS) {
5796
6052
  const intervalMs = typeof intervalOrAcquisition === "number" ? intervalOrAcquisition : LEASE_HEARTBEAT_INTERVAL_MS;
5797
6053
  const acquisition = typeof intervalOrAcquisition === "number" ? undefined : intervalOrAcquisition;
5798
6054
  let lost = false;
5799
- let expectedGeneration = acquisition?.generation ?? getLeaseGeneration(db, leaseKey);
5800
- let lastConfirmedAt = acquisition?.acquiredAt ?? Date.now();
6055
+ const expectedGeneration = acquisition?.generation ?? getLeaseGeneration(db, leaseKey);
6056
+ let renewalFailures = 0;
5801
6057
  const declareLost = (reason) => {
5802
6058
  if (lost)
5803
6059
  return;
@@ -5809,32 +6065,20 @@ function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisiti
5809
6065
  return;
5810
6066
  try {
5811
6067
  if (renewLease(db, holderId, leaseKey, expectedGeneration === null ? undefined : expectedGeneration)) {
5812
- lastConfirmedAt = Date.now();
5813
6068
  return;
5814
6069
  }
5815
- if (expectedGeneration !== null && getLeaseGeneration(db, leaseKey) !== expectedGeneration) {
5816
- declareLost("lease generation changed — another holder acquired it");
5817
- return;
5818
- }
5819
- if (Date.now() - lastConfirmedAt > LEASE_DURATION_MS) {
5820
- declareLost("lease lapsed past TTL — another holder may have run");
5821
- return;
5822
- }
5823
- const reacquired = acquireLeaseWithAcquisition(db, holderId, leaseKey);
5824
- if (reacquired) {
5825
- if (expectedGeneration !== null && reacquired.generation !== expectedGeneration) {
5826
- declareLost("lease generation changed during reacquisition");
5827
- return;
5828
- }
5829
- expectedGeneration = reacquired.generation;
5830
- lastConfirmedAt = Date.now();
6070
+ if (expectedGeneration !== null && reacquireOwnedLease(db, holderId, leaseKey, expectedGeneration))
5831
6071
  return;
6072
+ const holder = getLeaseHolder(db, leaseKey);
6073
+ if (holder !== null && holder !== holderId) {
6074
+ declareLost(`lease_lost: taken by ${holder}`);
6075
+ } else if (getLeaseGeneration(db, leaseKey) !== expectedGeneration) {
6076
+ declareLost("lease_lost: generation changed");
6077
+ } else {
6078
+ declareLost("lease_expired: reacquire failed (holder missing)");
5832
6079
  }
5833
- declareLost("lease acquired by another holder");
5834
6080
  } catch {
5835
- if (Date.now() - lastConfirmedAt > LEASE_DURATION_MS) {
5836
- declareLost("lease renewal unconfirmed past TTL");
5837
- }
6081
+ renewalFailures += 1;
5838
6082
  }
5839
6083
  };
5840
6084
  beat();
@@ -5846,6 +6090,9 @@ function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisiti
5846
6090
  },
5847
6091
  get lost() {
5848
6092
  return lost;
6093
+ },
6094
+ get renewalFailures() {
6095
+ return renewalFailures;
5849
6096
  }
5850
6097
  };
5851
6098
  }
@@ -5923,6 +6170,14 @@ function assertParsedManifestNonEmpty(parsedCount, expectedCount, text, expected
5923
6170
  }
5924
6171
 
5925
6172
  // ../plugin/src/features/magic-context/dreamer/module-apply.ts
6173
+ var loggedNotOwnerProjects = new Set;
6174
+ function logDreamerNotOwnerOnce(projectIdentity) {
6175
+ if (loggedNotOwnerProjects.has(projectIdentity))
6176
+ return;
6177
+ loggedNotOwnerProjects.add(projectIdentity);
6178
+ log(`[dreamer] ${projectIdentity}: module-managed project; skipping TS writes without an owner route`);
6179
+ }
6180
+
5926
6181
  class DreamerModuleBusyError extends Error {
5927
6182
  state;
5928
6183
  transient = true;
@@ -6547,7 +6802,7 @@ async function runHiddenSingleShotPrompt(args) {
6547
6802
  fetchOutput: () => args.executor.collect(handle, 50),
6548
6803
  validateOutput: (completion) => {
6549
6804
  if (completion.lengthCapped) {
6550
- throw new Error(`${args.callContext} returned length-capped output`);
6805
+ throw new Error(completion.reasoning && !completion.text ? `${args.callContext} ran out of output budget while reasoning (length-capped at ${completion.usage.output} tokens, no text) — set dreamer.maxTokens or use a low-reasoning model` : `${args.callContext} returned length-capped output`);
6551
6806
  }
6552
6807
  const text = completion.text;
6553
6808
  if (!text && !args.allowEmpty)
@@ -7120,12 +7375,45 @@ function validateClassifyManifest(text, expectedIds) {
7120
7375
  return parsed;
7121
7376
  }
7122
7377
 
7378
+ // ../plugin/src/features/magic-context/dreamer/failed-invocation-evidence.ts
7379
+ var FAILED_CHILD_READ_TIMEOUT_MS = 5000;
7380
+ var FAILED_CHILD_MESSAGE_LIMIT = 200;
7381
+ async function readFailedChildMessages(client, sessionId, directory) {
7382
+ if (!client || !sessionId)
7383
+ return;
7384
+ let timer;
7385
+ try {
7386
+ const response = await Promise.race([
7387
+ client.session.messages({
7388
+ path: { id: sessionId },
7389
+ query: { directory, limit: FAILED_CHILD_MESSAGE_LIMIT }
7390
+ }),
7391
+ new Promise((resolve) => {
7392
+ timer = setTimeout(() => resolve(null), FAILED_CHILD_READ_TIMEOUT_MS);
7393
+ })
7394
+ ]);
7395
+ if (response === null)
7396
+ return;
7397
+ const messages = normalizeSDKResponse(response, [], {
7398
+ preferResponseOnMissingData: true
7399
+ });
7400
+ return Array.isArray(messages) ? messages : undefined;
7401
+ } catch (error) {
7402
+ log(`[dreamer] failed-child evidence read failed for ${sessionId}: ${String(error)}`);
7403
+ return;
7404
+ } finally {
7405
+ if (timer)
7406
+ clearTimeout(timer);
7407
+ }
7408
+ }
7409
+
7123
7410
  // ../plugin/src/features/magic-context/dreamer/classify.ts
7124
7411
  var MIN_POOL_TO_CLASSIFY = 10;
7125
7412
  var FULL_POOL_CEILING = 100;
7126
7413
  var STAGE3_ANCHOR_COUNT = 30;
7127
7414
  var CLASSIFY_CHUNK_SIZE = 100;
7128
7415
  var CLASSIFY_MODULE_RUN_TIMEOUT_MS = 660000;
7416
+ var HOST_COMPLETION_REQUIRED = "host_completion_required";
7129
7417
 
7130
7418
  class ClassifyModuleFailureError extends Error {
7131
7419
  transient = true;
@@ -7209,13 +7497,12 @@ async function runClassify(args) {
7209
7497
  let stage;
7210
7498
  let toClassify;
7211
7499
  let anchors = [];
7500
+ const unclassifiedIds = new Set(getUnclassifiedMemoryIds(args.db, active.map((candidate) => candidate.contextMemory.id)));
7501
+ toClassify = active.filter((candidate) => unclassifiedIds.has(candidate.contextMemory.id));
7212
7502
  if (active.length <= FULL_POOL_CEILING) {
7213
7503
  stage = 2;
7214
- toClassify = active;
7215
7504
  } else {
7216
7505
  stage = 3;
7217
- const unclassifiedIds = new Set(getUnclassifiedMemoryIds(args.db, active.map((candidate) => candidate.contextMemory.id)));
7218
- toClassify = active.filter((candidate) => unclassifiedIds.has(candidate.contextMemory.id));
7219
7506
  const classified = active.filter((candidate) => !unclassifiedIds.has(candidate.contextMemory.id));
7220
7507
  anchors = stratifiedAnchors(classified, STAGE3_ANCHOR_COUNT);
7221
7508
  }
@@ -7236,7 +7523,7 @@ async function runClassify(args) {
7236
7523
  chunks.push(toClassify.slice(i, i + CLASSIFY_CHUNK_SIZE));
7237
7524
  }
7238
7525
  const abortController = new AbortController;
7239
- const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => abortController.abort(), args.leaseAcquisition);
7526
+ const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
7240
7527
  try {
7241
7528
  for (let i = 0;i < chunks.length; i += 1) {
7242
7529
  const remainingMs = Math.max(0, args.deadline - Date.now());
@@ -7271,68 +7558,89 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
7271
7558
  memories: chunk.map(toPromptMemory2),
7272
7559
  anchors
7273
7560
  });
7561
+ const runOnHostCarrier = async (system) => {
7562
+ const executor = resolveHiddenCompletionExecutor(args.hiddenCompletionExecutor, args.client, args.db, args.sessionDirectory, "classify-memories");
7563
+ closeExecutor = executor;
7564
+ handle = await executor.open({
7565
+ parentSessionId: args.parentSessionId,
7566
+ agent: DREAMER_CLASSIFIER_AGENT,
7567
+ kind: "dreamer-task",
7568
+ system,
7569
+ model: args.model,
7570
+ configuredModels: [
7571
+ ...args.model ? [args.model] : [],
7572
+ ...args.fallbackModels ?? []
7573
+ ],
7574
+ timeoutMs: sliceMs,
7575
+ title: "magic-context-dream-classify",
7576
+ directory: args.sessionDirectory,
7577
+ metadata: { task: "classify-memories" }
7578
+ });
7579
+ agentSessionId = handle.id || null;
7580
+ if (!agentSessionId)
7581
+ throw new Error("Could not create classify session.");
7582
+ const opened = handle;
7583
+ const run = await promptSyncWithValidatedOutputRetry(args.client, {
7584
+ path: { id: agentSessionId },
7585
+ query: { directory: args.sessionDirectory },
7586
+ body: {
7587
+ agent: DREAMER_CLASSIFIER_AGENT,
7588
+ system,
7589
+ ...modelBodyField(args.model),
7590
+ parts: [{ type: "text", text: prompt, synthetic: true }]
7591
+ }
7592
+ }, {
7593
+ transport: Object.assign((request) => executor.attempt(opened, request), { childSessionId: opened.childSessionId }),
7594
+ timeoutMs: sliceMs,
7595
+ signal,
7596
+ fallbackModels: args.fallbackModels,
7597
+ callContext: "dreamer:classify-memories",
7598
+ fetchOutput: () => executor.collect(opened, 50),
7599
+ validateOutput: (completion) => {
7600
+ const messages = completion.messages ?? [];
7601
+ if (completion.lengthCapped) {
7602
+ throw new Error(completion.reasoning && !completion.text ? `classify ran out of output budget while reasoning (length-capped at ${completion.usage.output} tokens, no text) — set dreamer.maxTokens or use a low-reasoning model` : "classify returned length-capped output");
7603
+ }
7604
+ const text = completion.text;
7605
+ if (!text)
7606
+ throw new Error("classify returned no output");
7607
+ try {
7608
+ validateClassifyManifest(text, new Set(chunk.map((candidate) => candidate.id)));
7609
+ } catch (error) {
7610
+ const providerFailure = providerOutputFailureFromInvalidManifest(messages, text);
7611
+ if (providerFailure)
7612
+ throw providerFailure;
7613
+ throw error;
7614
+ }
7615
+ return text;
7616
+ }
7617
+ });
7618
+ promptSettled = true;
7619
+ return run;
7620
+ };
7274
7621
  if (moduleRoute) {
7275
- const { accounting, ...run } = await runClassifyThroughModule(args, chunk, anchors, signal);
7622
+ const { accounting, ...run } = await runClassifyThroughModule(args, chunk, anchors, signal, async (system) => {
7623
+ const run = await runOnHostCarrier(system);
7624
+ const output = run.output;
7625
+ return {
7626
+ text: run.validated,
7627
+ model: output.providerId && output.modelId ? `${output.providerId}/${output.modelId}` : run.attempt.label,
7628
+ length_capped: output.lengthCapped === true,
7629
+ usage: {
7630
+ input: output.usage?.input ?? 0,
7631
+ output: output.usage?.output ?? 0,
7632
+ cache_read: output.usage?.cacheRead ?? 0,
7633
+ cache_write: output.usage?.cacheWrite ?? 0
7634
+ }
7635
+ };
7636
+ });
7276
7637
  recordInvocation(args, startedAt, {
7277
7638
  status: "completed",
7278
7639
  moduleAccounting: accounting
7279
7640
  });
7280
7641
  return run;
7281
7642
  }
7282
- const executor = resolveHiddenCompletionExecutor(args.hiddenCompletionExecutor, args.client, args.db, args.sessionDirectory, "classify-memories");
7283
- closeExecutor = executor;
7284
- handle = await executor.open({
7285
- parentSessionId: args.parentSessionId,
7286
- agent: DREAMER_CLASSIFIER_AGENT,
7287
- kind: "dreamer-task",
7288
- system: withContentLanguageDirective(CLASSIFY_SYSTEM_PROMPT, args.language),
7289
- model: args.model,
7290
- configuredModels: [...args.model ? [args.model] : [], ...args.fallbackModels ?? []],
7291
- timeoutMs: sliceMs,
7292
- title: "magic-context-dream-classify",
7293
- directory: args.sessionDirectory,
7294
- metadata: { task: "classify-memories" }
7295
- });
7296
- agentSessionId = handle.id || null;
7297
- if (!agentSessionId)
7298
- throw new Error("Could not create classify session.");
7299
- const opened = handle;
7300
- const run = await promptSyncWithValidatedOutputRetry(args.client, {
7301
- path: { id: agentSessionId },
7302
- query: { directory: args.sessionDirectory },
7303
- body: {
7304
- agent: DREAMER_CLASSIFIER_AGENT,
7305
- system: withContentLanguageDirective(CLASSIFY_SYSTEM_PROMPT, args.language),
7306
- ...modelBodyField(args.model),
7307
- parts: [{ type: "text", text: prompt, synthetic: true }]
7308
- }
7309
- }, {
7310
- transport: Object.assign((request) => executor.attempt(opened, request), { childSessionId: opened.childSessionId }),
7311
- timeoutMs: sliceMs,
7312
- signal,
7313
- fallbackModels: args.fallbackModels,
7314
- callContext: "dreamer:classify-memories",
7315
- fetchOutput: () => executor.collect(opened, 50),
7316
- validateOutput: (completion) => {
7317
- const messages = completion.messages ?? [];
7318
- if (completion.lengthCapped) {
7319
- throw new Error("classify returned length-capped output");
7320
- }
7321
- const text = completion.text;
7322
- if (!text)
7323
- throw new Error("classify returned no output");
7324
- try {
7325
- validateClassifyManifest(text, new Set(chunk.map((candidate) => candidate.id)));
7326
- } catch (error) {
7327
- const providerFailure = providerOutputFailureFromInvalidManifest(messages, text);
7328
- if (providerFailure)
7329
- throw providerFailure;
7330
- throw error;
7331
- }
7332
- return text;
7333
- }
7334
- });
7335
- promptSettled = true;
7643
+ const run = await runOnHostCarrier(withContentLanguageDirective(CLASSIFY_SYSTEM_PROMPT, args.language));
7336
7644
  recordInvocation(args, startedAt, {
7337
7645
  status: "completed",
7338
7646
  messages: run.output.messages,
@@ -7345,7 +7653,8 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
7345
7653
  log(`[dreamer] classify chunk failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
7346
7654
  recordInvocation(args, startedAt, {
7347
7655
  status: failedInvocationStatus(failure),
7348
- error: failure
7656
+ error: failure,
7657
+ messages: args.hiddenCompletionExecutor ? undefined : await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
7349
7658
  });
7350
7659
  if (moduleRoute || failure instanceof HiddenCompletionRefusal || signal.aborted || failure instanceof DreamerProviderOutputFailureError || getPromptFailureDetail(failure)?.failureClass !== "parse_failed" && getPromptFailureDetail(failure) !== null)
7351
7660
  throw failure;
@@ -7359,7 +7668,7 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
7359
7668
  });
7360
7669
  }
7361
7670
  }
7362
- async function runClassifyThroughModule(args, chunk, anchors, signal) {
7671
+ async function runClassifyThroughModule(args, chunk, anchors, signal, runOnHost) {
7363
7672
  const prompt = buildClassifyPrompt({
7364
7673
  projectPath: args.projectIdentity,
7365
7674
  memories: chunk.map(toPromptMemory2),
@@ -7367,7 +7676,8 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
7367
7676
  });
7368
7677
  const modelChain = [args.model, ...args.fallbackModels ?? []].map(toModelEntry).filter((entry) => entry !== undefined).map((entry) => entry.model);
7369
7678
  const resolvedModelChain = [...new Set(modelChain)];
7370
- const response = await args.moduleClient?.call({
7679
+ const commandId = `classify:${args.moduleCommandId ?? Date.now()}:${createHash5("sha256").update(chunk.map((candidate) => candidate.id).join(",")).digest("hex").slice(0, 24)}`;
7680
+ const runTask = (hostCompletion) => args.moduleClient?.call({
7371
7681
  sessionId: args.moduleSessionId,
7372
7682
  projectRoot: args.moduleProjectRoot,
7373
7683
  method: "dreamer.run_task",
@@ -7376,7 +7686,7 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
7376
7686
  v: 1,
7377
7687
  session_id: args.moduleSessionId,
7378
7688
  task: "classify",
7379
- command_id: `classify:${args.moduleCommandId ?? Date.now()}:${createHash5("sha256").update(chunk.map((candidate) => candidate.id).join(",")).digest("hex").slice(0, 24)}`,
7689
+ command_id: commandId,
7380
7690
  authority_generation: args.moduleAuthorityGeneration,
7381
7691
  model_chain: resolvedModelChain,
7382
7692
  payload: {
@@ -7385,12 +7695,20 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
7385
7695
  memory_id: candidate.id,
7386
7696
  content_hash: candidate.normalizedHash
7387
7697
  }))
7388
- }
7698
+ },
7699
+ ...hostCompletion ? { host_completion: hostCompletion } : {}
7389
7700
  },
7390
7701
  signal,
7391
- timeoutMs: CLASSIFY_MODULE_RUN_TIMEOUT_MS
7702
+ timeoutMs: Math.max(1, resolvedModelChain.length) * CLASSIFY_MODULE_RUN_TIMEOUT_MS + 30000
7392
7703
  });
7393
- const result = response?.result ?? response;
7704
+ const unwrap = (response) => response?.result ?? response;
7705
+ let result = unwrap(await runTask());
7706
+ const asked = result;
7707
+ if (asked?.code === HOST_COMPLETION_REQUIRED) {
7708
+ if (typeof asked.system_prompt !== "string")
7709
+ throw new Error("module asked for a host classify completion without a system prompt");
7710
+ result = unwrap(await runTask(await runOnHost(asked.system_prompt)));
7711
+ }
7394
7712
  if (!result || typeof result !== "object")
7395
7713
  throw new Error("module returned invalid classify result");
7396
7714
  const manifestText = result.manifest_text;
@@ -8152,7 +8470,7 @@ async function guardedReadFile(projectRoot, repoRelativePath, signal, fileLimitB
8152
8470
  const body = guardedReadFileBody(projectRoot, repoRelativePath, signal, fileLimitBytes);
8153
8471
  let onAbort;
8154
8472
  const abort = new Promise((_, reject) => {
8155
- onAbort = () => reject(abortError(signal));
8473
+ onAbort = () => reject(abortError2(signal));
8156
8474
  if (signal.aborted)
8157
8475
  onAbort();
8158
8476
  else
@@ -8222,9 +8540,9 @@ async function closeLateOpenOnAbort(openPromise, signal) {
8222
8540
  return handle;
8223
8541
  if (handle)
8224
8542
  handle.close().catch(() => {});
8225
- throw abortError(signal);
8543
+ throw abortError2(signal);
8226
8544
  }
8227
- function abortError(signal) {
8545
+ function abortError2(signal) {
8228
8546
  return signal.reason instanceof SmartNoteNetworkError ? signal.reason : new SmartNoteNetworkError("SMART_NOTE_NETWORK: aborted");
8229
8547
  }
8230
8548
  function isPathInside(root, target) {
@@ -9043,9 +9361,9 @@ async function evaluateSmartNotes(args) {
9043
9361
  throw new Error(`Dream lease lost during smart-notes ${phase}`);
9044
9362
  }
9045
9363
  };
9046
- const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => {
9364
+ const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => {
9047
9365
  leaseLost = true;
9048
- leaseAbortController.abort(new Error("Dream lease lost during smart notes"));
9366
+ leaseAbortController.abort(new Error(reason));
9049
9367
  log("[dreamer] smart notes: lease lost — aborting");
9050
9368
  args.onLeaseLost?.("smart notes");
9051
9369
  }, args.leaseAcquisition);
@@ -9360,6 +9678,7 @@ async function confirmReadOnly(args, noteId, content, surfaceCondition, leaseSig
9360
9678
  parts: [{ type: "text", text: prompt, synthetic: true }]
9361
9679
  }
9362
9680
  }, {
9681
+ transport: createPromptAsyncTransport(client, childSessionId),
9363
9682
  timeoutMs: Math.max(1000, args.deadline - Date.now()),
9364
9683
  signal: promptSignal.signal,
9365
9684
  fallbackModels: args.fallbackModels,
@@ -9667,7 +9986,7 @@ function computeMapBatchSliceMs(remainingMs, batchesRemaining) {
9667
9986
  return Math.min(remainingMs, Math.max(MAP_BATCH_FLOOR_MS, Math.floor(remainingMs / batchesRemaining)));
9668
9987
  }
9669
9988
  function isTimeoutClassError2(error) {
9670
- return error instanceof Error && /^prompt timed out after \d+ms$/.test(error.message);
9989
+ return isPromptTimeoutError(error);
9671
9990
  }
9672
9991
  function shouldRequeueIndependentMapping(state, content, repoDir) {
9673
9992
  if (!state.hasSentinel || state.files.length > 0)
@@ -9722,7 +10041,7 @@ async function mapMemories(args) {
9722
10041
  }
9723
10042
  result.remaining = inputs.length;
9724
10043
  const abortController = new AbortController;
9725
- const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => abortController.abort(), args.leaseAcquisition);
10044
+ const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
9726
10045
  try {
9727
10046
  let consecutiveTimeouts = 0;
9728
10047
  let timeoutStreakElapsedMs = [];
@@ -9837,6 +10156,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
9837
10156
  parts: [{ type: "text", text: prompt, synthetic: true }]
9838
10157
  }
9839
10158
  }, {
10159
+ transport: createPromptAsyncTransport(client, agentSessionId),
9840
10160
  timeoutMs: sliceMs,
9841
10161
  signal,
9842
10162
  fallbackModels: args.fallbackModels,
@@ -9871,7 +10191,11 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
9871
10191
  } catch (error) {
9872
10192
  const desc = describeError(error);
9873
10193
  log(`[dreamer] map-memories batch failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
9874
- recordInvocation2(args, startedAt, { status: failedInvocationStatus(error), error });
10194
+ recordInvocation2(args, startedAt, {
10195
+ status: failedInvocationStatus(error),
10196
+ error,
10197
+ messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
10198
+ });
9875
10199
  if (error instanceof DreamerModuleFailureError)
9876
10200
  throw error;
9877
10201
  if (signal.aborted)
@@ -10196,7 +10520,7 @@ function pruneExpiredPrimerCandidatesForProject(db, projectIdentity, now = Date.
10196
10520
  db.transaction(() => {
10197
10521
  for (const id of toDelete)
10198
10522
  stmt.run(id, projectIdentity);
10199
- })();
10523
+ }).immediate();
10200
10524
  return toDelete.length;
10201
10525
  }
10202
10526
  async function promotePrimers(args) {
@@ -10615,13 +10939,7 @@ var FRUSTRATION_MARKER_REGEX = /\b(?:not what i asked|i already (?:said|told you
10615
10939
  var LEARNINGS_BLOCK_REGEX = /<learnings\b[^>]*>(.*?)<\/learnings>/is;
10616
10940
  var LEARNING_REGEX = /<learning\b([^>]*)>(.*?)<\/learning>/gis;
10617
10941
  var ATTR_REGEX = /([a-zA-Z_:-]+)\s*=\s*"([^"]*)"/g;
10618
- var VALID_MEMORY_CATEGORIES = new Set([
10619
- "PROJECT_RULES",
10620
- "ARCHITECTURE",
10621
- "CONSTRAINTS",
10622
- "CONFIG_VALUES",
10623
- "NAMING"
10624
- ]);
10942
+ var VALID_MEMORY_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
10625
10943
  var RAW_QUOTE_REGEX = /["“”][^"“”]{4,}["“”]|'[^']{4,}'/;
10626
10944
  var DATE_REGEX = /\b(?:20\d{2}[-/]\d{1,2}[-/]\d{1,2}|\d{1,2}\/\d{1,2}\/20\d{2}|(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\s+\d{1,2},?\s+20\d{2})\b/i;
10627
10945
  function parseRetrospectiveLearnings(text) {
@@ -11380,6 +11698,10 @@ function validateVerifyManifest(text, expectedIds) {
11380
11698
  // ../plugin/src/features/magic-context/dreamer/verify.ts
11381
11699
  var VERIFY_BATCH_SIZE = 50;
11382
11700
  var IDENTICAL_PROVIDER_FAILURE_BATCH_LIMIT = 2;
11701
+ var VERIFY_BATCH_FLOOR_MS = 240000;
11702
+ function computeVerifyBatchSliceMs(remainingMs, batchesRemaining) {
11703
+ return Math.min(remainingMs, Math.max(VERIFY_BATCH_FLOOR_MS, Math.floor(remainingMs / batchesRemaining)));
11704
+ }
11383
11705
  function closeBroadCycle(args, cycleStartAt) {
11384
11706
  if (!args.forceBroad || cycleStartAt === undefined)
11385
11707
  return;
@@ -11434,17 +11756,19 @@ async function runVerify(args) {
11434
11756
  batches.push(gate.inScope.slice(i, i + VERIFY_BATCH_SIZE));
11435
11757
  }
11436
11758
  const abortController = new AbortController;
11437
- const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => abortController.abort(), args.leaseAcquisition);
11759
+ const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
11438
11760
  let consecutiveProviderFailures = 0;
11439
11761
  let priorProviderFailureFingerprint = null;
11440
11762
  let lastProviderFailure = null;
11441
11763
  try {
11442
11764
  for (let i = 0;i < batches.length; i += 1) {
11443
11765
  const remainingMs = Math.max(0, args.deadline - Date.now());
11444
- if (remainingMs <= 0)
11766
+ if (remainingMs < VERIFY_BATCH_FLOOR_MS) {
11767
+ result.stopReason = "deadline";
11768
+ log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"}: stopping before batch ${i + 1}/${batches.length} — remaining budget ${remainingMs}ms is below the ${VERIFY_BATCH_FLOOR_MS}ms batch floor`);
11445
11769
  break;
11446
- const batchesRemaining = batches.length - i;
11447
- const sliceMs = Math.max(1, Math.floor(remainingMs / batchesRemaining));
11770
+ }
11771
+ const sliceMs = computeVerifyBatchSliceMs(remainingMs, batches.length - i);
11448
11772
  const counts = await verifyOneBatch(args, batches[i], sliceMs, abortController.signal);
11449
11773
  result.verified += counts.verified;
11450
11774
  result.updated += counts.updated;
@@ -11455,6 +11779,11 @@ async function runVerify(args) {
11455
11779
  result.remaining -= batchProcessed;
11456
11780
  result.batches += 1;
11457
11781
  args.onProgress?.(result.verified + result.updated + result.archived + result.skipped + result.refused, result.refused);
11782
+ if (counts.timedOut) {
11783
+ result.stopReason = "batch-timeout";
11784
+ log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"}: batch ${i + 1}/${batches.length} timed out after its ${sliceMs}ms slice; stopping with ${result.remaining} remaining`);
11785
+ break;
11786
+ }
11458
11787
  if (counts.providerFailure) {
11459
11788
  lastProviderFailure = counts.providerFailure;
11460
11789
  if (counts.providerFailure.fingerprint === priorProviderFailureFingerprint) {
@@ -11543,6 +11872,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11543
11872
  parts: [{ type: "text", text: prompt, synthetic: true }]
11544
11873
  }
11545
11874
  }, {
11875
+ transport: createPromptAsyncTransport(client, agentSessionId),
11546
11876
  timeoutMs: sliceMs,
11547
11877
  signal,
11548
11878
  fallbackModels: args.fallbackModels,
@@ -11577,8 +11907,24 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11577
11907
  const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
11578
11908
  const promptFailure = getPromptFailureDetail(error);
11579
11909
  log(`[dreamer] verify batch ${providerFailure ? "provider failure" : "failed"}: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
11580
- recordInvocation4(args, startedAt, { status: failedInvocationStatus(error), error });
11581
- if (error instanceof DreamerModuleFailureError || signal.aborted || promptFailure !== null && promptFailure.failureClass !== "parse_failed" && !providerFailure)
11910
+ recordInvocation4(args, startedAt, {
11911
+ status: failedInvocationStatus(error),
11912
+ error,
11913
+ messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
11914
+ });
11915
+ if (error instanceof DreamerModuleFailureError || signal.aborted)
11916
+ throw error;
11917
+ if (promptFailure?.failureClass === "provider_timeout" || isPromptTimeoutError(error)) {
11918
+ return {
11919
+ verified: 0,
11920
+ updated: 0,
11921
+ archived: 0,
11922
+ skipped: 0,
11923
+ refused: 0,
11924
+ timedOut: true
11925
+ };
11926
+ }
11927
+ if (promptFailure !== null && promptFailure.failureClass !== "parse_failed" && !providerFailure)
11582
11928
  throw error;
11583
11929
  return {
11584
11930
  verified: 0,
@@ -11820,6 +12166,16 @@ function dreamRunFailureDetail(error) {
11820
12166
  }
11821
12167
  const described = describeError(error);
11822
12168
  const message = described.brief;
12169
+ if (error instanceof Error && error.name === "HiddenAgentStepLimit") {
12170
+ return {
12171
+ failure_class: "step_limit",
12172
+ model_attempted: null,
12173
+ models_tried: [],
12174
+ provider_error: null,
12175
+ timeout_ms: null,
12176
+ child_session_id: null
12177
+ };
12178
+ }
11823
12179
  const providerFailure = error instanceof HiddenCompletionRefusal || error instanceof Error && error.name === "DreamerProviderOutputFailureError";
11824
12180
  return {
11825
12181
  failure_class: providerFailure ? "provider_error" : /no models?|model chain is empty/i.test(message) ? "no_models" : "unknown",
@@ -11959,11 +12315,16 @@ function createDreamTaskExecutor(deps) {
11959
12315
  });
11960
12316
  };
11961
12317
  reportProgress(0);
12318
+ if (config.modelChainUnavailable) {
12319
+ return {
12320
+ status: "completed",
12321
+ detail: "skipped: Pi model chain is empty (no configured model resolves in Pi)"
12322
+ };
12323
+ }
11962
12324
  const incompleteMessage = (remaining) => {
11963
12325
  const processed = processedDreamTaskItems(backlogAtStart.pending, remaining);
11964
12326
  return `${config.task} incomplete: ${remaining} remain (was ${backlogAtStart.pending} at run start; processed ${processed} this run)`;
11965
12327
  };
11966
- const parent = await resolveParentSessionId();
11967
12328
  let moduleRoute;
11968
12329
  if (config.task === "curate" || config.task === "map-memories" || config.task === "compress-cues" || config.task === "classify-memories" || config.task === "verify" || config.task === "verify-broad" || config.task === "retrospective") {
11969
12330
  try {
@@ -11979,6 +12340,19 @@ function createDreamTaskExecutor(deps) {
11979
12340
  throw new DreamerModuleFailureError("authority.status", error);
11980
12341
  }
11981
12342
  }
12343
+ if (!moduleRoute && getAuthorityManagedMarker(db, projectIdentity) && [
12344
+ "curate",
12345
+ "map-memories",
12346
+ "compress-cues",
12347
+ "classify-memories",
12348
+ "verify",
12349
+ "verify-broad",
12350
+ "retrospective"
12351
+ ].includes(config.task)) {
12352
+ logDreamerNotOwnerOnce(projectIdentity);
12353
+ return { status: "completed", detail: "skipped: module-managed memory; not owner" };
12354
+ }
12355
+ const parent = await resolveParentSessionId();
11982
12356
  if (!leaseOwnershipMatches(db, holderId, leaseAcquisition.generation, leaseKey)) {
11983
12357
  throw new Error("Dream lease lost during executor setup");
11984
12358
  }
@@ -12168,13 +12542,14 @@ function createDreamTaskExecutor(deps) {
12168
12542
  });
12169
12543
  return { status: "completed" };
12170
12544
  }
12171
- const error = incompleteMessage(result.remaining);
12545
+ const batchTimedOut = result.stopReason === "batch-timeout";
12546
+ const error = batchTimedOut ? `${config.task}: a batch timed out within its time slice; ${result.remaining} remain` : incompleteMessage(result.remaining);
12172
12547
  recordRun("failed", error, {
12173
12548
  progress: verificationProgress,
12174
12549
  memoryChanges: computeMemoryDelta(memoryBefore),
12175
12550
  backlogAfter
12176
12551
  });
12177
- return { status: "failed", transient: true, error };
12552
+ return { status: "failed", transient: !batchTimedOut, error };
12178
12553
  }
12179
12554
  recordRun("completed", null, {
12180
12555
  progress: verificationProgress,
@@ -13000,25 +13375,45 @@ function ensureSeeded(db, projectIdentity, config, now) {
13000
13375
  const nextDueAt = nextDueAtMs(config.schedule, now);
13001
13376
  seedTaskScheduleState(db, projectIdentity, config.task, nextDueAt, lastRunAt, config.schedule);
13002
13377
  }
13378
+ function firstRepeatedCivilMinute(candidateMs) {
13379
+ const date = new Date(candidateMs);
13380
+ const first = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes()).getTime();
13381
+ return first < candidateMs ? first : null;
13382
+ }
13003
13383
  function reconcileSchedule(db, projectIdentity, config, now) {
13004
13384
  ensureSeeded(db, projectIdentity, config, now);
13005
- const stored = getTaskScheduleState(db, projectIdentity, config.task);
13006
- if (!stored || stored.schedule === config.schedule)
13007
- return;
13008
- if (config.schedule.trim() === "") {
13009
- writeTaskScheduleState(db, { ...stored, schedule: config.schedule, nextDueAt: null });
13010
- return;
13011
- }
13012
- if (stored.schedule === null && stored.nextDueAt !== null) {
13013
- writeTaskScheduleState(db, { ...stored, schedule: config.schedule });
13385
+ if (getTaskScheduleState(db, projectIdentity, config.task)?.schedule === config.schedule)
13014
13386
  return;
13387
+ const candidate = nextDueAtMs(config.schedule, now);
13388
+ const repeatedFirst = candidate === null ? null : firstRepeatedCivilMinute(candidate);
13389
+ const afterRepeatedFirst = repeatedFirst === null ? candidate : nextDueAtMs(config.schedule, now, repeatedFirst);
13390
+ db.exec("BEGIN IMMEDIATE");
13391
+ let committed = false;
13392
+ try {
13393
+ const stored = getTaskScheduleState(db, projectIdentity, config.task);
13394
+ if (stored && stored.schedule !== config.schedule) {
13395
+ if (stored.schedule === null && stored.nextDueAt !== null) {
13396
+ writeTaskScheduleState(db, { ...stored, schedule: config.schedule });
13397
+ } else {
13398
+ const nextDueAt = repeatedFirst !== null && candidate !== null && stored.nextDueAt !== null && stored.nextDueAt > candidate && stored.lastStatus !== null ? afterRepeatedFirst : candidate;
13399
+ const reconciledNextDueAt = stored.nextDueAt === null ? nextDueAt : nextDueAt === null ? stored.nextDueAt : Math.min(stored.nextDueAt, nextDueAt);
13400
+ writeTaskScheduleState(db, {
13401
+ ...stored,
13402
+ schedule: config.schedule,
13403
+ nextDueAt: reconciledNextDueAt,
13404
+ retryCount: reconciledNextDueAt === stored.nextDueAt ? stored.retryCount : 0
13405
+ });
13406
+ }
13407
+ }
13408
+ db.exec("COMMIT");
13409
+ committed = true;
13410
+ } finally {
13411
+ if (!committed) {
13412
+ try {
13413
+ db.exec("ROLLBACK");
13414
+ } catch {}
13415
+ }
13015
13416
  }
13016
- writeTaskScheduleState(db, {
13017
- ...stored,
13018
- schedule: config.schedule,
13019
- nextDueAt: nextDueAtMs(config.schedule, now),
13020
- retryCount: 0
13021
- });
13022
13417
  }
13023
13418
  function planDueTasks(db, projectIdentity, tasks, now) {
13024
13419
  const pruned = pruneNonCanonicalTaskRows(db, projectIdentity, CANONICAL_DREAM_TASKS);
@@ -13027,6 +13422,8 @@ function planDueTasks(db, projectIdentity, tasks, now) {
13027
13422
  }
13028
13423
  const due = [];
13029
13424
  for (const config of tasks) {
13425
+ if (config.schedule.trim() === "")
13426
+ continue;
13030
13427
  reconcileSchedule(db, projectIdentity, config, now);
13031
13428
  const state = getTaskScheduleState(db, projectIdentity, config.task);
13032
13429
  if (!state || state.nextDueAt === null)
@@ -13112,10 +13509,11 @@ async function runDomainGroup(deps, group, cb) {
13112
13509
  }
13113
13510
  try {
13114
13511
  for (const due of [...group].sort((a, b) => compareTaskOrder(a.config.task, b.config.task))) {
13115
- if (!leaseOwnershipMatches(db, holderId, acquisition.generation, leaseKey)) {
13512
+ if (!reacquireOwnedLease(db, holderId, leaseKey, acquisition.generation)) {
13116
13513
  log(`[dreamer] domain lease lost (${leaseKey}) — stopping remaining task(s)`);
13117
13514
  break;
13118
13515
  }
13516
+ acquisition = { acquiredAt: Date.now(), generation: acquisition.generation };
13119
13517
  if (!cb?.forceGate) {
13120
13518
  const gatePass = evaluateTaskGate(due.config.task, {
13121
13519
  db,
@@ -13704,7 +14102,8 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
13704
14102
  }
13705
14103
  const dreamerConfig = reg.dreamerConfig;
13706
14104
  const dreamingEnabled = Boolean(dreamerConfig && dreamerConfig.disable !== true);
13707
- const runtimeConfigs = dreamingEnabled && dreamerConfig ? buildDreamTaskRuntimeConfigs(dreamerConfig, reg.harness, reg.language, reg.mural?.model) : [];
14105
+ const configuredTasks = dreamingEnabled && dreamerConfig ? buildDreamTaskRuntimeConfigs(dreamerConfig, reg.harness, reg.language, reg.mural?.model) : [];
14106
+ const runtimeConfigs = reg.validateTaskModels?.(configuredTasks) ?? configuredTasks;
13708
14107
  await sweepOrphanedInternalChildren(reg, runtimeConfigs.filter((config) => PRIVACY_SENSITIVE_CHILD_TASKS.includes(config.task)).map((config) => config.timeoutMinutes));
13709
14108
  if (commitIndexingEnabled && reg.gitCommitIndexing) {
13710
14109
  await sweepGitCommits({
@@ -13719,6 +14118,10 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
13719
14118
  }
13720
14119
  try {
13721
14120
  await runCompiledSmartNoteSweep(reg, db);
14121
+ } catch (error) {
14122
+ log(`[dreamer] compiled smart-note sweep failed for ${reg.projectIdentity}:`, error);
14123
+ }
14124
+ try {
13722
14125
  const executor = createDreamTaskExecutor({
13723
14126
  client: reg.client,
13724
14127
  sessionDirectory: reg.directory,
@@ -13773,6 +14176,10 @@ async function sweepOrphanedInternalChildren(reg, privacyTimeoutMinutes) {
13773
14176
  }
13774
14177
  }
13775
14178
  async function runCompiledSmartNoteSweep(reg, db) {
14179
+ if (getAuthorityManagedMarker(db, reg.projectIdentity)) {
14180
+ logDreamerNotOwnerOnce(reg.projectIdentity);
14181
+ return;
14182
+ }
13776
14183
  const leaseKey = leaseKeyFor("evaluate-smart-notes", reg.projectIdentity);
13777
14184
  const holderId = crypto.randomUUID();
13778
14185
  if (!acquireLease(db, holderId, leaseKey))
@@ -15418,6 +15825,40 @@ function extractPiTextContent(content) {
15418
15825
  }
15419
15826
 
15420
15827
  // src/dreamer/index.ts
15828
+ var warnedUnknownModels = new Set;
15829
+ var modelAvailability = new WeakMap;
15830
+ function validatePiDreamerModels(tasks, registry) {
15831
+ let resolved = modelAvailability.get(registry);
15832
+ if (!resolved) {
15833
+ resolved = new Map;
15834
+ modelAvailability.set(registry, resolved);
15835
+ }
15836
+ return tasks.map((task) => {
15837
+ const entries = [task.model, ...task.fallbackModels ?? []].filter((entry) => entry !== undefined);
15838
+ const valid = entries.filter((entry) => {
15839
+ const model = typeof entry === "string" ? entry : entry.model;
15840
+ let available = resolved.get(model);
15841
+ if (available === undefined) {
15842
+ const separator = model.indexOf("/");
15843
+ available = separator > 0 && Boolean(registry.find(model.slice(0, separator), model.slice(separator + 1)));
15844
+ resolved.set(model, available);
15845
+ }
15846
+ if (available)
15847
+ return true;
15848
+ if (!warnedUnknownModels.has(model)) {
15849
+ log(`[dreamer] WARNING: dropping Pi model not found: ${model}`);
15850
+ warnedUnknownModels.add(model);
15851
+ }
15852
+ return false;
15853
+ });
15854
+ return {
15855
+ ...task,
15856
+ model: valid[0],
15857
+ fallbackModels: valid.slice(1),
15858
+ modelChainUnavailable: entries.length > 0 && valid.length === 0
15859
+ };
15860
+ });
15861
+ }
15421
15862
  var PI_DREAMER_PROJECTS = Symbol.for("magic-context.pi.dreamer-registered-projects");
15422
15863
  function getRegisteredProjects() {
15423
15864
  const globals = globalThis;
@@ -15446,7 +15887,8 @@ var sessionCounter = 0;
15446
15887
  var piSubagentRunnerFactory = () => new PiSubagentRunner;
15447
15888
  var startDreamScheduleTimerFn = startDreamScheduleTimer;
15448
15889
  function registerPiDreamerProject(opts) {
15449
- if (opts.config.disable === true) {
15890
+ const directory = resolve3(opts.projectDir);
15891
+ if (opts.config.disable === true || directory === parse(directory).root || directory === homedir2()) {
15450
15892
  return;
15451
15893
  }
15452
15894
  const existing = registeredProjects2.get(opts.projectIdentity);
@@ -15459,7 +15901,7 @@ function registerPiDreamerProject(opts) {
15459
15901
  callback(projectIdentity);
15460
15902
  };
15461
15903
  if (existing) {
15462
- if (existing.generation && existing.projectDir === opts.projectDir) {
15904
+ if (existing.generation && existing.projectDir === opts.projectDir && (!opts.modelRegistry || existing.modelRegistry)) {
15463
15905
  return;
15464
15906
  }
15465
15907
  existing.cleanup();
@@ -15472,12 +15914,14 @@ function registerPiDreamerProject(opts) {
15472
15914
  });
15473
15915
  let cleanup;
15474
15916
  let cancelled = false;
15917
+ const modelRegistry = opts.modelRegistry;
15475
15918
  startDreamScheduleTimerFn({
15476
15919
  directory: opts.projectDir,
15477
15920
  projectIdentity: opts.projectIdentity,
15478
15921
  harness: opts.harness,
15479
15922
  client,
15480
15923
  dreamerConfig: opts.config,
15924
+ validateTaskModels: modelRegistry ? (tasks) => validatePiDreamerModels(tasks, modelRegistry) : undefined,
15481
15925
  sampleDreamRun: opts.sampleDreamRun,
15482
15926
  language: opts.language,
15483
15927
  gitCommitIndexing: opts.gitCommitIndexing,
@@ -15509,7 +15953,7 @@ function registerPiDreamerProject(opts) {
15509
15953
  const manualRun = runManualDream({
15510
15954
  db: manualOpts.db,
15511
15955
  projectIdentity: manualOpts.projectIdentity,
15512
- tasks: buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model),
15956
+ tasks: manualOpts.modelRegistry ? validatePiDreamerModels(buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model), manualOpts.modelRegistry) : buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model),
15513
15957
  executor: createDreamTaskExecutor({
15514
15958
  client: manualClient,
15515
15959
  sessionDirectory: manualOpts.projectDir,
@@ -15542,7 +15986,8 @@ function registerPiDreamerProject(opts) {
15542
15986
  cleanup?.();
15543
15987
  },
15544
15988
  runManual,
15545
- projectDir: opts.projectDir
15989
+ projectDir: opts.projectDir,
15990
+ modelRegistry: opts.modelRegistry
15546
15991
  });
15547
15992
  }
15548
15993
  async function runPiDreamForProject(projectIdentity, task, registrationOwner) {
@@ -16054,7 +16499,7 @@ function formatEmbedStatusText(coverage, drain) {
16054
16499
  lines.push(`Synapse — ${formatSynapseLaneDescriptor(coverage.synapseDescriptor)}`);
16055
16500
  }
16056
16501
  lines.push(`This session: ${coverage.session.embedded} / ${coverage.session.total} compartments embedded`);
16057
- lines.push(`Project memories: ${coverage.memories.embedded} / ${coverage.memories.total} embedded`);
16502
+ lines.push(coverage.memories.memoryEnabled === false ? "Project memories: off (memory disabled)" : `Project memories: ${coverage.memories.embedded} / ${coverage.memories.total} embedded`);
16058
16503
  if (coverage.commits.gitEnabled) {
16059
16504
  lines.push(`Git commits: ${coverage.commits.embedded} / ${coverage.commits.total}`);
16060
16505
  } else {
@@ -16230,7 +16675,6 @@ No active Pi session is available.`,
16230
16675
  projectDir: deps.projectDir,
16231
16676
  projectIdentity: deps.projectIdentity
16232
16677
  };
16233
- const memoryEnabled = deps.resolveMemoryEnabled?.(ctx) ?? deps.memoryEnabled;
16234
16678
  const sub = args.trim().toLowerCase();
16235
16679
  if (sub === "pause") {
16236
16680
  embedPauseBySession.add(sessionId);
@@ -16247,16 +16691,6 @@ Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`,
16247
16691
  });
16248
16692
  return;
16249
16693
  }
16250
- if (memoryEnabled === false) {
16251
- sendStatus({
16252
- title: "/ctx-embed",
16253
- text: `## /ctx-embed
16254
-
16255
- Memory is disabled for this project, so there is no semantic embedding to backfill.`,
16256
- level: "info"
16257
- });
16258
- return;
16259
- }
16260
16694
  await ensureProjectRegisteredFromPiDirectory(project.projectDir, deps.db);
16261
16695
  if (sub === "start") {
16262
16696
  const { text, level } = await runEmbedDrain(deps.db, project.projectIdentity, sessionId, {
@@ -16289,13 +16723,11 @@ ${statusText}`,
16289
16723
  }
16290
16724
  });
16291
16725
  }
16292
- function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity, _notify) {
16726
+ function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity) {
16293
16727
  if (autoEmbedAttemptedBySession.has(sessionId))
16294
16728
  return;
16295
16729
  if (embedPauseBySession.has(sessionId))
16296
16730
  return;
16297
- if (deps.memoryEnabled === false)
16298
- return;
16299
16731
  autoEmbedAttemptedBySession.add(sessionId);
16300
16732
  (async () => {
16301
16733
  let drainReachedTerminal = false;
@@ -16331,7 +16763,7 @@ function executeFlush(db, sessionId) {
16331
16763
  removePendingOp(db, sessionId, op.tagId);
16332
16764
  dropped++;
16333
16765
  }
16334
- })();
16766
+ }).immediate();
16335
16767
  const parts = [];
16336
16768
  if (dropped > 0)
16337
16769
  parts.push(`${dropped} dropped`);
@@ -16630,7 +17062,7 @@ function createTagger() {
16630
17062
  db.transaction(() => {
16631
17063
  getUpsertCounterStatement(db).run(sessionId, next, getHarness());
16632
17064
  insertTag(db, sessionId, messageId, type, byteSize, next, reasoningByteSize, toolName, inputByteSize, toolOwnerMessageId, entryFingerprint, tokenCounts);
16633
- })();
17065
+ }).immediate();
16634
17066
  } catch (error) {
16635
17067
  if (!isUniqueConstraintError(error)) {
16636
17068
  throw error;
@@ -16866,7 +17298,8 @@ var canonicalReasons = new Set([
16866
17298
  "ttl_expiry",
16867
17299
  "epoch_change",
16868
17300
  "coverage_fold",
16869
- "profile_transition"
17301
+ "profile_transition",
17302
+ "host_compaction"
16870
17303
  ]);
16871
17304
  var piReasonAliases = {
16872
17305
  project_memory_change: "project_memory_epoch",
@@ -17100,6 +17533,72 @@ var RECENT_TOOL_SKELETON_WINDOW = 20;
17100
17533
  function buildReplacementContent(tagId) {
17101
17534
  return `[dropped §${tagId}§]`;
17102
17535
  }
17536
+ function applyNewToolDrop(target, options) {
17537
+ if (!target)
17538
+ return { result: "absent", mode: "full" };
17539
+ if (options.inWindow && hasSmallToolInput(target) || options.keepSkeleton === true || target.cannotRemove?.() === true) {
17540
+ const strip = target.hasAttachments?.() === true;
17541
+ return {
17542
+ result: (strip ? target.skeletonStripped?.() : target.skeletonReal?.()) ?? "absent",
17543
+ mode: strip ? "skeleton_stripped" : "skeleton_real"
17544
+ };
17545
+ }
17546
+ const result = target.drop?.() ?? "absent";
17547
+ if (result === "truncated" && target.hasAttachments?.() === true) {
17548
+ return { result: target.skeletonStripped?.() ?? "absent", mode: "skeleton_stripped" };
17549
+ }
17550
+ return { result, mode: result === "truncated" ? "skeleton_real" : "full" };
17551
+ }
17552
+ function hasSmallToolInput(target) {
17553
+ const bytes = target?.inputStringBytes?.() ?? null;
17554
+ return bytes !== null && bytes <= SKELETON_REAL_INPUT_MAX_BYTES;
17555
+ }
17556
+ var CACHE_LOSING_FOLD_REASONS = new Set([
17557
+ "model_change",
17558
+ "system_hash",
17559
+ "ttl_idle",
17560
+ "host_compaction"
17561
+ ]);
17562
+ function sameBytes(left, right) {
17563
+ if (left === null || right === null)
17564
+ return left === right;
17565
+ return Buffer.compare(Buffer.from(left), Buffer.from(right)) === 0;
17566
+ }
17567
+ function foldChangesServedPrefix(before, after) {
17568
+ if (!sameBytes(before.m0Bytes, after.m0Bytes))
17569
+ return true;
17570
+ if (!sameBytes(before.m1Bytes, after.m1Bytes))
17571
+ return true;
17572
+ return before.muralDataUrl !== after.muralDataUrl;
17573
+ }
17574
+ function foldBustsServedPrefix(reason, before, after) {
17575
+ return CACHE_LOSING_FOLD_REASONS.has(reason ?? "") || foldChangesServedPrefix(before, after);
17576
+ }
17577
+ function convertLegacyToolSkeletons(db, sessionId, targets) {
17578
+ const rows = db.prepare(`SELECT tag_number AS tagNumber, drop_mode AS dropMode
17579
+ FROM tags
17580
+ WHERE session_id = ? AND type = 'tool' AND status = 'dropped'
17581
+ AND drop_mode IN ('truncated', 'full')
17582
+ ORDER BY tag_number`).all(sessionId);
17583
+ const converted = new Map;
17584
+ for (const row of rows) {
17585
+ const target = targets.get(row.tagNumber);
17586
+ if (target?.canDrop?.() !== true)
17587
+ continue;
17588
+ const cannotRemove = target.cannotRemove?.() === true || target.requiresToolArcSkeleton === true || target.wouldStrandConversationEnd?.() === true;
17589
+ let mode;
17590
+ if (row.dropMode === "full") {
17591
+ if (target.cannotRemove?.() !== true)
17592
+ continue;
17593
+ mode = "skeleton_real";
17594
+ } else {
17595
+ mode = hasSmallToolInput(target) || cannotRemove ? "skeleton_real" : "full";
17596
+ }
17597
+ updateTagDropMode(db, sessionId, row.tagNumber, mode);
17598
+ converted.set(row.tagNumber, mode);
17599
+ }
17600
+ return converted;
17601
+ }
17103
17602
  function applyPendingOperations(sessionId, db, targets, protectedTagIds, preloadedTags, preloadedPendingOps, syntheticPendingOps = [], editMarkerTagIds = new Set, onTagReduced, onBatchComplete) {
17104
17603
  let didMutateMessage = false;
17105
17604
  let diagnostics;
@@ -17159,7 +17658,8 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
17159
17658
  let shouldPersistDrop = false;
17160
17659
  if (isToolTag) {
17161
17660
  if (editMarkerTagIds.has(pendingOp.tagId)) {
17162
- const markResult = target?.editMarker?.() ?? "absent";
17661
+ const strip = target?.hasAttachments?.() === true;
17662
+ const markResult = (strip ? target?.editMarkerStripped?.() : target?.editMarker?.()) ?? "absent";
17163
17663
  if (markResult === "incomplete" || markResult === "absent") {
17164
17664
  reject(`edit_marker_${markResult}`);
17165
17665
  continue;
@@ -17167,34 +17667,23 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
17167
17667
  didMutateMessage = true;
17168
17668
  operationMutated = true;
17169
17669
  onTagReduced?.({ tagNumber: pendingOp.tagId, mode: "edit_marker" });
17170
- updateTagDropMode(db, sessionId, pendingOp.tagId, "edit_marker");
17171
- shouldPersistDrop = true;
17172
- } else if (skeletonWindow.has(pendingOp.tagId)) {
17173
- const truncResult = target?.truncate?.() ?? "absent";
17174
- if (truncResult === "incomplete" || synthetic && truncResult !== "truncated") {
17175
- reject(`truncate_${truncResult}`);
17176
- continue;
17177
- }
17178
- if (truncResult === "truncated") {
17179
- didMutateMessage = true;
17180
- operationMutated = true;
17181
- onTagReduced?.({ tagNumber: pendingOp.tagId, mode: "truncated" });
17182
- } else {
17183
- reject(`truncate_${truncResult}`);
17184
- }
17185
- updateTagDropMode(db, sessionId, pendingOp.tagId, "truncated");
17670
+ updateTagDropMode(db, sessionId, pendingOp.tagId, strip ? "edit_marker_stripped" : "edit_marker");
17186
17671
  shouldPersistDrop = true;
17187
17672
  } else {
17188
- const dropResult = target?.drop?.() ?? "absent";
17673
+ const { result: dropResult, mode: appliedMode } = applyNewToolDrop(target, {
17674
+ inWindow: skeletonWindow.has(pendingOp.tagId)
17675
+ });
17189
17676
  if (dropResult === "incomplete" || synthetic && dropResult !== "removed" && dropResult !== "truncated") {
17190
17677
  reject(`drop_${dropResult}`);
17191
17678
  continue;
17192
17679
  }
17193
- const appliedMode = dropResult === "truncated" ? "truncated" : "full";
17194
17680
  if (dropResult === "removed" || dropResult === "truncated") {
17195
17681
  didMutateMessage = true;
17196
17682
  operationMutated = true;
17197
- onTagReduced?.({ tagNumber: pendingOp.tagId, mode: appliedMode });
17683
+ onTagReduced?.({
17684
+ tagNumber: pendingOp.tagId,
17685
+ mode: appliedMode === "full" ? "full" : "truncated"
17686
+ });
17198
17687
  } else {
17199
17688
  reject(`drop_${dropResult}`);
17200
17689
  }
@@ -17261,11 +17750,24 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
17261
17750
  if (tag.status === "dropped") {
17262
17751
  const target = targets.get(tag.tagNumber);
17263
17752
  if (tag.type === "tool") {
17264
- if (tag.dropMode === "edit_marker") {
17753
+ if (tag.dropMode === "edit_marker_stripped") {
17754
+ const result = target?.editMarkerStripped?.() ?? "absent";
17755
+ if (result === "truncated")
17756
+ didMutateMessage = true;
17757
+ } else if (tag.dropMode === "edit_marker") {
17265
17758
  const markResult = target?.editMarker?.() ?? "absent";
17266
17759
  if (markResult === "truncated") {
17267
17760
  didMutateMessage = true;
17268
17761
  }
17762
+ } else if (tag.dropMode === "skeleton_stripped") {
17763
+ const result = target?.skeletonStripped?.() ?? "absent";
17764
+ if (result === "truncated")
17765
+ didMutateMessage = true;
17766
+ } else if (tag.dropMode === "skeleton_real") {
17767
+ const result = target?.skeletonReal?.() ?? "absent";
17768
+ if (result === "truncated") {
17769
+ didMutateMessage = true;
17770
+ }
17269
17771
  } else if (tag.dropMode === "truncated") {
17270
17772
  const truncResult = target?.truncate?.() ?? "absent";
17271
17773
  if (truncResult === "truncated") {
@@ -17611,7 +18113,7 @@ function applyCavemanCleanup(sessionId, db, targets, tags, config) {
17611
18113
  else if (targetDepth === DEPTH_ULTRA)
17612
18114
  result.compressedToUltra += 1;
17613
18115
  }
17614
- })();
18116
+ }).immediate();
17615
18117
  const total = result.compressedToLite + result.compressedToFull + result.compressedToUltra;
17616
18118
  if (total > 0) {
17617
18119
  sessionLog(sessionId, `caveman cleanup: compressed ${total} text tags (lite=${result.compressedToLite}, full=${result.compressedToFull}, ultra=${result.compressedToUltra})`);
@@ -19533,6 +20035,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19533
20035
  let any = false;
19534
20036
  for (const occ of occurrences) {
19535
20037
  if (occ.kind === "tool_use" && occ.part.setToolInput) {
20038
+ occ.originalInput ??= occ.part.getToolInput?.() ?? undefined;
19536
20039
  if (occ.part.setToolInput(droppedInputMarker(tagId)))
19537
20040
  any = true;
19538
20041
  } else if (setToolContentOrText(occ.part, sentinel)) {
@@ -19541,6 +20044,28 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19541
20044
  }
19542
20045
  return any ? "truncated" : "absent";
19543
20046
  };
20047
+ const complete = () => occurrences.some((occ) => occ.kind === "tool_use") && occurrences.some((occ) => occ.kind === "tool_result");
20048
+ const skeletonReal = () => {
20049
+ if (!complete())
20050
+ return "incomplete";
20051
+ const sentinel = `[dropped §${tagId}§]`;
20052
+ let any = false;
20053
+ for (const occ of occurrences) {
20054
+ if (occ.kind === "tool_use") {
20055
+ if (occ.originalInput && occ.part.setToolInput?.(occ.originalInput))
20056
+ any = true;
20057
+ } else if (setToolContentOrText(occ.part, sentinel)) {
20058
+ any = true;
20059
+ }
20060
+ }
20061
+ return any ? "truncated" : "absent";
20062
+ };
20063
+ const cannotRemove = () => {
20064
+ if (requiresToolArcSkeleton)
20065
+ return true;
20066
+ const authorization = occurrences.map((occ) => occ.part.canRemove?.());
20067
+ return !occurrences.every((occ, index) => occ.part.remove && authorization[index] !== false);
20068
+ };
19544
20069
  return {
19545
20070
  measureReclaim(skeleton) {
19546
20071
  let beforeTools = 0;
@@ -19551,7 +20076,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19551
20076
  const before = occ.kind === "tool_use" ? JSON.stringify(occ.part.getToolInput?.() ?? {}) : occ.part.getText() ?? "";
19552
20077
  beforeTools += estimateTokens(before);
19553
20078
  if (skeleton || !removable) {
19554
- const after = occ.kind === "tool_use" ? JSON.stringify(droppedInputMarker(tagId)) : `[dropped §${tagId}§]`;
20079
+ const after = occ.kind === "tool_use" ? before : `[dropped §${tagId}§]`;
19555
20080
  afterTools += estimateTokens(after);
19556
20081
  }
19557
20082
  }
@@ -19580,8 +20105,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19580
20105
  return occurrences[0]?.part.getText() ?? null;
19581
20106
  },
19582
20107
  drop() {
19583
- const complete = occurrences.some((occ) => occ.kind === "tool_use") && occurrences.some((occ) => occ.kind === "tool_result");
19584
- if (!complete)
20108
+ if (!complete())
19585
20109
  return "incomplete";
19586
20110
  if (!requiresToolArcSkeleton) {
19587
20111
  const authorization = occurrences.map((occ) => occ.part.canRemove?.());
@@ -19589,7 +20113,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19589
20113
  return "incomplete";
19590
20114
  if (occurrences.every((occ, index) => occ.part.remove && authorization[index] !== false)) {
19591
20115
  if (conversationEnd?.wouldStrand(occurrences))
19592
- return truncate();
20116
+ return skeletonReal();
19593
20117
  let removed = false;
19594
20118
  for (const occ of occurrences)
19595
20119
  removed = occ.part.remove?.() || removed;
@@ -19607,6 +20131,19 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19607
20131
  return any ? "removed" : "absent";
19608
20132
  },
19609
20133
  truncate,
20134
+ skeletonReal,
20135
+ cannotRemove,
20136
+ inputStringBytes() {
20137
+ for (const occ of occurrences) {
20138
+ if (occ.kind !== "tool_use")
20139
+ continue;
20140
+ return toolInputStringBytes(occ.originalInput ?? occ.part.getToolInput?.() ?? null);
20141
+ }
20142
+ return null;
20143
+ },
20144
+ wouldStrandConversationEnd() {
20145
+ return conversationEnd?.wouldStrand(occurrences) ?? false;
20146
+ },
19610
20147
  editMarker() {
19611
20148
  const sentinel = `[dropped §${tagId}§]`;
19612
20149
  let any = false;
@@ -19625,9 +20162,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19625
20162
  }
19626
20163
  return any ? "truncated" : "absent";
19627
20164
  },
19628
- canDrop() {
19629
- return occurrences.some((occ) => occ.kind === "tool_use") && occurrences.some((occ) => occ.kind === "tool_result");
19630
- },
20165
+ canDrop: complete,
19631
20166
  requiresToolArcSkeleton,
19632
20167
  readInput() {
19633
20168
  for (const occ of occurrences) {
@@ -19673,6 +20208,15 @@ function buildToolTarget(part, message, tagId) {
19673
20208
  const changed = part.setToolInput ? part.setToolInput(droppedInputMarker(tagId)) : setToolContentOrText(part, `[dropped §${tagId}§]`);
19674
20209
  return changed ? "truncated" : "absent";
19675
20210
  },
20211
+ skeletonReal() {
20212
+ if (part.setToolInput)
20213
+ return "truncated";
20214
+ return setToolContentOrText(part, `[dropped §${tagId}§]`) ? "truncated" : "absent";
20215
+ },
20216
+ cannotRemove: () => part.setToolInput !== undefined,
20217
+ inputStringBytes() {
20218
+ return part.setToolInput ? toolInputStringBytes(part.getToolInput?.() ?? null) : 0;
20219
+ },
19676
20220
  message: {
19677
20221
  info: { id: message.info.id, role: message.info.role },
19678
20222
  parts: []
@@ -19946,7 +20490,7 @@ async function runAutoSearchHintForPi(args) {
19946
20490
  try {
19947
20491
  const snapshot = getProjectEmbeddingSnapshot(options.projectPath);
19948
20492
  const memoryEnabled = snapshot?.features.memoryEnabled ?? true;
19949
- const embeddingEnabled = snapshot ? snapshot.enabled || snapshot.gitCommitEnabled : true;
20493
+ const embeddingEnabled = snapshot ? snapshot.historyEnabled : true;
19950
20494
  const gitCommitsEnabled = snapshot?.gitCommitEnabled ?? false;
19951
20495
  const searchOptions = {
19952
20496
  limit: 10,
@@ -20010,6 +20554,8 @@ import * as crypto2 from "node:crypto";
20010
20554
  async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
20011
20555
  if (compartments.length === 0)
20012
20556
  return;
20557
+ if (getProjectChunkEmbeddingModelId(projectPath) === "off")
20558
+ return;
20013
20559
  const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
20014
20560
  for (const compartment of compartments) {
20015
20561
  try {
@@ -20166,7 +20712,7 @@ function persistFilteredNoise(db, sessionId, chunk, eligibleEnd) {
20166
20712
  }
20167
20713
  ]);
20168
20714
  return true;
20169
- })();
20715
+ }).immediate();
20170
20716
  if (saved)
20171
20717
  sessionLog(sessionId, `historian skipped ordinals ${start}-${eligibleEnd - 1}: all ${rows.length} raw rows excluded by chunk filters (no-content boundary marker)`);
20172
20718
  return saved;
@@ -22763,8 +23309,7 @@ ${chunkText}`,
22763
23309
  const discardedLast = newCompartments.length < emittedCompartments.length;
22764
23310
  const weakLookaheadFinalCompartment = forceKeepLastCompartmentForChunk;
22765
23311
  const skipUnanchoredPromotion = discardedLast || weakLookaheadFinalCompartment;
22766
- const embeddingActive = memoryEnabled !== false;
22767
- const promotionActive = embeddingActive && autoPromote !== false;
23312
+ const promotionActive = memoryEnabled !== false && autoPromote !== false;
22768
23313
  const publishableEvents = (validatedPass.events ?? []).filter((e) => {
22769
23314
  if (typeof e.atCompartment !== "number")
22770
23315
  return !weakLookaheadFinalCompartment;
@@ -22897,7 +23442,7 @@ ${chunkText}`,
22897
23442
  sessionLog(sessionId, "failed to store primer candidates:", error);
22898
23443
  }
22899
23444
  }
22900
- if (embeddingActive) {
23445
+ if (newCompartments.length > 0) {
22901
23446
  const chunksToEmbed = newCompartments.map((c, i) => ({
22902
23447
  id: persistedIds[i],
22903
23448
  startMessage: c.startMessage,
@@ -23280,7 +23825,7 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23280
23825
  const candidateTags = tags.filter((t) => t.status === "active" && t.type === "tool" && targets.get(t.tagNumber)?.canDrop?.());
23281
23826
  const recentTags = new Set(candidateTags.slice().sort((a, b) => b.tagNumber - a.tagNumber).slice(0, 20).map((tag) => tag.tagNumber));
23282
23827
  const calibration = sessionDecisionCalibration(db, sessionId);
23283
- const activeTags = tags.filter((t) => t.status === "active").map((tag) => measureEmergencyTag(tag, targets.get(tag.tagNumber), calibration, targets.get(tag.tagNumber)?.requiresToolArcSkeleton === true || (emergency.usagePercentage ?? 0) < 95 && recentTags.has(tag.tagNumber)));
23828
+ const activeTags = tags.filter((t) => t.status === "active").map((tag) => measureEmergencyTag(tag, targets.get(tag.tagNumber), calibration, targets.get(tag.tagNumber)?.requiresToolArcSkeleton === true || (emergency.usagePercentage ?? 0) < 95 && recentTags.has(tag.tagNumber) && hasSmallToolInput(targets.get(tag.tagNumber))));
23284
23829
  const byTag = new Map(activeTags.map((tag) => [tag.tagNumber, tag]));
23285
23830
  const droppableTags = candidateTags.flatMap((tag) => {
23286
23831
  const measured = byTag.get(tag.tagNumber);
@@ -23309,18 +23854,18 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23309
23854
  continue;
23310
23855
  const target = targets.get(tag.tagNumber);
23311
23856
  const recent = (emergency.usagePercentage ?? 0) < 95 && newestEmergencyTags.has(tag.tagNumber);
23312
- const reasoningSafeSkeleton = target?.requiresToolArcSkeleton === true;
23313
- const skeleton = recent || reasoningSafeSkeleton;
23314
- const result = reasoningSafeSkeleton ? target?.truncate?.() ?? "absent" : recent ? target?.truncate?.() ?? target?.drop?.() ?? "absent" : target?.drop?.() ?? "absent";
23857
+ const { result, mode } = applyNewToolDrop(target, {
23858
+ inWindow: recent,
23859
+ keepSkeleton: target?.requiresToolArcSkeleton === true
23860
+ });
23315
23861
  if (result === "removed" || result === "truncated") {
23316
- const appliedMode = skeleton || result === "truncated" ? "truncated" : "full";
23317
23862
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
23318
- updateTagDropMode(db, sessionId, tag.tagNumber, appliedMode);
23863
+ updateTagDropMode(db, sessionId, tag.tagNumber, mode);
23319
23864
  droppedTools++;
23320
23865
  emergencyDroppedTools++;
23321
23866
  droppedTokenReductions.push({
23322
23867
  tagNumber: tag.tagNumber,
23323
- mode: appliedMode
23868
+ mode: mode === "full" ? "full" : "truncated"
23324
23869
  });
23325
23870
  }
23326
23871
  }
@@ -23344,10 +23889,10 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23344
23889
  if (!matched)
23345
23890
  continue;
23346
23891
  const target = targets.get(tag.tagNumber);
23347
- const result = target?.drop?.() ?? "absent";
23892
+ const { result, mode } = applyNewToolDrop(target, { inWindow: false });
23348
23893
  if (result === "incomplete")
23349
23894
  continue;
23350
- updateTagDropMode(db, sessionId, tag.tagNumber, result === "truncated" ? "truncated" : "full");
23895
+ updateTagDropMode(db, sessionId, tag.tagNumber, mode);
23351
23896
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
23352
23897
  if (result === "removed" || result === "truncated") {
23353
23898
  droppedStaleReduceCalls++;
@@ -23428,17 +23973,18 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23428
23973
  for (let i = 0;i < group.length - 1; i++) {
23429
23974
  const tag = group[i];
23430
23975
  const target = targets.get(tag.tagNumber);
23431
- const result = target?.drop?.() ?? "absent";
23976
+ const { result, mode } = applyNewToolDrop(target, {
23977
+ inWindow: false
23978
+ });
23432
23979
  if (result === "incomplete")
23433
23980
  continue;
23434
- const appliedMode = result === "truncated" ? "truncated" : "full";
23435
- updateTagDropMode(db, sessionId, tag.tagNumber, appliedMode);
23981
+ updateTagDropMode(db, sessionId, tag.tagNumber, mode);
23436
23982
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
23437
23983
  if (result === "removed" || result === "truncated") {
23438
23984
  deduplicatedTools++;
23439
23985
  droppedTokenReductions.push({
23440
23986
  tagNumber: tag.tagNumber,
23441
- mode: appliedMode
23987
+ mode: mode === "full" ? "full" : "truncated"
23442
23988
  });
23443
23989
  }
23444
23990
  }
@@ -26204,7 +26750,7 @@ function messageRole(message) {
26204
26750
  const role = recordValue(message, "role");
26205
26751
  return typeof role === "string" ? role : undefined;
26206
26752
  }
26207
- function messageId(message) {
26753
+ function messageId2(message) {
26208
26754
  const id = recordValue(messageInfo(message), "id") ?? recordValue(message, "id");
26209
26755
  return typeof id === "string" && id.length > 0 ? id : null;
26210
26756
  }
@@ -26337,7 +26883,7 @@ function validateLkgSeam(prefix, tail, providerKey) {
26337
26883
  const calls = new Set;
26338
26884
  const results = new Set;
26339
26885
  for (const message of all) {
26340
- const id = messageId(message);
26886
+ const id = messageId2(message);
26341
26887
  if (id !== null) {
26342
26888
  if (ids.has(id))
26343
26889
  return false;
@@ -26880,6 +27426,56 @@ function resolvePiPressureSnapshot(args) {
26880
27426
  ...validInferredLimit ? { contextLimit: inferredLimit } : {}
26881
27427
  };
26882
27428
  }
27429
+ var estimateSetAsideLogged = new Set;
27430
+ function notePiUsageReadingUsed(sessionId) {
27431
+ estimateSetAsideLogged.delete(sessionId);
27432
+ }
27433
+ function entryUsageTokens(usage) {
27434
+ if (!usage || typeof usage !== "object")
27435
+ return 0;
27436
+ const u = usage;
27437
+ const n = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
27438
+ return n(u.totalTokens) || n(u.input) + n(u.output) + n(u.cacheRead) + n(u.cacheWrite);
27439
+ }
27440
+ function isPiLiveUsageRawBranchEstimate(branchEntries) {
27441
+ if (!branchEntries)
27442
+ return false;
27443
+ for (let index = branchEntries.length - 1;index >= 0; index -= 1) {
27444
+ const entry = branchEntries[index];
27445
+ if (!entry || typeof entry !== "object")
27446
+ continue;
27447
+ if (entry.type === "context_edit" || entry.type === "compaction")
27448
+ return true;
27449
+ if (entry.type !== "message")
27450
+ continue;
27451
+ const message = entry.message;
27452
+ if (message?.role === "assistant" && message.stopReason !== "aborted" && message.stopReason !== "error" && entryUsageTokens(message.usage) > 0) {
27453
+ return false;
27454
+ }
27455
+ }
27456
+ return false;
27457
+ }
27458
+ function noteRawBranchEstimateSetAside(sessionId, reading, source) {
27459
+ if (estimateSetAsideLogged.has(sessionId))
27460
+ return;
27461
+ estimateSetAsideLogged.add(sessionId);
27462
+ sessionLog(sessionId, `usage reading ${reading} set aside (${source}): Pi re-estimated its whole unreduced session branch because a context edit or compaction follows the last recorded usage (for example after a retried request); keeping the previous reading until provider usage arrives`);
27463
+ }
27464
+ function resolvePiPressureSnapshotWithEstimateGuard(args) {
27465
+ const live = typeof args.liveInputTokens === "number" && Number.isFinite(args.liveInputTokens) ? args.liveInputTokens : 0;
27466
+ const setAsideEstimate = args.liveIsRawBranchEstimate === true && live > 0;
27467
+ const snapshot = resolvePiPressureSnapshot(setAsideEstimate ? {
27468
+ ...args,
27469
+ liveInputTokens: undefined,
27470
+ ...args.persistedFromLive ? { persistedInputTokens: 0, persistedPercentage: 0 } : {}
27471
+ } : args);
27472
+ if (setAsideEstimate) {
27473
+ noteRawBranchEstimateSetAside(args.sessionId, live, args.source);
27474
+ } else if (snapshot.inputTokens > 0) {
27475
+ notePiUsageReadingUsed(args.sessionId);
27476
+ }
27477
+ return snapshot;
27478
+ }
26883
27479
  function formatPiPressureForLog(snapshot) {
26884
27480
  return `usage=${snapshot.percentage.toFixed(1)}% (${snapshot.inputTokens} tokens, limit=${snapshot.contextLimit ?? "?"})`;
26885
27481
  }
@@ -27336,12 +27932,12 @@ function handlePiProviderFailure(args) {
27336
27932
  const model = typeof message.model === "string" ? message.model : undefined;
27337
27933
  const binding = detectThinkingBindingMismatch(message.errorMessage);
27338
27934
  if (binding.isBindingMismatch) {
27339
- const enabled = args.thinkingBindingRecoveryEnabled !== false && !args.compactionOff && isFable51ThinkingBindingModel(provider, model);
27935
+ const enabled = args.thinkingBindingRecoveryEnabled !== false && !args.compactionOff && isPrefixBoundThinkingModel(provider, model);
27340
27936
  if (enabled) {
27341
27937
  armThinkingBindingRecovery(args.db, args.sessionId);
27342
27938
  clearPiLkgSessionState(args.sessionId);
27343
27939
  dropSlot(args.sessionId, "thinking-binding-recovery-arm");
27344
- reportBindingRecovery(args.sessionId, args.report, `Fable thinking-binding recovery armed from message_end target=${NEWEST_REASONING_BEARING_ASSISTANT}`);
27940
+ reportBindingRecovery(args.sessionId, args.report, `thinking-binding recovery armed from message_end (provider paths: failing=${binding.failingBlockPath ?? "?"} firstChanged=${binding.firstChangedPath ?? "?"})`);
27345
27941
  }
27346
27942
  return { kind: "thinking_binding", armed: enabled };
27347
27943
  }
@@ -27390,36 +27986,25 @@ function stripThinkingParts(message) {
27390
27986
  function applyPiThinkingBindingRecovery(args) {
27391
27987
  if (args.provider?.toLowerCase() !== "anthropic")
27392
27988
  return null;
27393
- const frozenEntryIds = new Set;
27394
- for (const frozenId of getMergedReasoningStrippedIds(args.db, args.sessionId)) {
27395
- if (!frozenId.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX))
27396
- continue;
27397
- const entryId = frozenId.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length);
27398
- if (entryId.length > 0)
27399
- frozenEntryIds.add(entryId);
27400
- }
27401
- const flagTarget = isFable51ThinkingBindingModel(args.provider, args.model) ? getThinkingBindingRecoveryTarget(args.db, args.sessionId) : null;
27989
+ const frozenEntryIds = frozenBindingEntryIds(args.db, args.sessionId);
27990
+ const flagTarget = isPrefixBoundThinkingModel(args.provider, args.model) ? getThinkingBindingRecoveryTarget(args.db, args.sessionId) : null;
27402
27991
  let applied = null;
27403
27992
  if (flagTarget) {
27404
- let messageIndex = -1;
27405
- if (flagTarget === NEWEST_REASONING_BEARING_ASSISTANT) {
27406
- for (let index = args.messages.length - 1;index >= 0; index -= 1) {
27407
- if (hasThinkingPart(args.messages[index]) && args.entryIds[index]) {
27408
- messageIndex = index;
27409
- break;
27410
- }
27411
- }
27412
- } else {
27413
- messageIndex = args.entryIds.findIndex((entryId, index) => entryId === flagTarget && hasThinkingPart(args.messages[index]));
27414
- }
27415
- const entryId = messageIndex >= 0 ? args.entryIds[messageIndex] : undefined;
27416
- if (entryId) {
27417
- const frozenId = thinkingBindingRecoveryFrozenId(entryId);
27418
- if (frozenEntryIds.has(entryId) || addMergedReasoningStrippedIds(args.db, args.sessionId, [frozenId])) {
27419
- frozenEntryIds.add(entryId);
27420
- applied = { flagTarget, entryId };
27421
- reportBindingRecovery(args.sessionId, args.report, `Fable thinking-binding recovery consumed on context pass target=${flagTarget} entry=${entryId}`);
27422
- }
27993
+ const entryIds = new Set;
27994
+ for (let index = 0;index < args.messages.length; index += 1) {
27995
+ const entryId = args.entryIds[index];
27996
+ if (entryId && hasThinkingPart(args.messages[index]))
27997
+ entryIds.add(entryId);
27998
+ }
27999
+ const newEntryIds = [...entryIds].filter((id) => !frozenEntryIds.has(id));
28000
+ if (newEntryIds.length === 0 || addMergedReasoningStrippedIds(args.db, args.sessionId, [
28001
+ ...newEntryIds.map(thinkingBindingRecoveryFrozenId),
28002
+ ...args.endOfPassOrder ? [THINKING_BINDING_STRIP_ORDER_END_MARKER] : []
28003
+ ])) {
28004
+ for (const id of newEntryIds)
28005
+ frozenEntryIds.add(id);
28006
+ applied = { flagTarget, entryIds: [...entryIds] };
28007
+ reportBindingRecovery(args.sessionId, args.report, `thinking-binding recovery consumed on context pass target=${flagTarget} entries=${applied.entryIds.length} [${applied.entryIds.join(",")}]`);
27423
28008
  }
27424
28009
  }
27425
28010
  for (let index = 0;index < args.messages.length; index += 1) {
@@ -27429,6 +28014,76 @@ function applyPiThinkingBindingRecovery(args) {
27429
28014
  }
27430
28015
  return applied;
27431
28016
  }
28017
+ function applyPiProactiveThinkingStrip(args) {
28018
+ if (!args.cacheBustingPass)
28019
+ return null;
28020
+ if (!isPrefixBoundThinkingModel(args.provider, args.model))
28021
+ return null;
28022
+ const entryIds = [];
28023
+ const indices = [];
28024
+ for (let index = 0;index < args.messages.length; index += 1) {
28025
+ const entryId = args.entryIds[index];
28026
+ if (!entryId || !hasThinkingPart(args.messages[index]))
28027
+ continue;
28028
+ entryIds.push(entryId);
28029
+ indices.push(index);
28030
+ }
28031
+ if (entryIds.length === 0)
28032
+ return null;
28033
+ let persisted = false;
28034
+ try {
28035
+ persisted = addMergedReasoningStrippedIds(args.db, args.sessionId, [
28036
+ ...entryIds.map(thinkingBindingRecoveryFrozenId),
28037
+ THINKING_BINDING_STRIP_ORDER_END_MARKER
28038
+ ]);
28039
+ } catch {
28040
+ persisted = false;
28041
+ }
28042
+ if (!persisted) {
28043
+ reportBindingRecovery(args.sessionId, args.report, "proactive thinking strip: persistence failed; serving the thinking unchanged");
28044
+ return null;
28045
+ }
28046
+ for (const index of indices)
28047
+ stripThinkingParts(args.messages[index]);
28048
+ reportBindingRecovery(args.sessionId, args.report, `proactive thinking strip: busting pass froze thinking of ${entryIds.length} entr${entryIds.length === 1 ? "y" : "ies"} [${entryIds.join(",")}]`);
28049
+ return { entryIds };
28050
+ }
28051
+ function frozenBindingEntryIds(db, sessionId) {
28052
+ const ids = new Set;
28053
+ for (const entry of getMergedReasoningStrippedIds(db, sessionId)) {
28054
+ if (!entry.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX))
28055
+ continue;
28056
+ const entryId = entry.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length);
28057
+ if (entryId.length > 0)
28058
+ ids.add(entryId);
28059
+ }
28060
+ return ids;
28061
+ }
28062
+ function resolvePiBindingStripOrder(args) {
28063
+ const ledger = getMergedReasoningStrippedIds(args.db, args.sessionId);
28064
+ if (ledger.has(THINKING_BINDING_STRIP_ORDER_END_MARKER))
28065
+ return "end";
28066
+ const frozen = frozenBindingEntryIds(args.db, args.sessionId);
28067
+ if (frozen.size === 0)
28068
+ return "end";
28069
+ let onBranch = false;
28070
+ let carriesThinking = false;
28071
+ for (let index = 0;index < args.messages.length; index += 1) {
28072
+ const entryId = args.entryIds[index];
28073
+ if (!entryId || !frozen.has(entryId))
28074
+ continue;
28075
+ onBranch = true;
28076
+ if (hasThinkingPart(args.messages[index]))
28077
+ carriesThinking = true;
28078
+ }
28079
+ const mayChangeOrder = onBranch && carriesThinking && args.bustPermittedAtStart;
28080
+ if (mayChangeOrder && addMergedReasoningStrippedIds(args.db, args.sessionId, [
28081
+ THINKING_BINDING_STRIP_ORDER_END_MARKER
28082
+ ])) {
28083
+ return "end";
28084
+ }
28085
+ return "start";
28086
+ }
27432
28087
 
27433
28088
  // src/reasoning-replay-pi.ts
27434
28089
  var INLINE_THINKING_PATTERNS = [
@@ -27976,9 +28631,9 @@ var BASE_INTRO = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `#
27976
28631
 
27977
28632
  Think of your context as a desk. Every message and every tool output lands on it, and each item arrives with a §N§ tag (§1§, §42§) — the tag is the item's handle.
27978
28633
 
27979
- When an item no longer needs to stay on the desk for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping does not remove anything — the item stays on the desk, fully readable. From time to time, when stamped items have piled up and the desk needs room, Magic Context clears them all in one sweep; you don't pick the moment, you only stamp. Stamp as soon as an item has served its purpose, not at the end of the turn, and do it silently: nobody wants to read "I'll drop these outputs". Never stamp a user message for what it asks of you; a large paste inside one is fine once you have used it.
28634
+ When an item no longer needs to stay on the desk for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping does not remove anything — the item stays on the desk, fully readable. From time to time, when stamped items have piled up and the desk needs room, Magic Context clears them all in one sweep; you don't pick the moment, you only stamp. Stamp as soon as an item has served its purpose, not at the end of the turn, and do it silently: nobody wants to read "I'll drop these outputs". An output is used once you've taken what you need from it into your reasoning or your answer. Never stamp a user message for what it asks of you; a large paste inside one is fine once you have used it.
27980
28635
 
27981
- Nothing stamped is ever lost. A cleared item goes to the archive — a recent one leaves a \`[dropped §N§]\` placeholder on the desk, an older one leaves nothing — and \`ctx_expand(message=N)\` brings it back whole: text, tool input, tool output. Now and then Magic Context leaves a short reminder on the desk saying how much unstamped material is lying around; that is housekeeping, not a warning, and the desk never gets smaller for it.
28636
+ Nothing stamped is ever lost. A cleared item goes to the archive — a recent one leaves a \`[dropped §N§]\` placeholder on the desk, an older one leaves nothing — and \`ctx_expand(message=N)\` brings it back whole: text, tool input, tool output. Now and then Magic Context leaves a short reminder on the desk saying how much unstamped material is lying around. A reminder means stamping is overdue: act on it in your next step. It isn't a warning about the desk's size. The desk never gets smaller for it.
27982
28637
 
27983
28638
  ${TOOL_HISTORY_GUIDANCE}
27984
28639
 
@@ -28007,7 +28662,7 @@ var SMART_NOTE_GUIDANCE_LIGHT = `; a \`surface_condition\` leaves the note with
28007
28662
  var TEMPORAL_AWARENESS_GUIDANCE_LIGHT = `, \`<!-- +Xm -->\` (time since your last reply)`;
28008
28663
  var BASE_INTRO_LIGHT = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28009
28664
 
28010
- Your context is a desk. Every message and tool output lands on it with a §N§ tag as its handle. When an item no longer needs to stay for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping removes nothing — the item stays readable; from time to time, when stamped items have piled up and the desk needs room, Magic Context clears them in one sweep (you only stamp, you don't pick the moment). Stamp as soon as an item has served its purpose, silently; never stamp a user message for what it asks (a used paste inside one is fine). Nothing is lost: a cleared item goes to the archive (a recent one leaves \`[dropped §N§]\`, an older one nothing) and \`ctx_expand(message=N)\` brings it back whole. Occasional reminders about unstamped material are housekeeping, not warnings; the desk never gets smaller.
28665
+ Your context is a desk. Every message and tool output lands on it with a §N§ tag as its handle. When an item no longer needs to stay for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping removes nothing — the item stays readable; from time to time, when stamped items have piled up and the desk needs room, Magic Context clears them in one sweep (you only stamp, you don't pick the moment). Stamp as soon as an item has served its purpose, silently; never stamp a user message for what it asks (a used paste inside one is fine). An output is used once you've taken what you need from it into your reasoning or your answer. Nothing is lost: a cleared item goes to the archive (a recent one leaves \`[dropped §N§]\`, an older one nothing) and \`ctx_expand(message=N)\` brings it back whole. Occasional reminders about unstamped material may appear. A reminder means stamping is overdue: act on it in your next step. It isn't a warning about the desk's size. The desk never gets smaller.
28011
28666
 
28012
28667
  ${TOOL_HISTORY_GUIDANCE_LIGHT}
28013
28668
 
@@ -29490,7 +30145,7 @@ function databaseIsInTransaction(db) {
29490
30145
  }
29491
30146
  function runImmediateTransaction(db, fn) {
29492
30147
  if (databaseIsInTransaction(db)) {
29493
- return db.transaction(fn)();
30148
+ return db.transaction(fn).immediate();
29494
30149
  }
29495
30150
  const transactionStartedAt = performance.now();
29496
30151
  db.exec("BEGIN IMMEDIATE");
@@ -29672,6 +30327,7 @@ function registerPiContextHandler(pi, baseOptions) {
29672
30327
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
29673
30328
  const tEntryBranch = performance.now();
29674
30329
  const branchEntries = readPiBranchEntriesForContext(ctx, sessionId);
30330
+ const piLiveUsageIsRawBranchEstimate = isPiLiveUsageRawBranchEstimate(branchEntries);
29675
30331
  schedulePiTransformDecisionResolve({
29676
30332
  db: options.db,
29677
30333
  sessionId,
@@ -29704,14 +30360,29 @@ function registerPiContextHandler(pi, baseOptions) {
29704
30360
  const lkgInputIdByRef = new Map;
29705
30361
  for (const input of lkgPassSnapshot.inputs)
29706
30362
  lkgInputIdByRef.set(event.messages[input.messageIndex], input.id);
29707
- const thinkingBindingRecoveryApplied = applyPiThinkingBindingRecovery({
30363
+ const bindingStripOrder = resolvePiBindingStripOrder({
30364
+ db: options.db,
30365
+ sessionId,
30366
+ messages: event.messages,
30367
+ entryIds: lkgEntryIds ?? [],
30368
+ bustPermittedAtStart: hasPendingMaterialization(sessionId)
30369
+ });
30370
+ const startOfPassBindingRecovery = bindingStripOrder === "start" ? applyPiThinkingBindingRecovery({
29708
30371
  db: options.db,
29709
30372
  sessionId,
29710
30373
  messages: event.messages,
29711
30374
  entryIds: lkgEntryIds ?? [],
29712
30375
  provider: lkgProviderKey ?? undefined,
29713
30376
  model: ctx.model?.id
29714
- });
30377
+ }) : null;
30378
+ const bindingEntryIdByRef = new Map;
30379
+ if (lkgEntryIds) {
30380
+ for (let index = 0;index < event.messages.length; index += 1) {
30381
+ const entryId = lkgEntryIds[index];
30382
+ if (entryId)
30383
+ bindingEntryIdByRef.set(event.messages[index], entryId);
30384
+ }
30385
+ }
29715
30386
  const tMeta = performance.now();
29716
30387
  const sessionMetaForUsage = getOrCreateSessionMeta(options.db, sessionId);
29717
30388
  sessionMetaForPass = sessionMetaForUsage;
@@ -29921,7 +30592,11 @@ function registerPiContextHandler(pi, baseOptions) {
29921
30592
  if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
29922
30593
  usagePercentage = usageInputTokens / usageContextLimit * 100;
29923
30594
  }
29924
- ({ percentage: usagePercentage, inputTokens: usageInputTokens } = resolvePiPressureSnapshot({
30595
+ ({ percentage: usagePercentage, inputTokens: usageInputTokens } = resolvePiPressureSnapshotWithEstimateGuard({
30596
+ sessionId,
30597
+ source: "transform",
30598
+ liveIsRawBranchEstimate: piLiveUsageIsRawBranchEstimate,
30599
+ persistedFromLive: !usedPersistedUsage,
29925
30600
  persistedPercentage: usagePercentage,
29926
30601
  persistedInputTokens: usageInputTokens,
29927
30602
  liveInputTokens: piUsage?.tokens,
@@ -30099,6 +30774,7 @@ function registerPiContextHandler(pi, baseOptions) {
30099
30774
  sessionMeta,
30100
30775
  piUsage,
30101
30776
  minimumPercentage: usagePercentage,
30777
+ liveIsRawBranchEstimate: piLiveUsageIsRawBranchEstimate,
30102
30778
  historianStateSnapshot: historianStateForPass,
30103
30779
  publishedHistoryRide: isCacheBusting
30104
30780
  });
@@ -30176,6 +30852,34 @@ function registerPiContextHandler(pi, baseOptions) {
30176
30852
  sessionLog(sessionId, `synthetic todowrite injection failed: ${err instanceof Error ? err.message : String(err)}`);
30177
30853
  }
30178
30854
  logTransformTiming(sessionId, "todoCapture", tTodoCapture);
30855
+ const tThinkingBinding = performance.now();
30856
+ const outputEntryIds = resolvePiLkgOutputEntryIds(outputMessages, result.syntheticLeadingCount, (message) => result.lkgEntryIdByRef.get(message) ?? result.postCommitEntryIdByRef.get(message) ?? bindingEntryIdByRef.get(message)).map((entryId) => entryId ?? undefined);
30857
+ const thinkingBindingRecoveryApplied = bindingStripOrder === "end" ? applyPiThinkingBindingRecovery({
30858
+ db: options.db,
30859
+ sessionId,
30860
+ messages: outputMessages,
30861
+ entryIds: outputEntryIds,
30862
+ provider: lkgProviderKey ?? undefined,
30863
+ model: ctx.model?.id,
30864
+ endOfPassOrder: true
30865
+ }) : startOfPassBindingRecovery;
30866
+ if (!options.compactionOff && !sessionMeta.isSubagent && bindingStripOrder === "end") {
30867
+ try {
30868
+ applyPiProactiveThinkingStrip({
30869
+ db: options.db,
30870
+ sessionId,
30871
+ messages: outputMessages,
30872
+ entryIds: outputEntryIds,
30873
+ provider: lkgProviderKey ?? undefined,
30874
+ model: ctx.model?.id,
30875
+ cacheBustingPass: isCacheBusting || result.executedWorkThisPass,
30876
+ report: (line) => sessionLog(sessionId, line)
30877
+ });
30878
+ } catch (err) {
30879
+ sessionLog(sessionId, `proactive thinking strip failed: ${err instanceof Error ? err.message : String(err)}`);
30880
+ }
30881
+ }
30882
+ logTransformTiming(sessionId, "thinkingBinding", tThinkingBinding);
30179
30883
  const tChannelAccounting = performance.now();
30180
30884
  try {
30181
30885
  const sessionMetaForCh1 = sessionMeta;
@@ -30655,7 +31359,11 @@ function maybeFireHistorian(args) {
30655
31359
  };
30656
31360
  usageSource = "piUsage fallback";
30657
31361
  }
30658
- usage = resolvePiPressureSnapshot({
31362
+ usage = resolvePiPressureSnapshotWithEstimateGuard({
31363
+ sessionId,
31364
+ source: "historian trigger",
31365
+ liveIsRawBranchEstimate: args.liveIsRawBranchEstimate,
31366
+ persistedFromLive: usageSource === "piUsage fallback",
30659
31367
  persistedPercentage: usage.percentage,
30660
31368
  persistedInputTokens: usage.inputTokens,
30661
31369
  liveInputTokens: piUsage?.tokens,
@@ -30978,6 +31686,7 @@ async function runPipeline(args) {
30978
31686
  const foldDueDecision = piM0State ? mustMaterializePi(piM0State, args.db, injectionPassSnapshot?.compartments, injectionPassSnapshot) : { value: false, reason: null };
30979
31687
  const firstRenderBust = piM0State !== undefined && !(injectionPassSnapshot?.cachedRow?.cached_m0_bytes && injectionPassSnapshot.cachedRow.cached_m1_bytes);
30980
31688
  let foldExecutedThisPass = false;
31689
+ let foldBustsServedPrefixThisPass = false;
30981
31690
  let publishedM1RefreshedThisPass = false;
30982
31691
  let prefixPreflightContended = false;
30983
31692
  const softRefreshOpportunity = args.schedulerDecision === "execute";
@@ -30991,6 +31700,18 @@ async function runPipeline(args) {
30991
31700
  prefixPreflightContended = preFoldInjectionResult.contentionExhausted === true;
30992
31701
  publishedM1RefreshedThisPass = !prefixPreflightContended && persistedM0BeforeFold.cachedM1Bytes?.toString("utf8") !== getOrCreateSessionMeta(args.db, args.sessionId).cachedM1Bytes?.toString("utf8");
30993
31702
  foldExecutedThisPass = foldExecutesThisPass(foldDueDecision.value || softRefreshOpportunity, preFoldInjectionResult.m0Materialized === true);
31703
+ if (foldExecutedThisPass) {
31704
+ const afterFold = getOrCreateSessionMeta(args.db, args.sessionId);
31705
+ foldBustsServedPrefixThisPass = foldBustsServedPrefix(foldDueDecision.reason, {
31706
+ m0Bytes: persistedM0BeforeFold.cachedM0Bytes ?? null,
31707
+ m1Bytes: persistedM0BeforeFold.cachedM1Bytes ?? null,
31708
+ muralDataUrl: persistedM0BeforeFold.cachedM0MuralDataUrl ?? null
31709
+ }, {
31710
+ m0Bytes: afterFold.cachedM0Bytes ?? null,
31711
+ m1Bytes: afterFold.cachedM1Bytes ?? null,
31712
+ muralDataUrl: afterFold.cachedM0MuralDataUrl ?? null
31713
+ });
31714
+ }
30994
31715
  if (preFoldInjectionResult.m0Materialized) {
30995
31716
  injectionPassSnapshot = createPiM0M1PassSnapshot({
30996
31717
  db: args.db,
@@ -31018,7 +31739,7 @@ async function runPipeline(args) {
31018
31739
  sessionLog(args.sessionId, `pi m[0] HARD fold pre-execution failed: ${error instanceof Error ? error.message : String(error)}`);
31019
31740
  }
31020
31741
  const mismatch = foldDueDecision.mismatch ? ` mismatch=${JSON.stringify(foldDueDecision.mismatch)}` : "";
31021
- sessionLog(args.sessionId, `pi m[0] HARD fold decision: reason=${foldDueDecision.reason ?? "unknown"}${mismatch} executed=${foldExecutedThisPass}`);
31742
+ sessionLog(args.sessionId, `pi m[0] HARD fold decision: reason=${foldDueDecision.reason ?? "unknown"}${mismatch} executed=${foldExecutedThisPass} bustsServedPrefix=${foldBustsServedPrefixThisPass}`);
31022
31743
  }
31023
31744
  const executePressureEligible = args.schedulerDecision === "execute" || emergencyDropEligible;
31024
31745
  if (!executePressureEligible) {
@@ -31029,7 +31750,7 @@ async function runPipeline(args) {
31029
31750
  const routinePressureAlreadyApplied = !args.sessionMeta.isSubagent && executePressureEligible && routinePressureAppliedBySession.get(args.sessionId) === true;
31030
31751
  const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
31031
31752
  const rideSignals = {
31032
- hardFold: foldExecutedThisPass || firstRenderBust,
31753
+ hardFold: foldBustsServedPrefixThisPass || firstRenderBust,
31033
31754
  force: (args.forceMaterialization === true || emergencyDropEligible) && (args.contextUsage.percentage >= 95 || getEmergencyInputSample(args.db, args.sessionId) === 0),
31034
31755
  explicitFlush: hasPendingMaterializeSignal || deferredMaterializeEligible && !prefixPreflightContended,
31035
31756
  publishedHistory: !prefixPreflightContended && (args.isCacheBusting || publishedM1RefreshedThisPass || canConsumeDeferredLate && deferredHistoryWasPendingAtPassStart)
@@ -31045,7 +31766,7 @@ async function runPipeline(args) {
31045
31766
  onRejectedProjectOverride: (warning) => sessionLog(args.sessionId, warning)
31046
31767
  });
31047
31768
  let protectionFloorResolution = resolveProtectionFloor();
31048
- const shouldRunHeuristics = args.heuristics !== undefined && isCacheBustingPass && (rideSignals.publishedHistory || args.forceMaterialization === true || hasPendingMaterializeSignal || deferredMaterializeEligible || foldExecutedThisPass || firstRenderBust || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
31769
+ const shouldRunHeuristics = args.heuristics !== undefined && isCacheBustingPass && (rideSignals.publishedHistory || args.forceMaterialization === true || hasPendingMaterializeSignal || deferredMaterializeEligible || foldBustsServedPrefixThisPass || firstRenderBust || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
31049
31770
  const tFallbackIdentity = performance.now();
31050
31771
  const hasFallbackMessageTags = hasPiFallbackMessageTags(args.db, args.sessionId);
31051
31772
  const entryFingerprintByMessageId = buildEntryFingerprintMap(args.messages, stableIdResolver, args.reusableMessageIds, hasFallbackMessageTags);
@@ -31085,6 +31806,19 @@ async function runPipeline(args) {
31085
31806
  } : undefined
31086
31807
  });
31087
31808
  logTransformTiming(args.sessionId, "tagMessages", tTag);
31809
+ if (foldBustsServedPrefixThisPass) {
31810
+ try {
31811
+ let converted = 0;
31812
+ args.db.transaction(() => {
31813
+ converted = convertLegacyToolSkeletons(args.db, args.sessionId, targets).size;
31814
+ }).immediate();
31815
+ if (converted > 0) {
31816
+ sessionLog(args.sessionId, `pi HARD fold converted ${converted} legacy dropped-tool skeleton(s) to real-or-absent`);
31817
+ }
31818
+ } catch (error) {
31819
+ sessionLog(args.sessionId, `pi legacy dropped-tool skeleton conversion failed (kept legacy bytes): ${error instanceof Error ? error.message : String(error)}`);
31820
+ }
31821
+ }
31088
31822
  try {
31089
31823
  if (!args.sessionMeta.isSubagent) {
31090
31824
  const hasRecentCommit = detectRecentCommit(args.messages);
@@ -31111,12 +31845,13 @@ async function runPipeline(args) {
31111
31845
  mutationGateObserverForTests?.({
31112
31846
  foldDue: foldDueDecision.value,
31113
31847
  foldExecuted: foldExecutedThisPass,
31848
+ foldBustsServedPrefix: foldBustsServedPrefixThisPass,
31114
31849
  shouldApplyPendingOps,
31115
31850
  shouldRunHeuristics,
31116
31851
  shouldRunReasoningCleanup: args.reasoningClearing !== undefined && shouldRunHeuristics
31117
31852
  });
31118
31853
  if (shouldApplyPendingOps) {
31119
- const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : foldExecutedThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : `${reclaimRideLabel(rideSignals)} (scheduler=${args.schedulerDecision})`;
31854
+ const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : foldBustsServedPrefixThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : `${reclaimRideLabel(rideSignals)} (scheduler=${args.schedulerDecision})`;
31120
31855
  const pendingOpsDepth = getPendingOpsCount(args.db, args.sessionId);
31121
31856
  const pendingDecisionLog = `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOpsDepth === null ? "not loaded (deferred pass)" : pendingOpsDepth} context=${args.contextUsage.percentage.toFixed(1)}%`;
31122
31857
  sessionLog(args.sessionId, pendingDecisionLog);
@@ -31203,7 +31938,7 @@ async function runPipeline(args) {
31203
31938
  const activeTags = allTagsForPass.filter((tag) => tag.status === "active");
31204
31939
  logTransformTiming(args.sessionId, "getTagsBySessionSnapshot", tActiveTags, `all=${allTagsForPass.length} active=${activeTags.length}`);
31205
31940
  if (shouldRunHeuristics) {
31206
- const reason = args.forceMaterialization ? "force_materialization" : foldExecutedThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : `${reclaimRideLabel(rideSignals)} (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
31941
+ const reason = args.forceMaterialization ? "force_materialization" : foldBustsServedPrefixThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : `${reclaimRideLabel(rideSignals)} (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
31207
31942
  const heuristicsDecisionLog = `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=n/a`;
31208
31943
  sessionLog(args.sessionId, heuristicsDecisionLog);
31209
31944
  pendingDecisionLogObserverForTests?.(heuristicsDecisionLog);
@@ -31216,7 +31951,7 @@ async function runPipeline(args) {
31216
31951
  if (shouldRunHeuristics && args.heuristics) {
31217
31952
  try {
31218
31953
  const tHeuristic = performance.now();
31219
- const independentMutationBeforeHeuristics = pendingOpsDidMutate || foldExecutedThisPass;
31954
+ const independentMutationBeforeHeuristics = pendingOpsDidMutate || foldBustsServedPrefixThisPass;
31220
31955
  if (args.forceMaterialization === true && independentMutationBeforeHeuristics && getEmergencyInputSample(args.db, args.sessionId) > 0) {
31221
31956
  clearEmergencyDropSample(args.db, args.sessionId);
31222
31957
  }
@@ -31991,6 +32726,7 @@ ${result}`;
31991
32726
  }
31992
32727
 
31993
32728
  // ../plugin/src/features/magic-context/store-generation-rebase.ts
32729
+ var rebasesInFlight = new WeakMap;
31994
32730
  var repairCheckedSessions = new WeakMap;
31995
32731
 
31996
32732
  // ../plugin/src/hooks/magic-context/historian-state-file.ts
@@ -32195,7 +32931,7 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
32195
32931
  clearInjectionCache(sessionId);
32196
32932
  }
32197
32933
  promoted.facts;
32198
- if (deps.memoryEnabled !== false) {
32934
+ {
32199
32935
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
32200
32936
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
32201
32937
  const liveCompartments = getCompartments(db, sessionId);
@@ -32412,7 +33148,7 @@ Another process acquired the compartment-state lease before recomp could publish
32412
33148
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd, compartmentTagKeys);
32413
33149
  }
32414
33150
  deps.onCompartmentStatePublished?.(sessionId);
32415
- if (deps.memoryEnabled !== false) {
33151
+ {
32416
33152
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
32417
33153
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
32418
33154
  const liveCompartments = getCompartments(db, sessionId);
@@ -32574,7 +33310,7 @@ Could not acquire the compartment-state lease for this session.`;
32574
33310
  clearInjectionCache(sessionId);
32575
33311
  }
32576
33312
  deps.onCompartmentStatePublished?.(sessionId);
32577
- if (deps.memoryEnabled !== false) {
33313
+ {
32578
33314
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
32579
33315
  const liveCompartments = getCompartments(db, sessionId);
32580
33316
  const chunksToEmbed = liveCompartments.map((c) => ({
@@ -33601,6 +34337,11 @@ function compressionText(summary) {
33601
34337
  return "Waiting for enough conversation history";
33602
34338
  }
33603
34339
  }
34340
+ function runnerText(status) {
34341
+ const why = status.source === "configured" ? "configured" : `default for harness ${status.harness || "unknown"}`;
34342
+ const observed = status.observed === "last_completion" ? "" : " · no completion yet";
34343
+ return `${status.runner} (${why})${observed}`;
34344
+ }
33604
34345
  function reclaimableText(summary) {
33605
34346
  const count = Math.max(0, Math.floor(summary.reclaimable.toolOutputCount));
33606
34347
  if (count === 0)
@@ -33642,6 +34383,12 @@ function renderUserStatusSummary(summary, style) {
33642
34383
  ],
33643
34384
  ["Search indexing", embeddingText(summary)]
33644
34385
  ];
34386
+ if (summary.historianRunner) {
34387
+ values.push(["Historian runner", runnerText(summary.historianRunner)]);
34388
+ }
34389
+ if (summary.dreamerRunner) {
34390
+ values.push(["Dreamer runner", runnerText(summary.dreamerRunner)]);
34391
+ }
33645
34392
  if (summary.historianRefusal) {
33646
34393
  values.push([
33647
34394
  "Historian refusal",
@@ -34120,7 +34867,7 @@ function buildStatusView(source, options) {
34120
34867
  // package.json
34121
34868
  var package_default = {
34122
34869
  name: "@cortexkit/pi-magic-context",
34123
- version: "0.43.2",
34870
+ version: "0.44.0",
34124
34871
  type: "module",
34125
34872
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
34126
34873
  main: "dist/index.js",
@@ -34147,6 +34894,7 @@ var package_default = {
34147
34894
  "dist",
34148
34895
  "README.md"
34149
34896
  ],
34897
+ "//": "The build intentionally bundles typebox (no --external typebox) and keeps it a regular dependency. OMP's extension loader rewrites every bare `typebox` import to its own omptype-based shim, whose schemas are callable objects rather than JSON Schema; they fail structuredClone during tool registration, so an external typebox import stops Magic Context loading on OMP. src/typebox-bundling.test.ts guards this.",
34150
34898
  scripts: {
34151
34899
  build: "bun ../../scripts/check-bun.mjs && tsc -p ../retina-local-fs/tsconfig.build.json && rm -f dist/index.js dist/subagent-entry.js dist/historian-calibration-extension.js dist/transformers-web.js dist/transformers-node-wasm.js dist/*-*.js && bun build ../plugin/src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/transformers-web.js --target browser --format esm --external onnxruntime-web && bun ../plugin/scripts/build-transformers-node-wasm.ts dist && bun build src/index.ts src/subagent-entry.ts src/historian-calibration-extension.ts --outdir dist --target node --format esm --splitting --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external onnxruntime-node --external onnxruntime-web --external sharp --external node:sqlite",
34152
34900
  "build:e2e-argv": "bun build src/subagent-runner.ts --outfile ../../tests/docker/.generated/subagent-runner-e2e.mjs --target node --format esm --external node:sqlite",
@@ -34161,7 +34909,7 @@ var package_default = {
34161
34909
  prepublishOnly: "bun run build"
34162
34910
  },
34163
34911
  dependencies: {
34164
- "@cortexkit/subc-client": "0.4.1",
34912
+ "@cortexkit/subc-client": "0.11.1",
34165
34913
  "@jitl/quickjs-singlefile-cjs-release-asyncify": "0.32.0",
34166
34914
  "ai-tokenizer": "^1.0.6",
34167
34915
  "comment-json": "^5.0.0",
@@ -34183,6 +34931,7 @@ var package_default = {
34183
34931
  "@types/better-sqlite3": "^7.6.13",
34184
34932
  "@types/bun": "^1.3.10",
34185
34933
  "@types/node": "^22.20.0",
34934
+ "pi-agent-core-087": "npm:@earendil-works/pi-agent-core@0.87.1",
34186
34935
  "pi-ai-086": "npm:@earendil-works/pi-ai@0.86.0",
34187
34936
  "pi-coding-agent-086": "npm:@earendil-works/pi-coding-agent@0.86.0",
34188
34937
  "pi-coding-agent-087": "npm:@earendil-works/pi-coding-agent@0.87.1",
@@ -34723,11 +35472,14 @@ No active Pi session is available.`,
34723
35472
  }
34724
35473
  try {
34725
35474
  if (ctx.hasUI) {
35475
+ if (currentDeps.modelChainWarning)
35476
+ ctx.ui.notify(currentDeps.modelChainWarning, "error");
34726
35477
  await showStatusDialog(pi, ctx, currentDeps);
34727
35478
  return;
34728
35479
  }
34729
35480
  const statusDetail = buildPiStatusDetail(pi, ctx, currentDeps, sessionId);
34730
- const statusText = formatPiStatusSummary(statusDetail);
35481
+ const statusText = `${formatPiStatusSummary(statusDetail)}${currentDeps.modelChainWarning ? `
35482
+ WARNING: ${currentDeps.modelChainWarning}` : ""}`;
34731
35483
  const details = buildStatusDetails(currentDeps, statusDetail);
34732
35484
  sendStatus({
34733
35485
  title: "/ctx-status",
@@ -35514,7 +36266,7 @@ function ensureConfigLocationsMigrated(dir) {
35514
36266
  });
35515
36267
  }
35516
36268
  function logPiConfigLoad(args) {
35517
- const key = resolve3(args.dir);
36269
+ const key = resolve4(args.dir);
35518
36270
  if (args.dedupe && loggedPiConfigDirs.has(key))
35519
36271
  return;
35520
36272
  if (args.dedupe) {
@@ -35575,6 +36327,13 @@ function logPiUsageBoundOnce(sessionId, reading, absoluteWall, reason) {
35575
36327
  piUsageBoundLogSeen.add(key);
35576
36328
  info(`message_end: session=${sessionId} bounded ${reason} ${reading} at trusted absolute wall ${absoluteWall}; pressure_proof_not_capacity`);
35577
36329
  }
36330
+ function logPiUsageAboveWindowOnce(sessionId, reading, absoluteWall) {
36331
+ const key = `${sessionId}|accepted above window`;
36332
+ if (piUsageBoundLogSeen.has(key))
36333
+ return;
36334
+ piUsageBoundLogSeen.add(key);
36335
+ info(`message_end: session=${sessionId} provider usage ${reading} exceeds configured window ${absoluteWall}; counted as real pressure against the configured limit`);
36336
+ }
35578
36337
  function resolvePiPressureContextLimit(args) {
35579
36338
  let detectedContextLimit;
35580
36339
  try {
@@ -35606,24 +36365,32 @@ async function persistPiPressureFromMessageEnd(args) {
35606
36365
  });
35607
36366
  const trustedAbsoluteWall = reportedGeometry && hasTrustedAbsoluteWall(reportedGeometry) ? reportedGeometry.derivation.absoluteWall : undefined;
35608
36367
  const unboundedPressure = computePiPressure(usage, args.piContextWindow);
35609
- const rawPressure = computePiPressure(usage, args.piContextWindow, trustedAbsoluteWall);
35610
- if (unboundedPressure && trustedAbsoluteWall !== undefined && unboundedPressure.inputTokens > trustedAbsoluteWall) {
35611
- logPiUsageBoundOnce(args.sessionId, unboundedPressure.inputTokens, trustedAbsoluteWall, "current reading");
35612
- }
35613
36368
  const msg = args.message && typeof args.message === "object" ? args.message : undefined;
35614
36369
  const messageHadOverflowError = typeof msg?.errorMessage === "string" && detectOverflow(msg.errorMessage).isOverflow;
35615
- const requestSucceeded = !messageHadOverflowError && !(unboundedPressure && trustedAbsoluteWall !== undefined && unboundedPressure.inputTokens > trustedAbsoluteWall);
35616
- if (requestSucceeded && rawPressure) {
36370
+ const readingAboveTrustedWall = unboundedPressure !== null && trustedAbsoluteWall !== undefined && unboundedPressure.inputTokens > trustedAbsoluteWall;
36371
+ const requestAccepted = !messageHadOverflowError;
36372
+ if (readingAboveTrustedWall && unboundedPressure && trustedAbsoluteWall) {
36373
+ if (requestAccepted) {
36374
+ logPiUsageAboveWindowOnce(args.sessionId, unboundedPressure.inputTokens, trustedAbsoluteWall);
36375
+ } else {
36376
+ logPiUsageBoundOnce(args.sessionId, unboundedPressure.inputTokens, trustedAbsoluteWall, "current reading");
36377
+ }
36378
+ }
36379
+ const requestSucceeded = requestAccepted && !readingAboveTrustedWall;
36380
+ if (requestAccepted && unboundedPressure) {
35617
36381
  const rawOverflow = getOverflowState(args.db, args.sessionId);
35618
36382
  const detectedLimitMatchesModel = rawOverflow.detectedContextLimitModelKey === null || modelKey !== undefined && rawOverflow.detectedContextLimitModelKey === modelKey;
35619
- if (rawOverflow.detectedContextLimit > 0 && detectedLimitMatchesModel && rawPressure.inputTokens > rawOverflow.detectedContextLimit) {
36383
+ if (rawOverflow.detectedContextLimit > 0 && detectedLimitMatchesModel && unboundedPressure.inputTokens > rawOverflow.detectedContextLimit) {
35620
36384
  clearDetectedContextLimit(args.db, args.sessionId);
35621
- info(`message_end: detected limit ${rawOverflow.detectedContextLimit} invalidated by a successful ${rawPressure.inputTokens}-token request; using Pi contextWindow`);
36385
+ info(`message_end: detected limit ${rawOverflow.detectedContextLimit} invalidated by a successful ${unboundedPressure.inputTokens}-token request; using Pi contextWindow`);
35622
36386
  }
35623
36387
  }
35624
36388
  const meta = getOrCreateSessionMeta(args.db, args.sessionId);
35625
36389
  let observedSafeInputTokens = meta.observedSafeInputTokens ?? 0;
35626
- const updates = { lastResponseTime: Date.now() };
36390
+ const updates = {};
36391
+ if (unboundedPressure !== null) {
36392
+ updates.lastResponseTime = Date.now();
36393
+ }
35627
36394
  if (trustedAbsoluteWall !== undefined && observedSafeInputTokens > trustedAbsoluteWall) {
35628
36395
  logPiUsageBoundOnce(args.sessionId, observedSafeInputTokens, trustedAbsoluteWall, "persisted floor");
35629
36396
  observedSafeInputTokens = 0;
@@ -35644,8 +36411,10 @@ async function persistPiPressureFromMessageEnd(args) {
35644
36411
  provenInputTokens: observedSafeInputTokens
35645
36412
  });
35646
36413
  const reportedContextLimit = reportedGeometry?.usableSoft ?? 0;
35647
- const pressure = computePiPressure(usage, effectiveContextLimit, trustedAbsoluteWall);
36414
+ const pressure = computePiPressure(usage, effectiveContextLimit, requestAccepted ? undefined : trustedAbsoluteWall);
36415
+ let cacheAlert;
35648
36416
  if (pressure) {
36417
+ notePiUsageReadingUsed(args.sessionId);
35649
36418
  const provenSafeInputTokens = requestSucceeded ? Math.max(observedSafeInputTokens, pressure.inputTokens) : observedSafeInputTokens;
35650
36419
  const contextLimit = requestSucceeded ? resolvePiPressureContextLimit({
35651
36420
  db: args.db,
@@ -35659,7 +36428,7 @@ async function persistPiPressureFromMessageEnd(args) {
35659
36428
  if (requestSucceeded && reportedContextLimit > 0 && reportedContextLimit < provenSafeInputTokens && !meta.cacheAlertSent) {
35660
36429
  updates.cacheAlertSent = true;
35661
36430
  const modelLabel = activeModel.provider && activeModel.id ? `${activeModel.provider}/${activeModel.id}` : "the active model";
35662
- await args.notifyIssue?.(`⚠️ Magic Context: Pi reports a context limit of ${formatTokens(reportedContextLimit)} tokens for ${modelLabel}, but this session has sent ${formatTokens(provenSafeInputTokens)} tokens successfully. Magic Context will keep using the larger proven value for its pressure math. If Pi's model metadata is wrong for your provider, set contextWindow for that model in Pi's model configuration.`);
36431
+ cacheAlert = `⚠️ Magic Context: Pi reports a context limit of ${formatTokens(reportedContextLimit)} tokens for ${modelLabel}, but this session has sent ${formatTokens(provenSafeInputTokens)} tokens successfully. Magic Context will keep using the larger proven value for its pressure math. If Pi's model metadata is wrong for your provider, set contextWindow for that model in Pi's model configuration.`;
35663
36432
  }
35664
36433
  updates.lastContextPercentage = percentage;
35665
36434
  updates.lastInputTokens = pressure.inputTokens;
@@ -35668,13 +36437,21 @@ async function persistPiPressureFromMessageEnd(args) {
35668
36437
  updates.observedSafeInputTokens = provenSafeInputTokens;
35669
36438
  }
35670
36439
  } else if (usage === null && typeof args.piTokens === "number" && (trustedAbsoluteWall === undefined || args.piTokens <= trustedAbsoluteWall)) {
35671
- updates.lastInputTokens = args.piTokens;
35672
- if (effectiveContextLimit > 0) {
35673
- updates.lastContextPercentage = args.piTokens / effectiveContextLimit * 100;
35674
- updates.lastUsageContextLimit = effectiveContextLimit;
36440
+ if (args.piTokensIsRawBranchEstimate === true) {
36441
+ noteRawBranchEstimateSetAside(args.sessionId, args.piTokens, "message_end estimate");
36442
+ } else {
36443
+ notePiUsageReadingUsed(args.sessionId);
36444
+ updates.lastInputTokens = args.piTokens;
36445
+ if (effectiveContextLimit > 0) {
36446
+ updates.lastContextPercentage = args.piTokens / effectiveContextLimit * 100;
36447
+ updates.lastUsageContextLimit = effectiveContextLimit;
36448
+ }
35675
36449
  }
35676
36450
  }
35677
36451
  updateSessionMeta(args.db, args.sessionId, updates);
36452
+ if (cacheAlert !== undefined) {
36453
+ await args.notifyIssue?.(cacheAlert);
36454
+ }
35678
36455
  }
35679
36456
  var PLUGIN_VERSION = (() => {
35680
36457
  try {
@@ -35685,17 +36462,27 @@ var PLUGIN_VERSION = (() => {
35685
36462
  }
35686
36463
  })();
35687
36464
  setHarness(PI_HARNESS_KIND);
35688
- function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND) {
36465
+ function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND, modelRegistry) {
35689
36466
  const historian = config.historian;
35690
36467
  if (historian?.disable === true)
35691
36468
  return;
35692
36469
  const resolved = resolveHistorianModel(config, harness);
35693
- const model = resolved.primary?.model;
36470
+ const validated = modelRegistry && resolved.primary ? validatePiDreamerModels([
36471
+ {
36472
+ task: "classify-memories",
36473
+ schedule: "",
36474
+ timeoutMinutes: 20,
36475
+ model: resolved.primary,
36476
+ fallbackModels: resolved.fallbacks
36477
+ }
36478
+ ], modelRegistry)[0] : undefined;
36479
+ const primary = validated ? typeof validated.model === "string" ? { model: validated.model } : validated.model : resolved.primary;
36480
+ const model = primary?.model;
35694
36481
  if (!model)
35695
36482
  return;
35696
36483
  const historianContextLimit = resolveHistorianContextLimit(model);
35697
36484
  const historianChunkTokens = deriveHistorianChunkTokens(historianContextLimit);
35698
- const fallbackModels = resolved.fallbacks;
36485
+ const fallbackModels = validated?.fallbackModels ?? resolved.fallbacks;
35699
36486
  return {
35700
36487
  runner: new PiSubagentRunner,
35701
36488
  model,
@@ -35706,7 +36493,7 @@ function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND) {
35706
36493
  temperature: historian?.temperature,
35707
36494
  maxOutputTokens: historian?.maxTokens,
35708
36495
  twoPass: historian?.two_pass === true,
35709
- thinkingLevel: resolved.primary?.qualifier,
36496
+ thinkingLevel: primary?.qualifier,
35710
36497
  executeThresholdPercentage: config.execute_threshold_percentage,
35711
36498
  executeThresholdTokens: config.execute_threshold_tokens,
35712
36499
  commitClusterTrigger: config.commit_cluster_trigger,
@@ -35770,30 +36557,7 @@ async function src_default(pi) {
35770
36557
  return null;
35771
36558
  }
35772
36559
  };
35773
- const unavailableReason = () => {
35774
- const migration = getMigrationOnOpenRefusal();
35775
- const blockingProcesses = migration?.blockingProcesses ?? migration?.serverPids.map((pid) => ({ kind: "process", pid })) ?? [];
35776
- const fence = getSchemaFenceRejection();
35777
- if (migration && blockingProcesses.length > 0) {
35778
- return {
35779
- kind: "migration_guard",
35780
- persistedVersion: migration.persistedVersion,
35781
- supportedVersion: migration.supportedVersion,
35782
- blockingProcesses
35783
- };
35784
- }
35785
- if (fence) {
35786
- return {
35787
- kind: "schema_fence",
35788
- persistedVersion: fence.persistedVersion,
35789
- supportedVersion: fence.supportedVersion
35790
- };
35791
- }
35792
- return {
35793
- kind: "storage_failure",
35794
- cause: migration?.unreadableFile ? `migration guard could not read RPC discovery file ${migration.unreadableFile}` : openFailureCause ?? `storage unavailable at ${dbPath} (cache schema newer than this binary, or open failed)`
35795
- };
35796
- };
36560
+ const unavailableReason = () => describeStorageUnavailability(openFailureCause ?? `storage unavailable at ${dbPath} (cache schema newer than this binary, or open failed)`);
35797
36561
  const bootResult = await bootPiRuntimeWithDeadline({
35798
36562
  deadlineMs: PI_BOOT_DEADLINE_MS,
35799
36563
  openStorage,
@@ -35979,15 +36743,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
35979
36743
  maybeAutoEmbedPiSession({
35980
36744
  db: database,
35981
36745
  projectDir: dir,
35982
- projectIdentity: identity,
35983
- memoryEnabled: cfg.memory.enabled
35984
- }, sessionId, dir, identity, (text) => {
35985
- sendCtxStatusMessage(pi, {
35986
- title: "/ctx-embed",
35987
- text,
35988
- level: "info"
35989
- });
35990
- });
36746
+ projectIdentity: identity
36747
+ }, sessionId, dir, identity);
35991
36748
  }
35992
36749
  });
35993
36750
  function buildProjectDeps(dir, identity, cfg, loadMetadata) {
@@ -36041,10 +36798,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36041
36798
  function resolveCurrentProjectDeps(ctx) {
36042
36799
  return resolveProjectDepsForDir(ctx.cwd);
36043
36800
  }
36801
+ let activeModelRegistry;
36044
36802
  function resolveContextOptionsForProject(dir) {
36045
36803
  const project = resolveProjectDepsForDir(dir);
36046
36804
  const sampled = historianRunConfig(project.config, liveReaderFor(dir, project.config).poll().effective);
36047
- const historian = resolveHistorianFromConfig(sampled);
36805
+ const historian = resolveHistorianFromConfig(sampled, PI_HARNESS_KIND, activeModelRegistry);
36048
36806
  return { ...project.contextOptions, historian };
36049
36807
  }
36050
36808
  const bootProjectDeps = buildProjectDeps(projectDir, projectIdentity, config, {
@@ -36053,7 +36811,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36053
36811
  hasDeprecatedProtectedTags
36054
36812
  });
36055
36813
  projectDepsByDir.set(projectDir, bootProjectDeps);
36056
- function syncDreamerProjectRegistration(current) {
36814
+ function syncDreamerProjectRegistration(current, modelRegistry) {
36057
36815
  if (sessionShuttingDown)
36058
36816
  return;
36059
36817
  seenDreamerProjectIdentities.add(current.projectIdentity);
@@ -36070,6 +36828,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36070
36828
  projectIdentity: current.projectIdentity,
36071
36829
  registrationOwner: dreamerRegistrationOwner,
36072
36830
  config: current.dreamerConfig,
36831
+ modelRegistry,
36073
36832
  sampleDreamRun: () => {
36074
36833
  const fresh = liveReaderFor(current.projectDir, current.config).poll().effective;
36075
36834
  const sampled = dreamerRunConfig(current.config, fresh);
@@ -36111,6 +36870,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36111
36870
  pi.on("session_start", async (event, ctx) => {
36112
36871
  projectDepsByDir.delete(ctx.cwd);
36113
36872
  const current = resolveCurrentProjectDeps(ctx);
36873
+ activeModelRegistry = ctx.modelRegistry;
36874
+ syncDreamerProjectRegistration(current, ctx.modelRegistry);
36114
36875
  syncCtxMemoryToolEnabled(pi, current.config.memory.enabled);
36115
36876
  const failuresToShow = claimConfigParseFailuresOnce("pi", current.configParseFailures);
36116
36877
  if (ctx.hasUI && failuresToShow.length > 0) {
@@ -36189,6 +36950,16 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36189
36950
  runnable: current.dreamerEnabled,
36190
36951
  scheduleSummary: summarizeDreamSchedule(current.config.dreamer)
36191
36952
  },
36953
+ modelChainWarning: (() => {
36954
+ const registry = activeModelRegistry;
36955
+ if (!registry)
36956
+ return;
36957
+ const tasks = validatePiDreamerModels(buildDreamTaskRuntimeConfigs(current.config.dreamer, PI_HARNESS_KIND, current.config.language, current.config.mural.model), registry);
36958
+ const empty = tasks.filter((task) => task.modelChainUnavailable).map((task) => task.task);
36959
+ if (!resolveHistorianFromConfig(current.config, PI_HARNESS_KIND, registry) && current.historianConfig)
36960
+ empty.push("historian");
36961
+ return empty.length ? `Pi model chain empty (no model found): ${empty.join(", ")}` : undefined;
36962
+ })(),
36192
36963
  activeProfile: current.config.profile,
36193
36964
  cacheTtlConfig: current.config.cache_ttl,
36194
36965
  cacheTtlConfigured: current.cacheTtlConfigured,
@@ -36218,7 +36989,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36218
36989
  resolveRuntimeDeps: (ctx) => {
36219
36990
  const current = resolveCurrentProjectDeps(ctx);
36220
36991
  const fresh = historianRunConfig(current.config, liveReaderFor(current.projectDir, current.config).poll().effective);
36221
- const historian = resolveHistorianFromConfig(fresh) ?? current.historianConfig;
36992
+ const historian = resolveHistorianFromConfig(fresh, PI_HARNESS_KIND, activeModelRegistry);
36222
36993
  return {
36223
36994
  db,
36224
36995
  runner: recompRunner,
@@ -36253,7 +37024,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36253
37024
  resolveRuntimeDeps: (ctx) => {
36254
37025
  const current = resolveCurrentProjectDeps(ctx);
36255
37026
  const fresh = historianRunConfig(current.config, liveReaderFor(current.projectDir, current.config).poll().effective);
36256
- const historian = resolveHistorianFromConfig(fresh) ?? current.historianConfig;
37027
+ const historian = resolveHistorianFromConfig(fresh, PI_HARNESS_KIND, activeModelRegistry);
36257
37028
  return {
36258
37029
  db,
36259
37030
  runner: wrapupRunner,
@@ -36287,7 +37058,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36287
37058
  dreamerEnabled: bootProjectDeps.dreamerEnabled,
36288
37059
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
36289
37060
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity),
36290
- ensureRegistered: (ctx) => syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx)),
37061
+ ensureRegistered: (ctx) => syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry),
36291
37062
  registrationOwner: dreamerRegistrationOwner
36292
37063
  });
36293
37064
  info("registered /ctx-dream");
@@ -36295,8 +37066,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36295
37066
  db,
36296
37067
  projectDir,
36297
37068
  projectIdentity,
36298
- memoryEnabled: bootProjectDeps.config.memory.enabled,
36299
- resolveMemoryEnabled: (ctx) => resolveCurrentProjectDeps(ctx).config.memory.enabled,
36300
37069
  resolveProject: (ctx) => {
36301
37070
  const current = resolveCurrentProjectDeps(ctx);
36302
37071
  return {
@@ -36340,7 +37109,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36340
37109
  };
36341
37110
  const effectiveConfig = effectiveProjectDeps.config;
36342
37111
  try {
36343
- syncDreamerProjectRegistration(effectiveProjectDeps);
37112
+ activeModelRegistry = ctx.modelRegistry;
37113
+ syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
36344
37114
  } catch (err) {
36345
37115
  warn("before_agent_start: dreamer registration sync threw:", err);
36346
37116
  }
@@ -36540,6 +37310,14 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36540
37310
  piContextWindowSource: "catalog",
36541
37311
  piModel: ctx.model,
36542
37312
  piTokens: piUsage && typeof piUsage.tokens === "number" ? piUsage.tokens : undefined,
37313
+ piTokensIsRawBranchEstimate: (() => {
37314
+ try {
37315
+ const branch = ctx.sessionManager.getBranch?.();
37316
+ return isPiLiveUsageRawBranchEstimate(branch);
37317
+ } catch {
37318
+ return false;
37319
+ }
37320
+ })(),
36543
37321
  notifyIssue: async (message) => {
36544
37322
  const uiNotify = ctx.ui?.notify;
36545
37323
  if (typeof uiNotify === "function") {