@cortexkit/pi-magic-context 0.43.1 → 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-kyrajahw.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-822b3fcs.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";
@@ -1824,6 +1843,8 @@ function setCtxReduceRegisteredGlobally(registered) {
1824
1843
  var availabilityBySession = new BoundedSessionMap(1000);
1825
1844
  var permissionDeniedBySession = new BoundedSessionMap(2000);
1826
1845
  var ctxReducePermissionDenyLogged = new BoundedSessionMap(1000);
1846
+ var ctxReduceSpawnPermissionDenied = new BoundedSessionMap(1000);
1847
+ var ctxReduceSpawnPermissionReads = new Map;
1827
1848
 
1828
1849
  // ../plugin/src/hooks/magic-context/derive-budgets.ts
1829
1850
  var TRIGGER_BUDGET_PERCENTAGE = 0.05;
@@ -2780,6 +2801,11 @@ var USER_FACING_FAILURES = {
2780
2801
  sentence: "Some finished background sessions could not be removed from this host.",
2781
2802
  action: "They are removed automatically the next time Magic Context runs inside a registered OpenCode service."
2782
2803
  },
2804
+ historian_saved_history_misaligned: {
2805
+ code: "MC-H03",
2806
+ sentence: "History compression is paused because this session's saved summaries no longer line up with its messages.",
2807
+ action: "Run /ctx-recomp to rebuild them."
2808
+ },
2783
2809
  recomp_unavailable: {
2784
2810
  code: "MC-R01",
2785
2811
  sentence: "History compression could not be rebuilt.",
@@ -2795,6 +2821,11 @@ var USER_FACING_FAILURES = {
2795
2821
  sentence: "Memory maintenance could not reach its model.",
2796
2822
  action: "Check the model connection, then run /ctx-dream again."
2797
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
+ },
2798
2829
  dream_empty_completion: {
2799
2830
  code: "MC-D03",
2800
2831
  sentence: "Memory maintenance received no usable response.",
@@ -2950,11 +2981,21 @@ var USER_FACING_FAILURES = {
2950
2981
  sentence: "History compression is paused while the engine syncs.",
2951
2982
  action: "Retry in a moment."
2952
2983
  },
2984
+ history_compression_needs_message: {
2985
+ code: "MC-C12",
2986
+ sentence: "History compression has not seen this session since Magic Context reconnected.",
2987
+ action: "Send a message in this session first, then run /ctx-wrapup again."
2988
+ },
2953
2989
  context_service_unavailable: {
2954
2990
  code: "MC-C10",
2955
2991
  sentence: "Magic Context is temporarily unavailable.",
2956
2992
  action: "Retry in a moment."
2957
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
+ },
2958
2999
  compaction_marker_missing: {
2959
3000
  code: "MC-C11",
2960
3001
  sentence: "The history boundary marker is missing from the OpenCode store, so requests carry the full session.",
@@ -2970,11 +3011,6 @@ var USER_FACING_FAILURES = {
2970
3011
  sentence: "Memory authority is inconsistent between the host and module.",
2971
3012
  action: "Run `ck doctor drain-authority` before changing Rust mode."
2972
3013
  },
2973
- rust_mode_unsupported: {
2974
- code: "MC-S06",
2975
- sentence: "Experimental Rust transform mode is not available on this OpenCode host.",
2976
- action: 'Magic Context is running its TypeScript mode instead; set `transform_mode` to "ts" to stop requesting Rust.'
2977
- },
2978
3014
  dreamer_task_failing: {
2979
3015
  code: "MC-S05",
2980
3016
  sentence: "A background maintenance task keeps failing on its schedule.",
@@ -3006,6 +3042,7 @@ function renderCapabilityRefusal(capability) {
3006
3042
  var DREAM_FAILURE_KEYS = {
3007
3043
  provider_timeout: "dream_provider_timeout",
3008
3044
  provider_error: "dream_provider_error",
3045
+ step_limit: "dream_step_limit",
3009
3046
  empty_completion: "dream_empty_completion",
3010
3047
  no_models: "dream_no_models",
3011
3048
  child_aborted: "dream_child_aborted",
@@ -3035,6 +3072,10 @@ function renderEmbeddingFailure(failureClass, style = "markdown") {
3035
3072
  return renderUserFacingFailure(EMBEDDING_FAILURE_KEYS[failureClass], style);
3036
3073
  }
3037
3074
 
3075
+ // src/dreamer/index.ts
3076
+ import { homedir as homedir2 } from "node:os";
3077
+ import { parse, resolve as resolve3 } from "node:path";
3078
+
3038
3079
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
3039
3080
  function openOpenCodeDb() {
3040
3081
  const resolution = resolveOpenCodeDbPath();
@@ -3192,6 +3233,37 @@ class HiddenCompletionRefusal extends Error {
3192
3233
  this.name = "HiddenCompletionRefusal";
3193
3234
  }
3194
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
+
3195
3267
  // ../plugin/src/shared/assistant-message-extractor.ts
3196
3268
  function asSessionMessage(value) {
3197
3269
  if (!isRecord(value))
@@ -3290,6 +3362,14 @@ function copyPromptArgs(args, body) {
3290
3362
  return { ...args, body: { ...body } };
3291
3363
  }
3292
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
+ }
3293
3373
  function getPromptFailureDetail(error) {
3294
3374
  return error !== null && typeof error === "object" ? promptFailureDetails.get(error) ?? null : null;
3295
3375
  }
@@ -3346,9 +3426,13 @@ function parseModelSuggestion(error) {
3346
3426
  suggestion: suggestionMatch[1].trim()
3347
3427
  };
3348
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
+ }
3349
3433
  async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3350
3434
  if (signal?.aborted) {
3351
- throw new Error("prompt aborted by external signal");
3435
+ throw new Error(externalAbortMessage(signal));
3352
3436
  }
3353
3437
  const controller = new AbortController;
3354
3438
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -3368,7 +3452,7 @@ async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3368
3452
  if (!transport || transport.childSessionId) {
3369
3453
  await abortChildRun(client, transport?.childSessionId ?? args.path.id);
3370
3454
  }
3371
- throw new Error("prompt aborted by external signal");
3455
+ throw new Error(externalAbortMessage(signal));
3372
3456
  }
3373
3457
  if (controller.signal.aborted) {
3374
3458
  if (!transport || transport.childSessionId) {
@@ -3376,6 +3460,11 @@ async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3376
3460
  }
3377
3461
  throw new Error(`prompt timed out after ${timeoutMs}ms`);
3378
3462
  }
3463
+ if (isHostTimeoutError(error)) {
3464
+ if (!transport || transport.childSessionId) {
3465
+ await abortChildRun(client, transport?.childSessionId ?? args.path.id);
3466
+ }
3467
+ }
3379
3468
  throw error;
3380
3469
  } finally {
3381
3470
  clearTimeout(timeout);
@@ -3385,7 +3474,7 @@ async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3385
3474
  if (!transport || transport.childSessionId) {
3386
3475
  await abortChildRun(client, transport?.childSessionId ?? args.path.id);
3387
3476
  }
3388
- 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`);
3389
3478
  }
3390
3479
  }
3391
3480
  async function abortChildRun(client, sessionId) {
@@ -3401,7 +3490,7 @@ async function abortChildRun(client, sessionId) {
3401
3490
  }
3402
3491
  }
3403
3492
  function isNonRetryable(error, externalSignal) {
3404
- if (externalSignal?.aborted)
3493
+ if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit)
3405
3494
  return true;
3406
3495
  if (error instanceof Error) {
3407
3496
  if (error.name === "AbortError")
@@ -3411,6 +3500,8 @@ function isNonRetryable(error, externalSignal) {
3411
3500
  if (/^prompt timed out after \d+ms$/.test(error.message))
3412
3501
  return true;
3413
3502
  }
3503
+ if (isHostTimeoutError(error))
3504
+ return true;
3414
3505
  if (detectOverflow(error).isOverflow)
3415
3506
  return true;
3416
3507
  return false;
@@ -3423,11 +3514,14 @@ function shortErr(error) {
3423
3514
  }
3424
3515
  function classifyPromptFailure(error, phase, externalSignal) {
3425
3516
  const message = extractMessage(error);
3517
+ if (error instanceof HiddenAgentStepLimit)
3518
+ return "step_limit";
3426
3519
  if (externalSignal?.aborted || message === "prompt aborted by external signal") {
3427
3520
  return "child_aborted";
3428
3521
  }
3429
- if (/^prompt timed out after \d+ms$/.test(message))
3522
+ if (/^prompt timed out after \d+ms$/.test(message) || isHostTimeoutError(error)) {
3430
3523
  return "provider_timeout";
3524
+ }
3431
3525
  if (phase === "validation") {
3432
3526
  if (/returned no (?:assistant )?output|no assistant output/i.test(message)) {
3433
3527
  return "empty_completion";
@@ -3644,7 +3738,10 @@ async function promptSyncWithValidatedOutputRetry(client, args, options) {
3644
3738
  }
3645
3739
  }
3646
3740
  log(`[${callContext}] all models exhausted; tried: ${failedAttempts.map((failure) => failure.attempt.label).join(", ")}; original error: ${shortErr(firstError)}; last error: ${shortErr(lastError)}`);
3647
- 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);
3648
3745
  }
3649
3746
  // ../plugin/src/shared/normalize-sdk-response.ts
3650
3747
  function normalizeSDKResponse(response, fallback, options) {
@@ -3669,6 +3766,149 @@ function normalizeSDKResponse(response, fallback, options) {
3669
3766
  }
3670
3767
  return fallback;
3671
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
+ }
3672
3912
  // ../plugin/src/shared/summarize-child-stderr.ts
3673
3913
  var MAX_CHILD_STDERR_SUMMARY_LENGTH = 500;
3674
3914
  var CHILD_ERROR_LINE_PATTERN = /^(?:[A-Za-z]*Error|SqliteError|Error)\b[^\n]*/;
@@ -3808,6 +4048,9 @@ function failedInvocationStatus(error) {
3808
4048
  const message = describeError(error).brief;
3809
4049
  if (/timed out after \d+ms|prompt timed out/i.test(message))
3810
4050
  return "timed_out";
4051
+ if (error !== null && typeof error === "object" && error.name === "TimeoutError") {
4052
+ return "timed_out";
4053
+ }
3811
4054
  if (/length-capped|no (?:assistant )?output|empty (?:assistant )?(?:output|text)/i.test(message)) {
3812
4055
  return "empty";
3813
4056
  }
@@ -4869,7 +5112,7 @@ function shouldDiscardLastHistorianCompartment(compartments, chunk) {
4869
5112
  function validateHistorianOutput(text, sessionId, chunk, _priorCompartments, sequenceOffset) {
4870
5113
  const parseStarted = startHistorianPublishStage(sessionId, "parse", `response_chars=${text.length}`);
4871
5114
  const parsed = parseCompartmentOutput(text);
4872
- 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}`);
4873
5116
  const validationStarted = startHistorianPublishStage(sessionId, "validate");
4874
5117
  const finish = (result) => {
4875
5118
  finishHistorianPublishStage(sessionId, "validate", validationStarted, result.ok ? "completed" : "failed", result.ok ? `compartments=${result.compartments.length}` : `reason=${JSON.stringify(result.error)}`);
@@ -4908,6 +5151,7 @@ function validateHistorianOutput(text, sessionId, chunk, _priorCompartments, seq
4908
5151
  ok: true,
4909
5152
  compartments: mapped.compartments,
4910
5153
  facts: parsed.facts,
5154
+ ...parsed.droppedFactBlocks > 0 ? { droppedFactBlocks: parsed.droppedFactBlocks, droppedFacts: parsed.droppedFacts } : {},
4911
5155
  userObservations: parsed.userObservations.length > 0 ? parsed.userObservations : undefined,
4912
5156
  primerCandidates: parsed.primerCandidates.length > 0 ? parsed.primerCandidates.slice(0, 1) : undefined,
4913
5157
  events: parsed.events.length > 0 ? parsed.events : undefined
@@ -5160,6 +5404,7 @@ function resolveHiddenCompletionExecutor(executor, client, db, directory, entryP
5160
5404
  return createV1HiddenCompletionExecutor(client, db, directory);
5161
5405
  }
5162
5406
  function createV1HiddenCompletionExecutor(client, db, directory) {
5407
+ const asyncChildren = new Set;
5163
5408
  return {
5164
5409
  capabilities: { tools: true, harness: "opencode" },
5165
5410
  async open(run) {
@@ -5176,11 +5421,17 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5176
5421
  preferResponseOnMissingData: true
5177
5422
  });
5178
5423
  const id = typeof created?.id === "string" ? created.id : "";
5424
+ if (id && run.kind === "dreamer-task")
5425
+ asyncChildren.add(id);
5179
5426
  return { id, childSessionId: id || undefined };
5180
5427
  },
5181
- async attempt(_handle, request) {
5428
+ async attempt(handle, request) {
5182
5429
  if (!client)
5183
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
+ }
5184
5435
  await client.session.prompt(request);
5185
5436
  },
5186
5437
  async collect(handle, limit) {
@@ -5208,6 +5459,8 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5208
5459
  };
5209
5460
  },
5210
5461
  async close(handle, settlement) {
5462
+ if (handle?.id)
5463
+ asyncChildren.delete(handle.id);
5211
5464
  if (!client)
5212
5465
  return;
5213
5466
  await teardownChildSession({
@@ -5219,6 +5472,9 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5219
5472
  }
5220
5473
  };
5221
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
+ }
5222
5478
  async function runValidatedHistorianPass(args) {
5223
5479
  const firstRun = await runHistorianPrompt({
5224
5480
  ...args,
@@ -5445,14 +5701,14 @@ async function runHistorianPrompt(args) {
5445
5701
  }
5446
5702
  const backoffMs = getHistorianRetryBackoffMs(retryIndex);
5447
5703
  sessionLog(parentSessionId, `historian retry ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES} after ${backoffMs}ms: ${errorMsg}`);
5448
- await sleep(backoffMs);
5704
+ await sleep2(backoffMs);
5449
5705
  }
5450
5706
  }
5451
5707
  completion = await executor.collect(handle, 50);
5452
5708
  const lengthCapped = completion.lengthCapped;
5453
5709
  const textResult = completion.text;
5454
5710
  const reasoningResult = textResult ? null : completion.reasoning;
5455
- 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;
5456
5712
  const invocationId = recordInvocation({
5457
5713
  status: emptyError ? "empty" : "completed",
5458
5714
  messages: completion.messages,
@@ -5582,7 +5838,7 @@ function isTransientHistorianPromptError(message) {
5582
5838
  "overloaded"
5583
5839
  ].some((token) => normalized.includes(token));
5584
5840
  }
5585
- function sleep(ms) {
5841
+ function sleep2(ms) {
5586
5842
  return new Promise((resolve) => {
5587
5843
  setTimeout(resolve, ms);
5588
5844
  });
@@ -5779,13 +6035,25 @@ function runLeaseGuardedWrite(db, holderId, leaseKey, fn, slowWriteThresholdMs)
5779
6035
  return fn();
5780
6036
  }, `lease-guarded-write:${leaseKey}`, slowWriteThresholdMs);
5781
6037
  }
5782
- 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
+ }
5783
6051
  function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisition = LEASE_HEARTBEAT_INTERVAL_MS) {
5784
6052
  const intervalMs = typeof intervalOrAcquisition === "number" ? intervalOrAcquisition : LEASE_HEARTBEAT_INTERVAL_MS;
5785
6053
  const acquisition = typeof intervalOrAcquisition === "number" ? undefined : intervalOrAcquisition;
5786
6054
  let lost = false;
5787
- let expectedGeneration = acquisition?.generation ?? getLeaseGeneration(db, leaseKey);
5788
- let lastConfirmedAt = acquisition?.acquiredAt ?? Date.now();
6055
+ const expectedGeneration = acquisition?.generation ?? getLeaseGeneration(db, leaseKey);
6056
+ let renewalFailures = 0;
5789
6057
  const declareLost = (reason) => {
5790
6058
  if (lost)
5791
6059
  return;
@@ -5797,32 +6065,20 @@ function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisiti
5797
6065
  return;
5798
6066
  try {
5799
6067
  if (renewLease(db, holderId, leaseKey, expectedGeneration === null ? undefined : expectedGeneration)) {
5800
- lastConfirmedAt = Date.now();
5801
6068
  return;
5802
6069
  }
5803
- if (expectedGeneration !== null && getLeaseGeneration(db, leaseKey) !== expectedGeneration) {
5804
- declareLost("lease generation changed — another holder acquired it");
5805
- return;
5806
- }
5807
- if (Date.now() - lastConfirmedAt > LEASE_DURATION_MS) {
5808
- declareLost("lease lapsed past TTL — another holder may have run");
5809
- return;
5810
- }
5811
- const reacquired = acquireLeaseWithAcquisition(db, holderId, leaseKey);
5812
- if (reacquired) {
5813
- if (expectedGeneration !== null && reacquired.generation !== expectedGeneration) {
5814
- declareLost("lease generation changed during reacquisition");
5815
- return;
5816
- }
5817
- expectedGeneration = reacquired.generation;
5818
- lastConfirmedAt = Date.now();
6070
+ if (expectedGeneration !== null && reacquireOwnedLease(db, holderId, leaseKey, expectedGeneration))
5819
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)");
5820
6079
  }
5821
- declareLost("lease acquired by another holder");
5822
6080
  } catch {
5823
- if (Date.now() - lastConfirmedAt > LEASE_DURATION_MS) {
5824
- declareLost("lease renewal unconfirmed past TTL");
5825
- }
6081
+ renewalFailures += 1;
5826
6082
  }
5827
6083
  };
5828
6084
  beat();
@@ -5834,6 +6090,9 @@ function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisiti
5834
6090
  },
5835
6091
  get lost() {
5836
6092
  return lost;
6093
+ },
6094
+ get renewalFailures() {
6095
+ return renewalFailures;
5837
6096
  }
5838
6097
  };
5839
6098
  }
@@ -5911,6 +6170,14 @@ function assertParsedManifestNonEmpty(parsedCount, expectedCount, text, expected
5911
6170
  }
5912
6171
 
5913
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
+
5914
6181
  class DreamerModuleBusyError extends Error {
5915
6182
  state;
5916
6183
  transient = true;
@@ -6535,7 +6802,7 @@ async function runHiddenSingleShotPrompt(args) {
6535
6802
  fetchOutput: () => args.executor.collect(handle, 50),
6536
6803
  validateOutput: (completion) => {
6537
6804
  if (completion.lengthCapped) {
6538
- 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`);
6539
6806
  }
6540
6807
  const text = completion.text;
6541
6808
  if (!text && !args.allowEmpty)
@@ -7108,12 +7375,45 @@ function validateClassifyManifest(text, expectedIds) {
7108
7375
  return parsed;
7109
7376
  }
7110
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
+
7111
7410
  // ../plugin/src/features/magic-context/dreamer/classify.ts
7112
7411
  var MIN_POOL_TO_CLASSIFY = 10;
7113
7412
  var FULL_POOL_CEILING = 100;
7114
7413
  var STAGE3_ANCHOR_COUNT = 30;
7115
7414
  var CLASSIFY_CHUNK_SIZE = 100;
7116
7415
  var CLASSIFY_MODULE_RUN_TIMEOUT_MS = 660000;
7416
+ var HOST_COMPLETION_REQUIRED = "host_completion_required";
7117
7417
 
7118
7418
  class ClassifyModuleFailureError extends Error {
7119
7419
  transient = true;
@@ -7197,13 +7497,12 @@ async function runClassify(args) {
7197
7497
  let stage;
7198
7498
  let toClassify;
7199
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));
7200
7502
  if (active.length <= FULL_POOL_CEILING) {
7201
7503
  stage = 2;
7202
- toClassify = active;
7203
7504
  } else {
7204
7505
  stage = 3;
7205
- const unclassifiedIds = new Set(getUnclassifiedMemoryIds(args.db, active.map((candidate) => candidate.contextMemory.id)));
7206
- toClassify = active.filter((candidate) => unclassifiedIds.has(candidate.contextMemory.id));
7207
7506
  const classified = active.filter((candidate) => !unclassifiedIds.has(candidate.contextMemory.id));
7208
7507
  anchors = stratifiedAnchors(classified, STAGE3_ANCHOR_COUNT);
7209
7508
  }
@@ -7224,7 +7523,7 @@ async function runClassify(args) {
7224
7523
  chunks.push(toClassify.slice(i, i + CLASSIFY_CHUNK_SIZE));
7225
7524
  }
7226
7525
  const abortController = new AbortController;
7227
- 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);
7228
7527
  try {
7229
7528
  for (let i = 0;i < chunks.length; i += 1) {
7230
7529
  const remainingMs = Math.max(0, args.deadline - Date.now());
@@ -7259,65 +7558,89 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
7259
7558
  memories: chunk.map(toPromptMemory2),
7260
7559
  anchors
7261
7560
  });
7262
- if (moduleRoute) {
7263
- const run = await runClassifyThroughModule(args, chunk, anchors, signal);
7264
- recordInvocation(args, startedAt, { status: "completed" });
7265
- return run;
7266
- }
7267
- const executor = resolveHiddenCompletionExecutor(args.hiddenCompletionExecutor, args.client, args.db, args.sessionDirectory, "classify-memories");
7268
- closeExecutor = executor;
7269
- handle = await executor.open({
7270
- parentSessionId: args.parentSessionId,
7271
- agent: DREAMER_CLASSIFIER_AGENT,
7272
- kind: "dreamer-task",
7273
- system: withContentLanguageDirective(CLASSIFY_SYSTEM_PROMPT, args.language),
7274
- model: args.model,
7275
- configuredModels: [...args.model ? [args.model] : [], ...args.fallbackModels ?? []],
7276
- timeoutMs: sliceMs,
7277
- title: "magic-context-dream-classify",
7278
- directory: args.sessionDirectory,
7279
- metadata: { task: "classify-memories" }
7280
- });
7281
- agentSessionId = handle.id || null;
7282
- if (!agentSessionId)
7283
- throw new Error("Could not create classify session.");
7284
- const opened = handle;
7285
- const run = await promptSyncWithValidatedOutputRetry(args.client, {
7286
- path: { id: agentSessionId },
7287
- query: { directory: args.sessionDirectory },
7288
- body: {
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,
7289
7566
  agent: DREAMER_CLASSIFIER_AGENT,
7290
- system: withContentLanguageDirective(CLASSIFY_SYSTEM_PROMPT, args.language),
7291
- ...modelBodyField(args.model),
7292
- parts: [{ type: "text", text: prompt, synthetic: true }]
7293
- }
7294
- }, {
7295
- transport: Object.assign((request) => executor.attempt(opened, request), { childSessionId: opened.childSessionId }),
7296
- timeoutMs: sliceMs,
7297
- signal,
7298
- fallbackModels: args.fallbackModels,
7299
- callContext: "dreamer:classify-memories",
7300
- fetchOutput: () => executor.collect(opened, 50),
7301
- validateOutput: (completion) => {
7302
- const messages = completion.messages ?? [];
7303
- if (completion.lengthCapped) {
7304
- throw new Error("classify returned length-capped output");
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 }]
7305
7591
  }
7306
- const text = completion.text;
7307
- if (!text)
7308
- throw new Error("classify returned no output");
7309
- try {
7310
- validateClassifyManifest(text, new Set(chunk.map((candidate) => candidate.id)));
7311
- } catch (error) {
7312
- const providerFailure = providerOutputFailureFromInvalidManifest(messages, text);
7313
- if (providerFailure)
7314
- throw providerFailure;
7315
- throw error;
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;
7316
7616
  }
7317
- return text;
7318
- }
7319
- });
7320
- promptSettled = true;
7617
+ });
7618
+ promptSettled = true;
7619
+ return run;
7620
+ };
7621
+ if (moduleRoute) {
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
+ });
7637
+ recordInvocation(args, startedAt, {
7638
+ status: "completed",
7639
+ moduleAccounting: accounting
7640
+ });
7641
+ return run;
7642
+ }
7643
+ const run = await runOnHostCarrier(withContentLanguageDirective(CLASSIFY_SYSTEM_PROMPT, args.language));
7321
7644
  recordInvocation(args, startedAt, {
7322
7645
  status: "completed",
7323
7646
  messages: run.output.messages,
@@ -7330,7 +7653,8 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
7330
7653
  log(`[dreamer] classify chunk failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
7331
7654
  recordInvocation(args, startedAt, {
7332
7655
  status: failedInvocationStatus(failure),
7333
- error: failure
7656
+ error: failure,
7657
+ messages: args.hiddenCompletionExecutor ? undefined : await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
7334
7658
  });
7335
7659
  if (moduleRoute || failure instanceof HiddenCompletionRefusal || signal.aborted || failure instanceof DreamerProviderOutputFailureError || getPromptFailureDetail(failure)?.failureClass !== "parse_failed" && getPromptFailureDetail(failure) !== null)
7336
7660
  throw failure;
@@ -7344,7 +7668,7 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
7344
7668
  });
7345
7669
  }
7346
7670
  }
7347
- async function runClassifyThroughModule(args, chunk, anchors, signal) {
7671
+ async function runClassifyThroughModule(args, chunk, anchors, signal, runOnHost) {
7348
7672
  const prompt = buildClassifyPrompt({
7349
7673
  projectPath: args.projectIdentity,
7350
7674
  memories: chunk.map(toPromptMemory2),
@@ -7352,7 +7676,8 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
7352
7676
  });
7353
7677
  const modelChain = [args.model, ...args.fallbackModels ?? []].map(toModelEntry).filter((entry) => entry !== undefined).map((entry) => entry.model);
7354
7678
  const resolvedModelChain = [...new Set(modelChain)];
7355
- 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({
7356
7681
  sessionId: args.moduleSessionId,
7357
7682
  projectRoot: args.moduleProjectRoot,
7358
7683
  method: "dreamer.run_task",
@@ -7361,21 +7686,29 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
7361
7686
  v: 1,
7362
7687
  session_id: args.moduleSessionId,
7363
7688
  task: "classify",
7364
- 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,
7365
7690
  authority_generation: args.moduleAuthorityGeneration,
7366
- ...resolvedModelChain.length > 0 ? { model_chain: resolvedModelChain } : {},
7691
+ model_chain: resolvedModelChain,
7367
7692
  payload: {
7368
7693
  prompt_body: prompt,
7369
7694
  items: chunk.map((candidate) => ({
7370
7695
  memory_id: candidate.id,
7371
7696
  content_hash: candidate.normalizedHash
7372
7697
  }))
7373
- }
7698
+ },
7699
+ ...hostCompletion ? { host_completion: hostCompletion } : {}
7374
7700
  },
7375
7701
  signal,
7376
- timeoutMs: CLASSIFY_MODULE_RUN_TIMEOUT_MS
7702
+ timeoutMs: Math.max(1, resolvedModelChain.length) * CLASSIFY_MODULE_RUN_TIMEOUT_MS + 30000
7377
7703
  });
7378
- 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
+ }
7379
7712
  if (!result || typeof result !== "object")
7380
7713
  throw new Error("module returned invalid classify result");
7381
7714
  const manifestText = result.manifest_text;
@@ -7384,6 +7717,7 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
7384
7717
  if (result.truncated === true) {
7385
7718
  throw new Error("classify returned length-capped output");
7386
7719
  }
7720
+ const accounting = moduleClassifyAccounting(result);
7387
7721
  const parsed = validateClassifyManifest(manifestText, new Set(chunk.map((candidate) => candidate.id)));
7388
7722
  const rows = parsed.map((entry) => {
7389
7723
  const candidate = chunk.find((item) => item.id === entry.id);
@@ -7456,7 +7790,39 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
7456
7790
  throw new Error(`module accepted unknown memory ${moduleId}`);
7457
7791
  return candidate.contextMemory.id;
7458
7792
  });
7459
- return { classified: acceptedContextIds.length, changed: acceptedContextIds.length };
7793
+ return {
7794
+ classified: acceptedContextIds.length,
7795
+ changed: acceptedContextIds.length,
7796
+ accounting
7797
+ };
7798
+ }
7799
+ function nonNegativeCount(value) {
7800
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
7801
+ }
7802
+ function moduleClassifyAccounting(result) {
7803
+ const accounting = {};
7804
+ const usage = result.usage;
7805
+ if (usage && typeof usage === "object") {
7806
+ const record = usage;
7807
+ accounting.tokens = {
7808
+ input: nonNegativeCount(record.input),
7809
+ output: nonNegativeCount(record.output),
7810
+ cacheRead: nonNegativeCount(record.cache_read),
7811
+ cacheWrite: nonNegativeCount(record.cache_write)
7812
+ };
7813
+ }
7814
+ const diagnostics = result.diagnostics;
7815
+ const model = diagnostics && typeof diagnostics === "object" ? diagnostics.model : undefined;
7816
+ if (typeof model === "string") {
7817
+ const parsed = parseProviderModel(model);
7818
+ if (parsed) {
7819
+ accounting.providerId = parsed.providerID;
7820
+ accounting.modelId = parsed.modelID;
7821
+ } else if (model.trim()) {
7822
+ accounting.modelId = model.trim();
7823
+ }
7824
+ }
7825
+ return accounting;
7460
7826
  }
7461
7827
  function applyClassifications(args, chunk, manifestText) {
7462
7828
  const byId = new Map(chunk.map((m) => [m.id, m]));
@@ -7501,6 +7867,11 @@ function recordInvocation(args, startedAt, params) {
7501
7867
  providerId: params.completion.providerId,
7502
7868
  modelId: params.completion.modelId
7503
7869
  } : {},
7870
+ ...params.moduleAccounting ? {
7871
+ tokens: params.moduleAccounting.tokens,
7872
+ providerId: params.moduleAccounting.providerId,
7873
+ modelId: params.moduleAccounting.modelId
7874
+ } : {},
7504
7875
  error: params.error
7505
7876
  });
7506
7877
  }
@@ -8099,7 +8470,7 @@ async function guardedReadFile(projectRoot, repoRelativePath, signal, fileLimitB
8099
8470
  const body = guardedReadFileBody(projectRoot, repoRelativePath, signal, fileLimitBytes);
8100
8471
  let onAbort;
8101
8472
  const abort = new Promise((_, reject) => {
8102
- onAbort = () => reject(abortError(signal));
8473
+ onAbort = () => reject(abortError2(signal));
8103
8474
  if (signal.aborted)
8104
8475
  onAbort();
8105
8476
  else
@@ -8169,9 +8540,9 @@ async function closeLateOpenOnAbort(openPromise, signal) {
8169
8540
  return handle;
8170
8541
  if (handle)
8171
8542
  handle.close().catch(() => {});
8172
- throw abortError(signal);
8543
+ throw abortError2(signal);
8173
8544
  }
8174
- function abortError(signal) {
8545
+ function abortError2(signal) {
8175
8546
  return signal.reason instanceof SmartNoteNetworkError ? signal.reason : new SmartNoteNetworkError("SMART_NOTE_NETWORK: aborted");
8176
8547
  }
8177
8548
  function isPathInside(root, target) {
@@ -8990,9 +9361,9 @@ async function evaluateSmartNotes(args) {
8990
9361
  throw new Error(`Dream lease lost during smart-notes ${phase}`);
8991
9362
  }
8992
9363
  };
8993
- const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => {
9364
+ const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => {
8994
9365
  leaseLost = true;
8995
- leaseAbortController.abort(new Error("Dream lease lost during smart notes"));
9366
+ leaseAbortController.abort(new Error(reason));
8996
9367
  log("[dreamer] smart notes: lease lost — aborting");
8997
9368
  args.onLeaseLost?.("smart notes");
8998
9369
  }, args.leaseAcquisition);
@@ -9307,6 +9678,7 @@ async function confirmReadOnly(args, noteId, content, surfaceCondition, leaseSig
9307
9678
  parts: [{ type: "text", text: prompt, synthetic: true }]
9308
9679
  }
9309
9680
  }, {
9681
+ transport: createPromptAsyncTransport(client, childSessionId),
9310
9682
  timeoutMs: Math.max(1000, args.deadline - Date.now()),
9311
9683
  signal: promptSignal.signal,
9312
9684
  fallbackModels: args.fallbackModels,
@@ -9614,7 +9986,7 @@ function computeMapBatchSliceMs(remainingMs, batchesRemaining) {
9614
9986
  return Math.min(remainingMs, Math.max(MAP_BATCH_FLOOR_MS, Math.floor(remainingMs / batchesRemaining)));
9615
9987
  }
9616
9988
  function isTimeoutClassError2(error) {
9617
- return error instanceof Error && /^prompt timed out after \d+ms$/.test(error.message);
9989
+ return isPromptTimeoutError(error);
9618
9990
  }
9619
9991
  function shouldRequeueIndependentMapping(state, content, repoDir) {
9620
9992
  if (!state.hasSentinel || state.files.length > 0)
@@ -9669,7 +10041,7 @@ async function mapMemories(args) {
9669
10041
  }
9670
10042
  result.remaining = inputs.length;
9671
10043
  const abortController = new AbortController;
9672
- 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);
9673
10045
  try {
9674
10046
  let consecutiveTimeouts = 0;
9675
10047
  let timeoutStreakElapsedMs = [];
@@ -9784,6 +10156,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
9784
10156
  parts: [{ type: "text", text: prompt, synthetic: true }]
9785
10157
  }
9786
10158
  }, {
10159
+ transport: createPromptAsyncTransport(client, agentSessionId),
9787
10160
  timeoutMs: sliceMs,
9788
10161
  signal,
9789
10162
  fallbackModels: args.fallbackModels,
@@ -9818,7 +10191,11 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
9818
10191
  } catch (error) {
9819
10192
  const desc = describeError(error);
9820
10193
  log(`[dreamer] map-memories batch failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
9821
- 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
+ });
9822
10199
  if (error instanceof DreamerModuleFailureError)
9823
10200
  throw error;
9824
10201
  if (signal.aborted)
@@ -10143,7 +10520,7 @@ function pruneExpiredPrimerCandidatesForProject(db, projectIdentity, now = Date.
10143
10520
  db.transaction(() => {
10144
10521
  for (const id of toDelete)
10145
10522
  stmt.run(id, projectIdentity);
10146
- })();
10523
+ }).immediate();
10147
10524
  return toDelete.length;
10148
10525
  }
10149
10526
  async function promotePrimers(args) {
@@ -10562,13 +10939,7 @@ var FRUSTRATION_MARKER_REGEX = /\b(?:not what i asked|i already (?:said|told you
10562
10939
  var LEARNINGS_BLOCK_REGEX = /<learnings\b[^>]*>(.*?)<\/learnings>/is;
10563
10940
  var LEARNING_REGEX = /<learning\b([^>]*)>(.*?)<\/learning>/gis;
10564
10941
  var ATTR_REGEX = /([a-zA-Z_:-]+)\s*=\s*"([^"]*)"/g;
10565
- var VALID_MEMORY_CATEGORIES = new Set([
10566
- "PROJECT_RULES",
10567
- "ARCHITECTURE",
10568
- "CONSTRAINTS",
10569
- "CONFIG_VALUES",
10570
- "NAMING"
10571
- ]);
10942
+ var VALID_MEMORY_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
10572
10943
  var RAW_QUOTE_REGEX = /["“”][^"“”]{4,}["“”]|'[^']{4,}'/;
10573
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;
10574
10945
  function parseRetrospectiveLearnings(text) {
@@ -11327,6 +11698,10 @@ function validateVerifyManifest(text, expectedIds) {
11327
11698
  // ../plugin/src/features/magic-context/dreamer/verify.ts
11328
11699
  var VERIFY_BATCH_SIZE = 50;
11329
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
+ }
11330
11705
  function closeBroadCycle(args, cycleStartAt) {
11331
11706
  if (!args.forceBroad || cycleStartAt === undefined)
11332
11707
  return;
@@ -11381,17 +11756,19 @@ async function runVerify(args) {
11381
11756
  batches.push(gate.inScope.slice(i, i + VERIFY_BATCH_SIZE));
11382
11757
  }
11383
11758
  const abortController = new AbortController;
11384
- 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);
11385
11760
  let consecutiveProviderFailures = 0;
11386
11761
  let priorProviderFailureFingerprint = null;
11387
11762
  let lastProviderFailure = null;
11388
11763
  try {
11389
11764
  for (let i = 0;i < batches.length; i += 1) {
11390
11765
  const remainingMs = Math.max(0, args.deadline - Date.now());
11391
- 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`);
11392
11769
  break;
11393
- const batchesRemaining = batches.length - i;
11394
- const sliceMs = Math.max(1, Math.floor(remainingMs / batchesRemaining));
11770
+ }
11771
+ const sliceMs = computeVerifyBatchSliceMs(remainingMs, batches.length - i);
11395
11772
  const counts = await verifyOneBatch(args, batches[i], sliceMs, abortController.signal);
11396
11773
  result.verified += counts.verified;
11397
11774
  result.updated += counts.updated;
@@ -11402,6 +11779,11 @@ async function runVerify(args) {
11402
11779
  result.remaining -= batchProcessed;
11403
11780
  result.batches += 1;
11404
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
+ }
11405
11787
  if (counts.providerFailure) {
11406
11788
  lastProviderFailure = counts.providerFailure;
11407
11789
  if (counts.providerFailure.fingerprint === priorProviderFailureFingerprint) {
@@ -11490,6 +11872,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11490
11872
  parts: [{ type: "text", text: prompt, synthetic: true }]
11491
11873
  }
11492
11874
  }, {
11875
+ transport: createPromptAsyncTransport(client, agentSessionId),
11493
11876
  timeoutMs: sliceMs,
11494
11877
  signal,
11495
11878
  fallbackModels: args.fallbackModels,
@@ -11524,8 +11907,24 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11524
11907
  const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
11525
11908
  const promptFailure = getPromptFailureDetail(error);
11526
11909
  log(`[dreamer] verify batch ${providerFailure ? "provider failure" : "failed"}: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
11527
- recordInvocation4(args, startedAt, { status: failedInvocationStatus(error), error });
11528
- 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)
11529
11928
  throw error;
11530
11929
  return {
11531
11930
  verified: 0,
@@ -11767,6 +12166,16 @@ function dreamRunFailureDetail(error) {
11767
12166
  }
11768
12167
  const described = describeError(error);
11769
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
+ }
11770
12179
  const providerFailure = error instanceof HiddenCompletionRefusal || error instanceof Error && error.name === "DreamerProviderOutputFailureError";
11771
12180
  return {
11772
12181
  failure_class: providerFailure ? "provider_error" : /no models?|model chain is empty/i.test(message) ? "no_models" : "unknown",
@@ -11906,11 +12315,16 @@ function createDreamTaskExecutor(deps) {
11906
12315
  });
11907
12316
  };
11908
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
+ }
11909
12324
  const incompleteMessage = (remaining) => {
11910
12325
  const processed = processedDreamTaskItems(backlogAtStart.pending, remaining);
11911
12326
  return `${config.task} incomplete: ${remaining} remain (was ${backlogAtStart.pending} at run start; processed ${processed} this run)`;
11912
12327
  };
11913
- const parent = await resolveParentSessionId();
11914
12328
  let moduleRoute;
11915
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") {
11916
12330
  try {
@@ -11926,6 +12340,19 @@ function createDreamTaskExecutor(deps) {
11926
12340
  throw new DreamerModuleFailureError("authority.status", error);
11927
12341
  }
11928
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();
11929
12356
  if (!leaseOwnershipMatches(db, holderId, leaseAcquisition.generation, leaseKey)) {
11930
12357
  throw new Error("Dream lease lost during executor setup");
11931
12358
  }
@@ -12115,13 +12542,14 @@ function createDreamTaskExecutor(deps) {
12115
12542
  });
12116
12543
  return { status: "completed" };
12117
12544
  }
12118
- 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);
12119
12547
  recordRun("failed", error, {
12120
12548
  progress: verificationProgress,
12121
12549
  memoryChanges: computeMemoryDelta(memoryBefore),
12122
12550
  backlogAfter
12123
12551
  });
12124
- return { status: "failed", transient: true, error };
12552
+ return { status: "failed", transient: !batchTimedOut, error };
12125
12553
  }
12126
12554
  recordRun("completed", null, {
12127
12555
  progress: verificationProgress,
@@ -12947,25 +13375,45 @@ function ensureSeeded(db, projectIdentity, config, now) {
12947
13375
  const nextDueAt = nextDueAtMs(config.schedule, now);
12948
13376
  seedTaskScheduleState(db, projectIdentity, config.task, nextDueAt, lastRunAt, config.schedule);
12949
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
+ }
12950
13383
  function reconcileSchedule(db, projectIdentity, config, now) {
12951
13384
  ensureSeeded(db, projectIdentity, config, now);
12952
- const stored = getTaskScheduleState(db, projectIdentity, config.task);
12953
- if (!stored || stored.schedule === config.schedule)
12954
- return;
12955
- if (config.schedule.trim() === "") {
12956
- writeTaskScheduleState(db, { ...stored, schedule: config.schedule, nextDueAt: null });
12957
- return;
12958
- }
12959
- if (stored.schedule === null && stored.nextDueAt !== null) {
12960
- writeTaskScheduleState(db, { ...stored, schedule: config.schedule });
13385
+ if (getTaskScheduleState(db, projectIdentity, config.task)?.schedule === config.schedule)
12961
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
+ }
12962
13416
  }
12963
- writeTaskScheduleState(db, {
12964
- ...stored,
12965
- schedule: config.schedule,
12966
- nextDueAt: nextDueAtMs(config.schedule, now),
12967
- retryCount: 0
12968
- });
12969
13417
  }
12970
13418
  function planDueTasks(db, projectIdentity, tasks, now) {
12971
13419
  const pruned = pruneNonCanonicalTaskRows(db, projectIdentity, CANONICAL_DREAM_TASKS);
@@ -12974,6 +13422,8 @@ function planDueTasks(db, projectIdentity, tasks, now) {
12974
13422
  }
12975
13423
  const due = [];
12976
13424
  for (const config of tasks) {
13425
+ if (config.schedule.trim() === "")
13426
+ continue;
12977
13427
  reconcileSchedule(db, projectIdentity, config, now);
12978
13428
  const state = getTaskScheduleState(db, projectIdentity, config.task);
12979
13429
  if (!state || state.nextDueAt === null)
@@ -13059,10 +13509,11 @@ async function runDomainGroup(deps, group, cb) {
13059
13509
  }
13060
13510
  try {
13061
13511
  for (const due of [...group].sort((a, b) => compareTaskOrder(a.config.task, b.config.task))) {
13062
- if (!leaseOwnershipMatches(db, holderId, acquisition.generation, leaseKey)) {
13512
+ if (!reacquireOwnedLease(db, holderId, leaseKey, acquisition.generation)) {
13063
13513
  log(`[dreamer] domain lease lost (${leaseKey}) — stopping remaining task(s)`);
13064
13514
  break;
13065
13515
  }
13516
+ acquisition = { acquiredAt: Date.now(), generation: acquisition.generation };
13066
13517
  if (!cb?.forceGate) {
13067
13518
  const gatePass = evaluateTaskGate(due.config.task, {
13068
13519
  db,
@@ -13651,7 +14102,8 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
13651
14102
  }
13652
14103
  const dreamerConfig = reg.dreamerConfig;
13653
14104
  const dreamingEnabled = Boolean(dreamerConfig && dreamerConfig.disable !== true);
13654
- 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;
13655
14107
  await sweepOrphanedInternalChildren(reg, runtimeConfigs.filter((config) => PRIVACY_SENSITIVE_CHILD_TASKS.includes(config.task)).map((config) => config.timeoutMinutes));
13656
14108
  if (commitIndexingEnabled && reg.gitCommitIndexing) {
13657
14109
  await sweepGitCommits({
@@ -13666,6 +14118,10 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
13666
14118
  }
13667
14119
  try {
13668
14120
  await runCompiledSmartNoteSweep(reg, db);
14121
+ } catch (error) {
14122
+ log(`[dreamer] compiled smart-note sweep failed for ${reg.projectIdentity}:`, error);
14123
+ }
14124
+ try {
13669
14125
  const executor = createDreamTaskExecutor({
13670
14126
  client: reg.client,
13671
14127
  sessionDirectory: reg.directory,
@@ -13720,6 +14176,10 @@ async function sweepOrphanedInternalChildren(reg, privacyTimeoutMinutes) {
13720
14176
  }
13721
14177
  }
13722
14178
  async function runCompiledSmartNoteSweep(reg, db) {
14179
+ if (getAuthorityManagedMarker(db, reg.projectIdentity)) {
14180
+ logDreamerNotOwnerOnce(reg.projectIdentity);
14181
+ return;
14182
+ }
13723
14183
  const leaseKey = leaseKeyFor("evaluate-smart-notes", reg.projectIdentity);
13724
14184
  const holderId = crypto.randomUUID();
13725
14185
  if (!acquireLease(db, holderId, leaseKey))
@@ -15365,6 +15825,40 @@ function extractPiTextContent(content) {
15365
15825
  }
15366
15826
 
15367
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
+ }
15368
15862
  var PI_DREAMER_PROJECTS = Symbol.for("magic-context.pi.dreamer-registered-projects");
15369
15863
  function getRegisteredProjects() {
15370
15864
  const globals = globalThis;
@@ -15393,7 +15887,8 @@ var sessionCounter = 0;
15393
15887
  var piSubagentRunnerFactory = () => new PiSubagentRunner;
15394
15888
  var startDreamScheduleTimerFn = startDreamScheduleTimer;
15395
15889
  function registerPiDreamerProject(opts) {
15396
- if (opts.config.disable === true) {
15890
+ const directory = resolve3(opts.projectDir);
15891
+ if (opts.config.disable === true || directory === parse(directory).root || directory === homedir2()) {
15397
15892
  return;
15398
15893
  }
15399
15894
  const existing = registeredProjects2.get(opts.projectIdentity);
@@ -15406,7 +15901,7 @@ function registerPiDreamerProject(opts) {
15406
15901
  callback(projectIdentity);
15407
15902
  };
15408
15903
  if (existing) {
15409
- if (existing.generation && existing.projectDir === opts.projectDir) {
15904
+ if (existing.generation && existing.projectDir === opts.projectDir && (!opts.modelRegistry || existing.modelRegistry)) {
15410
15905
  return;
15411
15906
  }
15412
15907
  existing.cleanup();
@@ -15419,12 +15914,14 @@ function registerPiDreamerProject(opts) {
15419
15914
  });
15420
15915
  let cleanup;
15421
15916
  let cancelled = false;
15917
+ const modelRegistry = opts.modelRegistry;
15422
15918
  startDreamScheduleTimerFn({
15423
15919
  directory: opts.projectDir,
15424
15920
  projectIdentity: opts.projectIdentity,
15425
15921
  harness: opts.harness,
15426
15922
  client,
15427
15923
  dreamerConfig: opts.config,
15924
+ validateTaskModels: modelRegistry ? (tasks) => validatePiDreamerModels(tasks, modelRegistry) : undefined,
15428
15925
  sampleDreamRun: opts.sampleDreamRun,
15429
15926
  language: opts.language,
15430
15927
  gitCommitIndexing: opts.gitCommitIndexing,
@@ -15456,7 +15953,7 @@ function registerPiDreamerProject(opts) {
15456
15953
  const manualRun = runManualDream({
15457
15954
  db: manualOpts.db,
15458
15955
  projectIdentity: manualOpts.projectIdentity,
15459
- 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),
15460
15957
  executor: createDreamTaskExecutor({
15461
15958
  client: manualClient,
15462
15959
  sessionDirectory: manualOpts.projectDir,
@@ -15489,7 +15986,8 @@ function registerPiDreamerProject(opts) {
15489
15986
  cleanup?.();
15490
15987
  },
15491
15988
  runManual,
15492
- projectDir: opts.projectDir
15989
+ projectDir: opts.projectDir,
15990
+ modelRegistry: opts.modelRegistry
15493
15991
  });
15494
15992
  }
15495
15993
  async function runPiDreamForProject(projectIdentity, task, registrationOwner) {
@@ -16001,7 +16499,7 @@ function formatEmbedStatusText(coverage, drain) {
16001
16499
  lines.push(`Synapse — ${formatSynapseLaneDescriptor(coverage.synapseDescriptor)}`);
16002
16500
  }
16003
16501
  lines.push(`This session: ${coverage.session.embedded} / ${coverage.session.total} compartments embedded`);
16004
- 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`);
16005
16503
  if (coverage.commits.gitEnabled) {
16006
16504
  lines.push(`Git commits: ${coverage.commits.embedded} / ${coverage.commits.total}`);
16007
16505
  } else {
@@ -16177,7 +16675,6 @@ No active Pi session is available.`,
16177
16675
  projectDir: deps.projectDir,
16178
16676
  projectIdentity: deps.projectIdentity
16179
16677
  };
16180
- const memoryEnabled = deps.resolveMemoryEnabled?.(ctx) ?? deps.memoryEnabled;
16181
16678
  const sub = args.trim().toLowerCase();
16182
16679
  if (sub === "pause") {
16183
16680
  embedPauseBySession.add(sessionId);
@@ -16194,16 +16691,6 @@ Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`,
16194
16691
  });
16195
16692
  return;
16196
16693
  }
16197
- if (memoryEnabled === false) {
16198
- sendStatus({
16199
- title: "/ctx-embed",
16200
- text: `## /ctx-embed
16201
-
16202
- Memory is disabled for this project, so there is no semantic embedding to backfill.`,
16203
- level: "info"
16204
- });
16205
- return;
16206
- }
16207
16694
  await ensureProjectRegisteredFromPiDirectory(project.projectDir, deps.db);
16208
16695
  if (sub === "start") {
16209
16696
  const { text, level } = await runEmbedDrain(deps.db, project.projectIdentity, sessionId, {
@@ -16236,13 +16723,11 @@ ${statusText}`,
16236
16723
  }
16237
16724
  });
16238
16725
  }
16239
- function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity, _notify) {
16726
+ function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity) {
16240
16727
  if (autoEmbedAttemptedBySession.has(sessionId))
16241
16728
  return;
16242
16729
  if (embedPauseBySession.has(sessionId))
16243
16730
  return;
16244
- if (deps.memoryEnabled === false)
16245
- return;
16246
16731
  autoEmbedAttemptedBySession.add(sessionId);
16247
16732
  (async () => {
16248
16733
  let drainReachedTerminal = false;
@@ -16278,7 +16763,7 @@ function executeFlush(db, sessionId) {
16278
16763
  removePendingOp(db, sessionId, op.tagId);
16279
16764
  dropped++;
16280
16765
  }
16281
- })();
16766
+ }).immediate();
16282
16767
  const parts = [];
16283
16768
  if (dropped > 0)
16284
16769
  parts.push(`${dropped} dropped`);
@@ -16577,7 +17062,7 @@ function createTagger() {
16577
17062
  db.transaction(() => {
16578
17063
  getUpsertCounterStatement(db).run(sessionId, next, getHarness());
16579
17064
  insertTag(db, sessionId, messageId, type, byteSize, next, reasoningByteSize, toolName, inputByteSize, toolOwnerMessageId, entryFingerprint, tokenCounts);
16580
- })();
17065
+ }).immediate();
16581
17066
  } catch (error) {
16582
17067
  if (!isUniqueConstraintError(error)) {
16583
17068
  throw error;
@@ -16813,7 +17298,8 @@ var canonicalReasons = new Set([
16813
17298
  "ttl_expiry",
16814
17299
  "epoch_change",
16815
17300
  "coverage_fold",
16816
- "profile_transition"
17301
+ "profile_transition",
17302
+ "host_compaction"
16817
17303
  ]);
16818
17304
  var piReasonAliases = {
16819
17305
  project_memory_change: "project_memory_epoch",
@@ -17047,6 +17533,72 @@ var RECENT_TOOL_SKELETON_WINDOW = 20;
17047
17533
  function buildReplacementContent(tagId) {
17048
17534
  return `[dropped §${tagId}§]`;
17049
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
+ }
17050
17602
  function applyPendingOperations(sessionId, db, targets, protectedTagIds, preloadedTags, preloadedPendingOps, syntheticPendingOps = [], editMarkerTagIds = new Set, onTagReduced, onBatchComplete) {
17051
17603
  let didMutateMessage = false;
17052
17604
  let diagnostics;
@@ -17106,7 +17658,8 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
17106
17658
  let shouldPersistDrop = false;
17107
17659
  if (isToolTag) {
17108
17660
  if (editMarkerTagIds.has(pendingOp.tagId)) {
17109
- const markResult = target?.editMarker?.() ?? "absent";
17661
+ const strip = target?.hasAttachments?.() === true;
17662
+ const markResult = (strip ? target?.editMarkerStripped?.() : target?.editMarker?.()) ?? "absent";
17110
17663
  if (markResult === "incomplete" || markResult === "absent") {
17111
17664
  reject(`edit_marker_${markResult}`);
17112
17665
  continue;
@@ -17114,34 +17667,23 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
17114
17667
  didMutateMessage = true;
17115
17668
  operationMutated = true;
17116
17669
  onTagReduced?.({ tagNumber: pendingOp.tagId, mode: "edit_marker" });
17117
- updateTagDropMode(db, sessionId, pendingOp.tagId, "edit_marker");
17118
- shouldPersistDrop = true;
17119
- } else if (skeletonWindow.has(pendingOp.tagId)) {
17120
- const truncResult = target?.truncate?.() ?? "absent";
17121
- if (truncResult === "incomplete" || synthetic && truncResult !== "truncated") {
17122
- reject(`truncate_${truncResult}`);
17123
- continue;
17124
- }
17125
- if (truncResult === "truncated") {
17126
- didMutateMessage = true;
17127
- operationMutated = true;
17128
- onTagReduced?.({ tagNumber: pendingOp.tagId, mode: "truncated" });
17129
- } else {
17130
- reject(`truncate_${truncResult}`);
17131
- }
17132
- updateTagDropMode(db, sessionId, pendingOp.tagId, "truncated");
17670
+ updateTagDropMode(db, sessionId, pendingOp.tagId, strip ? "edit_marker_stripped" : "edit_marker");
17133
17671
  shouldPersistDrop = true;
17134
17672
  } else {
17135
- const dropResult = target?.drop?.() ?? "absent";
17673
+ const { result: dropResult, mode: appliedMode } = applyNewToolDrop(target, {
17674
+ inWindow: skeletonWindow.has(pendingOp.tagId)
17675
+ });
17136
17676
  if (dropResult === "incomplete" || synthetic && dropResult !== "removed" && dropResult !== "truncated") {
17137
17677
  reject(`drop_${dropResult}`);
17138
17678
  continue;
17139
17679
  }
17140
- const appliedMode = dropResult === "truncated" ? "truncated" : "full";
17141
17680
  if (dropResult === "removed" || dropResult === "truncated") {
17142
17681
  didMutateMessage = true;
17143
17682
  operationMutated = true;
17144
- onTagReduced?.({ tagNumber: pendingOp.tagId, mode: appliedMode });
17683
+ onTagReduced?.({
17684
+ tagNumber: pendingOp.tagId,
17685
+ mode: appliedMode === "full" ? "full" : "truncated"
17686
+ });
17145
17687
  } else {
17146
17688
  reject(`drop_${dropResult}`);
17147
17689
  }
@@ -17208,11 +17750,24 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
17208
17750
  if (tag.status === "dropped") {
17209
17751
  const target = targets.get(tag.tagNumber);
17210
17752
  if (tag.type === "tool") {
17211
- 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") {
17212
17758
  const markResult = target?.editMarker?.() ?? "absent";
17213
17759
  if (markResult === "truncated") {
17214
17760
  didMutateMessage = true;
17215
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
+ }
17216
17771
  } else if (tag.dropMode === "truncated") {
17217
17772
  const truncResult = target?.truncate?.() ?? "absent";
17218
17773
  if (truncResult === "truncated") {
@@ -17558,7 +18113,7 @@ function applyCavemanCleanup(sessionId, db, targets, tags, config) {
17558
18113
  else if (targetDepth === DEPTH_ULTRA)
17559
18114
  result.compressedToUltra += 1;
17560
18115
  }
17561
- })();
18116
+ }).immediate();
17562
18117
  const total = result.compressedToLite + result.compressedToFull + result.compressedToUltra;
17563
18118
  if (total > 0) {
17564
18119
  sessionLog(sessionId, `caveman cleanup: compressed ${total} text tags (lite=${result.compressedToLite}, full=${result.compressedToFull}, ultra=${result.compressedToUltra})`);
@@ -19480,6 +20035,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19480
20035
  let any = false;
19481
20036
  for (const occ of occurrences) {
19482
20037
  if (occ.kind === "tool_use" && occ.part.setToolInput) {
20038
+ occ.originalInput ??= occ.part.getToolInput?.() ?? undefined;
19483
20039
  if (occ.part.setToolInput(droppedInputMarker(tagId)))
19484
20040
  any = true;
19485
20041
  } else if (setToolContentOrText(occ.part, sentinel)) {
@@ -19488,6 +20044,28 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19488
20044
  }
19489
20045
  return any ? "truncated" : "absent";
19490
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
+ };
19491
20069
  return {
19492
20070
  measureReclaim(skeleton) {
19493
20071
  let beforeTools = 0;
@@ -19498,7 +20076,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19498
20076
  const before = occ.kind === "tool_use" ? JSON.stringify(occ.part.getToolInput?.() ?? {}) : occ.part.getText() ?? "";
19499
20077
  beforeTools += estimateTokens(before);
19500
20078
  if (skeleton || !removable) {
19501
- const after = occ.kind === "tool_use" ? JSON.stringify(droppedInputMarker(tagId)) : `[dropped §${tagId}§]`;
20079
+ const after = occ.kind === "tool_use" ? before : `[dropped §${tagId}§]`;
19502
20080
  afterTools += estimateTokens(after);
19503
20081
  }
19504
20082
  }
@@ -19527,8 +20105,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19527
20105
  return occurrences[0]?.part.getText() ?? null;
19528
20106
  },
19529
20107
  drop() {
19530
- const complete = occurrences.some((occ) => occ.kind === "tool_use") && occurrences.some((occ) => occ.kind === "tool_result");
19531
- if (!complete)
20108
+ if (!complete())
19532
20109
  return "incomplete";
19533
20110
  if (!requiresToolArcSkeleton) {
19534
20111
  const authorization = occurrences.map((occ) => occ.part.canRemove?.());
@@ -19536,7 +20113,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19536
20113
  return "incomplete";
19537
20114
  if (occurrences.every((occ, index) => occ.part.remove && authorization[index] !== false)) {
19538
20115
  if (conversationEnd?.wouldStrand(occurrences))
19539
- return truncate();
20116
+ return skeletonReal();
19540
20117
  let removed = false;
19541
20118
  for (const occ of occurrences)
19542
20119
  removed = occ.part.remove?.() || removed;
@@ -19554,6 +20131,19 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19554
20131
  return any ? "removed" : "absent";
19555
20132
  },
19556
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
+ },
19557
20147
  editMarker() {
19558
20148
  const sentinel = `[dropped §${tagId}§]`;
19559
20149
  let any = false;
@@ -19572,9 +20162,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19572
20162
  }
19573
20163
  return any ? "truncated" : "absent";
19574
20164
  },
19575
- canDrop() {
19576
- return occurrences.some((occ) => occ.kind === "tool_use") && occurrences.some((occ) => occ.kind === "tool_result");
19577
- },
20165
+ canDrop: complete,
19578
20166
  requiresToolArcSkeleton,
19579
20167
  readInput() {
19580
20168
  for (const occ of occurrences) {
@@ -19620,6 +20208,15 @@ function buildToolTarget(part, message, tagId) {
19620
20208
  const changed = part.setToolInput ? part.setToolInput(droppedInputMarker(tagId)) : setToolContentOrText(part, `[dropped §${tagId}§]`);
19621
20209
  return changed ? "truncated" : "absent";
19622
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
+ },
19623
20220
  message: {
19624
20221
  info: { id: message.info.id, role: message.info.role },
19625
20222
  parts: []
@@ -19893,7 +20490,7 @@ async function runAutoSearchHintForPi(args) {
19893
20490
  try {
19894
20491
  const snapshot = getProjectEmbeddingSnapshot(options.projectPath);
19895
20492
  const memoryEnabled = snapshot?.features.memoryEnabled ?? true;
19896
- const embeddingEnabled = snapshot ? snapshot.enabled || snapshot.gitCommitEnabled : true;
20493
+ const embeddingEnabled = snapshot ? snapshot.historyEnabled : true;
19897
20494
  const gitCommitsEnabled = snapshot?.gitCommitEnabled ?? false;
19898
20495
  const searchOptions = {
19899
20496
  limit: 10,
@@ -19957,6 +20554,8 @@ import * as crypto2 from "node:crypto";
19957
20554
  async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
19958
20555
  if (compartments.length === 0)
19959
20556
  return;
20557
+ if (getProjectChunkEmbeddingModelId(projectPath) === "off")
20558
+ return;
19960
20559
  const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
19961
20560
  for (const compartment of compartments) {
19962
20561
  try {
@@ -20113,7 +20712,7 @@ function persistFilteredNoise(db, sessionId, chunk, eligibleEnd) {
20113
20712
  }
20114
20713
  ]);
20115
20714
  return true;
20116
- })();
20715
+ }).immediate();
20117
20716
  if (saved)
20118
20717
  sessionLog(sessionId, `historian skipped ordinals ${start}-${eligibleEnd - 1}: all ${rows.length} raw rows excluded by chunk filters (no-content boundary marker)`);
20119
20718
  return saved;
@@ -22710,8 +23309,7 @@ ${chunkText}`,
22710
23309
  const discardedLast = newCompartments.length < emittedCompartments.length;
22711
23310
  const weakLookaheadFinalCompartment = forceKeepLastCompartmentForChunk;
22712
23311
  const skipUnanchoredPromotion = discardedLast || weakLookaheadFinalCompartment;
22713
- const embeddingActive = memoryEnabled !== false;
22714
- const promotionActive = embeddingActive && autoPromote !== false;
23312
+ const promotionActive = memoryEnabled !== false && autoPromote !== false;
22715
23313
  const publishableEvents = (validatedPass.events ?? []).filter((e) => {
22716
23314
  if (typeof e.atCompartment !== "number")
22717
23315
  return !weakLookaheadFinalCompartment;
@@ -22844,7 +23442,7 @@ ${chunkText}`,
22844
23442
  sessionLog(sessionId, "failed to store primer candidates:", error);
22845
23443
  }
22846
23444
  }
22847
- if (embeddingActive) {
23445
+ if (newCompartments.length > 0) {
22848
23446
  const chunksToEmbed = newCompartments.map((c, i) => ({
22849
23447
  id: persistedIds[i],
22850
23448
  startMessage: c.startMessage,
@@ -23227,7 +23825,7 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23227
23825
  const candidateTags = tags.filter((t) => t.status === "active" && t.type === "tool" && targets.get(t.tagNumber)?.canDrop?.());
23228
23826
  const recentTags = new Set(candidateTags.slice().sort((a, b) => b.tagNumber - a.tagNumber).slice(0, 20).map((tag) => tag.tagNumber));
23229
23827
  const calibration = sessionDecisionCalibration(db, sessionId);
23230
- 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))));
23231
23829
  const byTag = new Map(activeTags.map((tag) => [tag.tagNumber, tag]));
23232
23830
  const droppableTags = candidateTags.flatMap((tag) => {
23233
23831
  const measured = byTag.get(tag.tagNumber);
@@ -23256,18 +23854,18 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23256
23854
  continue;
23257
23855
  const target = targets.get(tag.tagNumber);
23258
23856
  const recent = (emergency.usagePercentage ?? 0) < 95 && newestEmergencyTags.has(tag.tagNumber);
23259
- const reasoningSafeSkeleton = target?.requiresToolArcSkeleton === true;
23260
- const skeleton = recent || reasoningSafeSkeleton;
23261
- 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
+ });
23262
23861
  if (result === "removed" || result === "truncated") {
23263
- const appliedMode = skeleton || result === "truncated" ? "truncated" : "full";
23264
23862
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
23265
- updateTagDropMode(db, sessionId, tag.tagNumber, appliedMode);
23863
+ updateTagDropMode(db, sessionId, tag.tagNumber, mode);
23266
23864
  droppedTools++;
23267
23865
  emergencyDroppedTools++;
23268
23866
  droppedTokenReductions.push({
23269
23867
  tagNumber: tag.tagNumber,
23270
- mode: appliedMode
23868
+ mode: mode === "full" ? "full" : "truncated"
23271
23869
  });
23272
23870
  }
23273
23871
  }
@@ -23291,10 +23889,10 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23291
23889
  if (!matched)
23292
23890
  continue;
23293
23891
  const target = targets.get(tag.tagNumber);
23294
- const result = target?.drop?.() ?? "absent";
23892
+ const { result, mode } = applyNewToolDrop(target, { inWindow: false });
23295
23893
  if (result === "incomplete")
23296
23894
  continue;
23297
- updateTagDropMode(db, sessionId, tag.tagNumber, result === "truncated" ? "truncated" : "full");
23895
+ updateTagDropMode(db, sessionId, tag.tagNumber, mode);
23298
23896
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
23299
23897
  if (result === "removed" || result === "truncated") {
23300
23898
  droppedStaleReduceCalls++;
@@ -23375,17 +23973,18 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23375
23973
  for (let i = 0;i < group.length - 1; i++) {
23376
23974
  const tag = group[i];
23377
23975
  const target = targets.get(tag.tagNumber);
23378
- const result = target?.drop?.() ?? "absent";
23976
+ const { result, mode } = applyNewToolDrop(target, {
23977
+ inWindow: false
23978
+ });
23379
23979
  if (result === "incomplete")
23380
23980
  continue;
23381
- const appliedMode = result === "truncated" ? "truncated" : "full";
23382
- updateTagDropMode(db, sessionId, tag.tagNumber, appliedMode);
23981
+ updateTagDropMode(db, sessionId, tag.tagNumber, mode);
23383
23982
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
23384
23983
  if (result === "removed" || result === "truncated") {
23385
23984
  deduplicatedTools++;
23386
23985
  droppedTokenReductions.push({
23387
23986
  tagNumber: tag.tagNumber,
23388
- mode: appliedMode
23987
+ mode: mode === "full" ? "full" : "truncated"
23389
23988
  });
23390
23989
  }
23391
23990
  }
@@ -26151,7 +26750,7 @@ function messageRole(message) {
26151
26750
  const role = recordValue(message, "role");
26152
26751
  return typeof role === "string" ? role : undefined;
26153
26752
  }
26154
- function messageId(message) {
26753
+ function messageId2(message) {
26155
26754
  const id = recordValue(messageInfo(message), "id") ?? recordValue(message, "id");
26156
26755
  return typeof id === "string" && id.length > 0 ? id : null;
26157
26756
  }
@@ -26284,7 +26883,7 @@ function validateLkgSeam(prefix, tail, providerKey) {
26284
26883
  const calls = new Set;
26285
26884
  const results = new Set;
26286
26885
  for (const message of all) {
26287
- const id = messageId(message);
26886
+ const id = messageId2(message);
26288
26887
  if (id !== null) {
26289
26888
  if (ids.has(id))
26290
26889
  return false;
@@ -26827,6 +27426,56 @@ function resolvePiPressureSnapshot(args) {
26827
27426
  ...validInferredLimit ? { contextLimit: inferredLimit } : {}
26828
27427
  };
26829
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
+ }
26830
27479
  function formatPiPressureForLog(snapshot) {
26831
27480
  return `usage=${snapshot.percentage.toFixed(1)}% (${snapshot.inputTokens} tokens, limit=${snapshot.contextLimit ?? "?"})`;
26832
27481
  }
@@ -27283,12 +27932,12 @@ function handlePiProviderFailure(args) {
27283
27932
  const model = typeof message.model === "string" ? message.model : undefined;
27284
27933
  const binding = detectThinkingBindingMismatch(message.errorMessage);
27285
27934
  if (binding.isBindingMismatch) {
27286
- const enabled = args.thinkingBindingRecoveryEnabled !== false && !args.compactionOff && isFable51ThinkingBindingModel(provider, model);
27935
+ const enabled = args.thinkingBindingRecoveryEnabled !== false && !args.compactionOff && isPrefixBoundThinkingModel(provider, model);
27287
27936
  if (enabled) {
27288
27937
  armThinkingBindingRecovery(args.db, args.sessionId);
27289
27938
  clearPiLkgSessionState(args.sessionId);
27290
27939
  dropSlot(args.sessionId, "thinking-binding-recovery-arm");
27291
- 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 ?? "?"})`);
27292
27941
  }
27293
27942
  return { kind: "thinking_binding", armed: enabled };
27294
27943
  }
@@ -27337,36 +27986,25 @@ function stripThinkingParts(message) {
27337
27986
  function applyPiThinkingBindingRecovery(args) {
27338
27987
  if (args.provider?.toLowerCase() !== "anthropic")
27339
27988
  return null;
27340
- const frozenEntryIds = new Set;
27341
- for (const frozenId of getMergedReasoningStrippedIds(args.db, args.sessionId)) {
27342
- if (!frozenId.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX))
27343
- continue;
27344
- const entryId = frozenId.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length);
27345
- if (entryId.length > 0)
27346
- frozenEntryIds.add(entryId);
27347
- }
27348
- 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;
27349
27991
  let applied = null;
27350
27992
  if (flagTarget) {
27351
- let messageIndex = -1;
27352
- if (flagTarget === NEWEST_REASONING_BEARING_ASSISTANT) {
27353
- for (let index = args.messages.length - 1;index >= 0; index -= 1) {
27354
- if (hasThinkingPart(args.messages[index]) && args.entryIds[index]) {
27355
- messageIndex = index;
27356
- break;
27357
- }
27358
- }
27359
- } else {
27360
- messageIndex = args.entryIds.findIndex((entryId, index) => entryId === flagTarget && hasThinkingPart(args.messages[index]));
27361
- }
27362
- const entryId = messageIndex >= 0 ? args.entryIds[messageIndex] : undefined;
27363
- if (entryId) {
27364
- const frozenId = thinkingBindingRecoveryFrozenId(entryId);
27365
- if (frozenEntryIds.has(entryId) || addMergedReasoningStrippedIds(args.db, args.sessionId, [frozenId])) {
27366
- frozenEntryIds.add(entryId);
27367
- applied = { flagTarget, entryId };
27368
- reportBindingRecovery(args.sessionId, args.report, `Fable thinking-binding recovery consumed on context pass target=${flagTarget} entry=${entryId}`);
27369
- }
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(",")}]`);
27370
28008
  }
27371
28009
  }
27372
28010
  for (let index = 0;index < args.messages.length; index += 1) {
@@ -27376,6 +28014,76 @@ function applyPiThinkingBindingRecovery(args) {
27376
28014
  }
27377
28015
  return applied;
27378
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
+ }
27379
28087
 
27380
28088
  // src/reasoning-replay-pi.ts
27381
28089
  var INLINE_THINKING_PATTERNS = [
@@ -27923,9 +28631,9 @@ var BASE_INTRO = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `#
27923
28631
 
27924
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.
27925
28633
 
27926
- 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.
27927
28635
 
27928
- 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.
27929
28637
 
27930
28638
  ${TOOL_HISTORY_GUIDANCE}
27931
28639
 
@@ -27954,7 +28662,7 @@ var SMART_NOTE_GUIDANCE_LIGHT = `; a \`surface_condition\` leaves the note with
27954
28662
  var TEMPORAL_AWARENESS_GUIDANCE_LIGHT = `, \`<!-- +Xm -->\` (time since your last reply)`;
27955
28663
  var BASE_INTRO_LIGHT = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
27956
28664
 
27957
- 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.
27958
28666
 
27959
28667
  ${TOOL_HISTORY_GUIDANCE_LIGHT}
27960
28668
 
@@ -29437,7 +30145,7 @@ function databaseIsInTransaction(db) {
29437
30145
  }
29438
30146
  function runImmediateTransaction(db, fn) {
29439
30147
  if (databaseIsInTransaction(db)) {
29440
- return db.transaction(fn)();
30148
+ return db.transaction(fn).immediate();
29441
30149
  }
29442
30150
  const transactionStartedAt = performance.now();
29443
30151
  db.exec("BEGIN IMMEDIATE");
@@ -29619,6 +30327,7 @@ function registerPiContextHandler(pi, baseOptions) {
29619
30327
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
29620
30328
  const tEntryBranch = performance.now();
29621
30329
  const branchEntries = readPiBranchEntriesForContext(ctx, sessionId);
30330
+ const piLiveUsageIsRawBranchEstimate = isPiLiveUsageRawBranchEstimate(branchEntries);
29622
30331
  schedulePiTransformDecisionResolve({
29623
30332
  db: options.db,
29624
30333
  sessionId,
@@ -29651,14 +30360,29 @@ function registerPiContextHandler(pi, baseOptions) {
29651
30360
  const lkgInputIdByRef = new Map;
29652
30361
  for (const input of lkgPassSnapshot.inputs)
29653
30362
  lkgInputIdByRef.set(event.messages[input.messageIndex], input.id);
29654
- 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({
29655
30371
  db: options.db,
29656
30372
  sessionId,
29657
30373
  messages: event.messages,
29658
30374
  entryIds: lkgEntryIds ?? [],
29659
30375
  provider: lkgProviderKey ?? undefined,
29660
30376
  model: ctx.model?.id
29661
- });
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
+ }
29662
30386
  const tMeta = performance.now();
29663
30387
  const sessionMetaForUsage = getOrCreateSessionMeta(options.db, sessionId);
29664
30388
  sessionMetaForPass = sessionMetaForUsage;
@@ -29868,7 +30592,11 @@ function registerPiContextHandler(pi, baseOptions) {
29868
30592
  if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
29869
30593
  usagePercentage = usageInputTokens / usageContextLimit * 100;
29870
30594
  }
29871
- ({ percentage: usagePercentage, inputTokens: usageInputTokens } = resolvePiPressureSnapshot({
30595
+ ({ percentage: usagePercentage, inputTokens: usageInputTokens } = resolvePiPressureSnapshotWithEstimateGuard({
30596
+ sessionId,
30597
+ source: "transform",
30598
+ liveIsRawBranchEstimate: piLiveUsageIsRawBranchEstimate,
30599
+ persistedFromLive: !usedPersistedUsage,
29872
30600
  persistedPercentage: usagePercentage,
29873
30601
  persistedInputTokens: usageInputTokens,
29874
30602
  liveInputTokens: piUsage?.tokens,
@@ -30046,6 +30774,7 @@ function registerPiContextHandler(pi, baseOptions) {
30046
30774
  sessionMeta,
30047
30775
  piUsage,
30048
30776
  minimumPercentage: usagePercentage,
30777
+ liveIsRawBranchEstimate: piLiveUsageIsRawBranchEstimate,
30049
30778
  historianStateSnapshot: historianStateForPass,
30050
30779
  publishedHistoryRide: isCacheBusting
30051
30780
  });
@@ -30123,6 +30852,34 @@ function registerPiContextHandler(pi, baseOptions) {
30123
30852
  sessionLog(sessionId, `synthetic todowrite injection failed: ${err instanceof Error ? err.message : String(err)}`);
30124
30853
  }
30125
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);
30126
30883
  const tChannelAccounting = performance.now();
30127
30884
  try {
30128
30885
  const sessionMetaForCh1 = sessionMeta;
@@ -30602,7 +31359,11 @@ function maybeFireHistorian(args) {
30602
31359
  };
30603
31360
  usageSource = "piUsage fallback";
30604
31361
  }
30605
- usage = resolvePiPressureSnapshot({
31362
+ usage = resolvePiPressureSnapshotWithEstimateGuard({
31363
+ sessionId,
31364
+ source: "historian trigger",
31365
+ liveIsRawBranchEstimate: args.liveIsRawBranchEstimate,
31366
+ persistedFromLive: usageSource === "piUsage fallback",
30606
31367
  persistedPercentage: usage.percentage,
30607
31368
  persistedInputTokens: usage.inputTokens,
30608
31369
  liveInputTokens: piUsage?.tokens,
@@ -30925,6 +31686,7 @@ async function runPipeline(args) {
30925
31686
  const foldDueDecision = piM0State ? mustMaterializePi(piM0State, args.db, injectionPassSnapshot?.compartments, injectionPassSnapshot) : { value: false, reason: null };
30926
31687
  const firstRenderBust = piM0State !== undefined && !(injectionPassSnapshot?.cachedRow?.cached_m0_bytes && injectionPassSnapshot.cachedRow.cached_m1_bytes);
30927
31688
  let foldExecutedThisPass = false;
31689
+ let foldBustsServedPrefixThisPass = false;
30928
31690
  let publishedM1RefreshedThisPass = false;
30929
31691
  let prefixPreflightContended = false;
30930
31692
  const softRefreshOpportunity = args.schedulerDecision === "execute";
@@ -30938,6 +31700,18 @@ async function runPipeline(args) {
30938
31700
  prefixPreflightContended = preFoldInjectionResult.contentionExhausted === true;
30939
31701
  publishedM1RefreshedThisPass = !prefixPreflightContended && persistedM0BeforeFold.cachedM1Bytes?.toString("utf8") !== getOrCreateSessionMeta(args.db, args.sessionId).cachedM1Bytes?.toString("utf8");
30940
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
+ }
30941
31715
  if (preFoldInjectionResult.m0Materialized) {
30942
31716
  injectionPassSnapshot = createPiM0M1PassSnapshot({
30943
31717
  db: args.db,
@@ -30965,7 +31739,7 @@ async function runPipeline(args) {
30965
31739
  sessionLog(args.sessionId, `pi m[0] HARD fold pre-execution failed: ${error instanceof Error ? error.message : String(error)}`);
30966
31740
  }
30967
31741
  const mismatch = foldDueDecision.mismatch ? ` mismatch=${JSON.stringify(foldDueDecision.mismatch)}` : "";
30968
- 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}`);
30969
31743
  }
30970
31744
  const executePressureEligible = args.schedulerDecision === "execute" || emergencyDropEligible;
30971
31745
  if (!executePressureEligible) {
@@ -30976,7 +31750,7 @@ async function runPipeline(args) {
30976
31750
  const routinePressureAlreadyApplied = !args.sessionMeta.isSubagent && executePressureEligible && routinePressureAppliedBySession.get(args.sessionId) === true;
30977
31751
  const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
30978
31752
  const rideSignals = {
30979
- hardFold: foldExecutedThisPass || firstRenderBust,
31753
+ hardFold: foldBustsServedPrefixThisPass || firstRenderBust,
30980
31754
  force: (args.forceMaterialization === true || emergencyDropEligible) && (args.contextUsage.percentage >= 95 || getEmergencyInputSample(args.db, args.sessionId) === 0),
30981
31755
  explicitFlush: hasPendingMaterializeSignal || deferredMaterializeEligible && !prefixPreflightContended,
30982
31756
  publishedHistory: !prefixPreflightContended && (args.isCacheBusting || publishedM1RefreshedThisPass || canConsumeDeferredLate && deferredHistoryWasPendingAtPassStart)
@@ -30992,7 +31766,7 @@ async function runPipeline(args) {
30992
31766
  onRejectedProjectOverride: (warning) => sessionLog(args.sessionId, warning)
30993
31767
  });
30994
31768
  let protectionFloorResolution = resolveProtectionFloor();
30995
- 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);
30996
31770
  const tFallbackIdentity = performance.now();
30997
31771
  const hasFallbackMessageTags = hasPiFallbackMessageTags(args.db, args.sessionId);
30998
31772
  const entryFingerprintByMessageId = buildEntryFingerprintMap(args.messages, stableIdResolver, args.reusableMessageIds, hasFallbackMessageTags);
@@ -31032,6 +31806,19 @@ async function runPipeline(args) {
31032
31806
  } : undefined
31033
31807
  });
31034
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
+ }
31035
31822
  try {
31036
31823
  if (!args.sessionMeta.isSubagent) {
31037
31824
  const hasRecentCommit = detectRecentCommit(args.messages);
@@ -31058,12 +31845,13 @@ async function runPipeline(args) {
31058
31845
  mutationGateObserverForTests?.({
31059
31846
  foldDue: foldDueDecision.value,
31060
31847
  foldExecuted: foldExecutedThisPass,
31848
+ foldBustsServedPrefix: foldBustsServedPrefixThisPass,
31061
31849
  shouldApplyPendingOps,
31062
31850
  shouldRunHeuristics,
31063
31851
  shouldRunReasoningCleanup: args.reasoningClearing !== undefined && shouldRunHeuristics
31064
31852
  });
31065
31853
  if (shouldApplyPendingOps) {
31066
- 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})`;
31067
31855
  const pendingOpsDepth = getPendingOpsCount(args.db, args.sessionId);
31068
31856
  const pendingDecisionLog = `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOpsDepth === null ? "not loaded (deferred pass)" : pendingOpsDepth} context=${args.contextUsage.percentage.toFixed(1)}%`;
31069
31857
  sessionLog(args.sessionId, pendingDecisionLog);
@@ -31150,7 +31938,7 @@ async function runPipeline(args) {
31150
31938
  const activeTags = allTagsForPass.filter((tag) => tag.status === "active");
31151
31939
  logTransformTiming(args.sessionId, "getTagsBySessionSnapshot", tActiveTags, `all=${allTagsForPass.length} active=${activeTags.length}`);
31152
31940
  if (shouldRunHeuristics) {
31153
- 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})`;
31154
31942
  const heuristicsDecisionLog = `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=n/a`;
31155
31943
  sessionLog(args.sessionId, heuristicsDecisionLog);
31156
31944
  pendingDecisionLogObserverForTests?.(heuristicsDecisionLog);
@@ -31163,7 +31951,7 @@ async function runPipeline(args) {
31163
31951
  if (shouldRunHeuristics && args.heuristics) {
31164
31952
  try {
31165
31953
  const tHeuristic = performance.now();
31166
- const independentMutationBeforeHeuristics = pendingOpsDidMutate || foldExecutedThisPass;
31954
+ const independentMutationBeforeHeuristics = pendingOpsDidMutate || foldBustsServedPrefixThisPass;
31167
31955
  if (args.forceMaterialization === true && independentMutationBeforeHeuristics && getEmergencyInputSample(args.db, args.sessionId) > 0) {
31168
31956
  clearEmergencyDropSample(args.db, args.sessionId);
31169
31957
  }
@@ -31937,6 +32725,10 @@ ${result}`;
31937
32725
  });
31938
32726
  }
31939
32727
 
32728
+ // ../plugin/src/features/magic-context/store-generation-rebase.ts
32729
+ var rebasesInFlight = new WeakMap;
32730
+ var repairCheckedSessions = new WeakMap;
32731
+
31940
32732
  // ../plugin/src/hooks/magic-context/historian-state-file.ts
31941
32733
  import { mkdirSync as mkdirSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync7 } from "node:fs";
31942
32734
  function cleanupHistorianStateFile(path) {
@@ -32000,6 +32792,7 @@ async function sendStatusNotification(_client, sessionId, text, params) {
32000
32792
  var inconsistentProducerWindows = new Set;
32001
32793
  var HISTORIAN_ALERT_COOLDOWN_MS2 = 60 * 1000;
32002
32794
  var lastHistorianAlertBySession2 = new Map;
32795
+ var STORED_COMPARTMENT_FAILURE_BACKOFF_MS = 30 * 60 * 1000;
32003
32796
 
32004
32797
  // ../plugin/src/hooks/magic-context/compartment-runner-recomp.ts
32005
32798
  function insertRecompCompartmentRows(db, sessionId, compartments, now) {
@@ -32138,7 +32931,7 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
32138
32931
  clearInjectionCache(sessionId);
32139
32932
  }
32140
32933
  promoted.facts;
32141
- if (deps.memoryEnabled !== false) {
32934
+ {
32142
32935
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
32143
32936
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
32144
32937
  const liveCompartments = getCompartments(db, sessionId);
@@ -32355,7 +33148,7 @@ Another process acquired the compartment-state lease before recomp could publish
32355
33148
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd, compartmentTagKeys);
32356
33149
  }
32357
33150
  deps.onCompartmentStatePublished?.(sessionId);
32358
- if (deps.memoryEnabled !== false) {
33151
+ {
32359
33152
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
32360
33153
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
32361
33154
  const liveCompartments = getCompartments(db, sessionId);
@@ -32517,7 +33310,7 @@ Could not acquire the compartment-state lease for this session.`;
32517
33310
  clearInjectionCache(sessionId);
32518
33311
  }
32519
33312
  deps.onCompartmentStatePublished?.(sessionId);
32520
- if (deps.memoryEnabled !== false) {
33313
+ {
32521
33314
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
32522
33315
  const liveCompartments = getCompartments(db, sessionId);
32523
33316
  const chunksToEmbed = liveCompartments.map((c) => ({
@@ -33544,6 +34337,11 @@ function compressionText(summary) {
33544
34337
  return "Waiting for enough conversation history";
33545
34338
  }
33546
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
+ }
33547
34345
  function reclaimableText(summary) {
33548
34346
  const count = Math.max(0, Math.floor(summary.reclaimable.toolOutputCount));
33549
34347
  if (count === 0)
@@ -33585,6 +34383,12 @@ function renderUserStatusSummary(summary, style) {
33585
34383
  ],
33586
34384
  ["Search indexing", embeddingText(summary)]
33587
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
+ }
33588
34392
  if (summary.historianRefusal) {
33589
34393
  values.push([
33590
34394
  "Historian refusal",
@@ -34063,7 +34867,7 @@ function buildStatusView(source, options) {
34063
34867
  // package.json
34064
34868
  var package_default = {
34065
34869
  name: "@cortexkit/pi-magic-context",
34066
- version: "0.43.1",
34870
+ version: "0.44.0",
34067
34871
  type: "module",
34068
34872
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
34069
34873
  main: "dist/index.js",
@@ -34090,6 +34894,7 @@ var package_default = {
34090
34894
  "dist",
34091
34895
  "README.md"
34092
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.",
34093
34898
  scripts: {
34094
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",
34095
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",
@@ -34104,7 +34909,7 @@ var package_default = {
34104
34909
  prepublishOnly: "bun run build"
34105
34910
  },
34106
34911
  dependencies: {
34107
- "@cortexkit/subc-client": "0.4.1",
34912
+ "@cortexkit/subc-client": "0.11.1",
34108
34913
  "@jitl/quickjs-singlefile-cjs-release-asyncify": "0.32.0",
34109
34914
  "ai-tokenizer": "^1.0.6",
34110
34915
  "comment-json": "^5.0.0",
@@ -34126,6 +34931,7 @@ var package_default = {
34126
34931
  "@types/better-sqlite3": "^7.6.13",
34127
34932
  "@types/bun": "^1.3.10",
34128
34933
  "@types/node": "^22.20.0",
34934
+ "pi-agent-core-087": "npm:@earendil-works/pi-agent-core@0.87.1",
34129
34935
  "pi-ai-086": "npm:@earendil-works/pi-ai@0.86.0",
34130
34936
  "pi-coding-agent-086": "npm:@earendil-works/pi-coding-agent@0.86.0",
34131
34937
  "pi-coding-agent-087": "npm:@earendil-works/pi-coding-agent@0.87.1",
@@ -34666,11 +35472,14 @@ No active Pi session is available.`,
34666
35472
  }
34667
35473
  try {
34668
35474
  if (ctx.hasUI) {
35475
+ if (currentDeps.modelChainWarning)
35476
+ ctx.ui.notify(currentDeps.modelChainWarning, "error");
34669
35477
  await showStatusDialog(pi, ctx, currentDeps);
34670
35478
  return;
34671
35479
  }
34672
35480
  const statusDetail = buildPiStatusDetail(pi, ctx, currentDeps, sessionId);
34673
- const statusText = formatPiStatusSummary(statusDetail);
35481
+ const statusText = `${formatPiStatusSummary(statusDetail)}${currentDeps.modelChainWarning ? `
35482
+ WARNING: ${currentDeps.modelChainWarning}` : ""}`;
34674
35483
  const details = buildStatusDetails(currentDeps, statusDetail);
34675
35484
  sendStatus({
34676
35485
  title: "/ctx-status",
@@ -35457,7 +36266,7 @@ function ensureConfigLocationsMigrated(dir) {
35457
36266
  });
35458
36267
  }
35459
36268
  function logPiConfigLoad(args) {
35460
- const key = resolve3(args.dir);
36269
+ const key = resolve4(args.dir);
35461
36270
  if (args.dedupe && loggedPiConfigDirs.has(key))
35462
36271
  return;
35463
36272
  if (args.dedupe) {
@@ -35518,6 +36327,13 @@ function logPiUsageBoundOnce(sessionId, reading, absoluteWall, reason) {
35518
36327
  piUsageBoundLogSeen.add(key);
35519
36328
  info(`message_end: session=${sessionId} bounded ${reason} ${reading} at trusted absolute wall ${absoluteWall}; pressure_proof_not_capacity`);
35520
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
+ }
35521
36337
  function resolvePiPressureContextLimit(args) {
35522
36338
  let detectedContextLimit;
35523
36339
  try {
@@ -35549,24 +36365,32 @@ async function persistPiPressureFromMessageEnd(args) {
35549
36365
  });
35550
36366
  const trustedAbsoluteWall = reportedGeometry && hasTrustedAbsoluteWall(reportedGeometry) ? reportedGeometry.derivation.absoluteWall : undefined;
35551
36367
  const unboundedPressure = computePiPressure(usage, args.piContextWindow);
35552
- const rawPressure = computePiPressure(usage, args.piContextWindow, trustedAbsoluteWall);
35553
- if (unboundedPressure && trustedAbsoluteWall !== undefined && unboundedPressure.inputTokens > trustedAbsoluteWall) {
35554
- logPiUsageBoundOnce(args.sessionId, unboundedPressure.inputTokens, trustedAbsoluteWall, "current reading");
35555
- }
35556
36368
  const msg = args.message && typeof args.message === "object" ? args.message : undefined;
35557
36369
  const messageHadOverflowError = typeof msg?.errorMessage === "string" && detectOverflow(msg.errorMessage).isOverflow;
35558
- const requestSucceeded = !messageHadOverflowError && !(unboundedPressure && trustedAbsoluteWall !== undefined && unboundedPressure.inputTokens > trustedAbsoluteWall);
35559
- 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) {
35560
36381
  const rawOverflow = getOverflowState(args.db, args.sessionId);
35561
36382
  const detectedLimitMatchesModel = rawOverflow.detectedContextLimitModelKey === null || modelKey !== undefined && rawOverflow.detectedContextLimitModelKey === modelKey;
35562
- if (rawOverflow.detectedContextLimit > 0 && detectedLimitMatchesModel && rawPressure.inputTokens > rawOverflow.detectedContextLimit) {
36383
+ if (rawOverflow.detectedContextLimit > 0 && detectedLimitMatchesModel && unboundedPressure.inputTokens > rawOverflow.detectedContextLimit) {
35563
36384
  clearDetectedContextLimit(args.db, args.sessionId);
35564
- 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`);
35565
36386
  }
35566
36387
  }
35567
36388
  const meta = getOrCreateSessionMeta(args.db, args.sessionId);
35568
36389
  let observedSafeInputTokens = meta.observedSafeInputTokens ?? 0;
35569
- const updates = { lastResponseTime: Date.now() };
36390
+ const updates = {};
36391
+ if (unboundedPressure !== null) {
36392
+ updates.lastResponseTime = Date.now();
36393
+ }
35570
36394
  if (trustedAbsoluteWall !== undefined && observedSafeInputTokens > trustedAbsoluteWall) {
35571
36395
  logPiUsageBoundOnce(args.sessionId, observedSafeInputTokens, trustedAbsoluteWall, "persisted floor");
35572
36396
  observedSafeInputTokens = 0;
@@ -35587,8 +36411,10 @@ async function persistPiPressureFromMessageEnd(args) {
35587
36411
  provenInputTokens: observedSafeInputTokens
35588
36412
  });
35589
36413
  const reportedContextLimit = reportedGeometry?.usableSoft ?? 0;
35590
- const pressure = computePiPressure(usage, effectiveContextLimit, trustedAbsoluteWall);
36414
+ const pressure = computePiPressure(usage, effectiveContextLimit, requestAccepted ? undefined : trustedAbsoluteWall);
36415
+ let cacheAlert;
35591
36416
  if (pressure) {
36417
+ notePiUsageReadingUsed(args.sessionId);
35592
36418
  const provenSafeInputTokens = requestSucceeded ? Math.max(observedSafeInputTokens, pressure.inputTokens) : observedSafeInputTokens;
35593
36419
  const contextLimit = requestSucceeded ? resolvePiPressureContextLimit({
35594
36420
  db: args.db,
@@ -35602,7 +36428,7 @@ async function persistPiPressureFromMessageEnd(args) {
35602
36428
  if (requestSucceeded && reportedContextLimit > 0 && reportedContextLimit < provenSafeInputTokens && !meta.cacheAlertSent) {
35603
36429
  updates.cacheAlertSent = true;
35604
36430
  const modelLabel = activeModel.provider && activeModel.id ? `${activeModel.provider}/${activeModel.id}` : "the active model";
35605
- 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.`;
35606
36432
  }
35607
36433
  updates.lastContextPercentage = percentage;
35608
36434
  updates.lastInputTokens = pressure.inputTokens;
@@ -35611,13 +36437,21 @@ async function persistPiPressureFromMessageEnd(args) {
35611
36437
  updates.observedSafeInputTokens = provenSafeInputTokens;
35612
36438
  }
35613
36439
  } else if (usage === null && typeof args.piTokens === "number" && (trustedAbsoluteWall === undefined || args.piTokens <= trustedAbsoluteWall)) {
35614
- updates.lastInputTokens = args.piTokens;
35615
- if (effectiveContextLimit > 0) {
35616
- updates.lastContextPercentage = args.piTokens / effectiveContextLimit * 100;
35617
- 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
+ }
35618
36449
  }
35619
36450
  }
35620
36451
  updateSessionMeta(args.db, args.sessionId, updates);
36452
+ if (cacheAlert !== undefined) {
36453
+ await args.notifyIssue?.(cacheAlert);
36454
+ }
35621
36455
  }
35622
36456
  var PLUGIN_VERSION = (() => {
35623
36457
  try {
@@ -35628,17 +36462,27 @@ var PLUGIN_VERSION = (() => {
35628
36462
  }
35629
36463
  })();
35630
36464
  setHarness(PI_HARNESS_KIND);
35631
- function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND) {
36465
+ function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND, modelRegistry) {
35632
36466
  const historian = config.historian;
35633
36467
  if (historian?.disable === true)
35634
36468
  return;
35635
36469
  const resolved = resolveHistorianModel(config, harness);
35636
- 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;
35637
36481
  if (!model)
35638
36482
  return;
35639
36483
  const historianContextLimit = resolveHistorianContextLimit(model);
35640
36484
  const historianChunkTokens = deriveHistorianChunkTokens(historianContextLimit);
35641
- const fallbackModels = resolved.fallbacks;
36485
+ const fallbackModels = validated?.fallbackModels ?? resolved.fallbacks;
35642
36486
  return {
35643
36487
  runner: new PiSubagentRunner,
35644
36488
  model,
@@ -35649,7 +36493,7 @@ function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND) {
35649
36493
  temperature: historian?.temperature,
35650
36494
  maxOutputTokens: historian?.maxTokens,
35651
36495
  twoPass: historian?.two_pass === true,
35652
- thinkingLevel: resolved.primary?.qualifier,
36496
+ thinkingLevel: primary?.qualifier,
35653
36497
  executeThresholdPercentage: config.execute_threshold_percentage,
35654
36498
  executeThresholdTokens: config.execute_threshold_tokens,
35655
36499
  commitClusterTrigger: config.commit_cluster_trigger,
@@ -35713,30 +36557,7 @@ async function src_default(pi) {
35713
36557
  return null;
35714
36558
  }
35715
36559
  };
35716
- const unavailableReason = () => {
35717
- const migration = getMigrationOnOpenRefusal();
35718
- const blockingProcesses = migration?.blockingProcesses ?? migration?.serverPids.map((pid) => ({ kind: "process", pid })) ?? [];
35719
- const fence = getSchemaFenceRejection();
35720
- if (migration && blockingProcesses.length > 0) {
35721
- return {
35722
- kind: "migration_guard",
35723
- persistedVersion: migration.persistedVersion,
35724
- supportedVersion: migration.supportedVersion,
35725
- blockingProcesses
35726
- };
35727
- }
35728
- if (fence) {
35729
- return {
35730
- kind: "schema_fence",
35731
- persistedVersion: fence.persistedVersion,
35732
- supportedVersion: fence.supportedVersion
35733
- };
35734
- }
35735
- return {
35736
- kind: "storage_failure",
35737
- 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)`
35738
- };
35739
- };
36560
+ const unavailableReason = () => describeStorageUnavailability(openFailureCause ?? `storage unavailable at ${dbPath} (cache schema newer than this binary, or open failed)`);
35740
36561
  const bootResult = await bootPiRuntimeWithDeadline({
35741
36562
  deadlineMs: PI_BOOT_DEADLINE_MS,
35742
36563
  openStorage,
@@ -35922,15 +36743,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
35922
36743
  maybeAutoEmbedPiSession({
35923
36744
  db: database,
35924
36745
  projectDir: dir,
35925
- projectIdentity: identity,
35926
- memoryEnabled: cfg.memory.enabled
35927
- }, sessionId, dir, identity, (text) => {
35928
- sendCtxStatusMessage(pi, {
35929
- title: "/ctx-embed",
35930
- text,
35931
- level: "info"
35932
- });
35933
- });
36746
+ projectIdentity: identity
36747
+ }, sessionId, dir, identity);
35934
36748
  }
35935
36749
  });
35936
36750
  function buildProjectDeps(dir, identity, cfg, loadMetadata) {
@@ -35984,10 +36798,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
35984
36798
  function resolveCurrentProjectDeps(ctx) {
35985
36799
  return resolveProjectDepsForDir(ctx.cwd);
35986
36800
  }
36801
+ let activeModelRegistry;
35987
36802
  function resolveContextOptionsForProject(dir) {
35988
36803
  const project = resolveProjectDepsForDir(dir);
35989
36804
  const sampled = historianRunConfig(project.config, liveReaderFor(dir, project.config).poll().effective);
35990
- const historian = resolveHistorianFromConfig(sampled);
36805
+ const historian = resolveHistorianFromConfig(sampled, PI_HARNESS_KIND, activeModelRegistry);
35991
36806
  return { ...project.contextOptions, historian };
35992
36807
  }
35993
36808
  const bootProjectDeps = buildProjectDeps(projectDir, projectIdentity, config, {
@@ -35996,7 +36811,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
35996
36811
  hasDeprecatedProtectedTags
35997
36812
  });
35998
36813
  projectDepsByDir.set(projectDir, bootProjectDeps);
35999
- function syncDreamerProjectRegistration(current) {
36814
+ function syncDreamerProjectRegistration(current, modelRegistry) {
36000
36815
  if (sessionShuttingDown)
36001
36816
  return;
36002
36817
  seenDreamerProjectIdentities.add(current.projectIdentity);
@@ -36013,6 +36828,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36013
36828
  projectIdentity: current.projectIdentity,
36014
36829
  registrationOwner: dreamerRegistrationOwner,
36015
36830
  config: current.dreamerConfig,
36831
+ modelRegistry,
36016
36832
  sampleDreamRun: () => {
36017
36833
  const fresh = liveReaderFor(current.projectDir, current.config).poll().effective;
36018
36834
  const sampled = dreamerRunConfig(current.config, fresh);
@@ -36054,6 +36870,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36054
36870
  pi.on("session_start", async (event, ctx) => {
36055
36871
  projectDepsByDir.delete(ctx.cwd);
36056
36872
  const current = resolveCurrentProjectDeps(ctx);
36873
+ activeModelRegistry = ctx.modelRegistry;
36874
+ syncDreamerProjectRegistration(current, ctx.modelRegistry);
36057
36875
  syncCtxMemoryToolEnabled(pi, current.config.memory.enabled);
36058
36876
  const failuresToShow = claimConfigParseFailuresOnce("pi", current.configParseFailures);
36059
36877
  if (ctx.hasUI && failuresToShow.length > 0) {
@@ -36132,6 +36950,16 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36132
36950
  runnable: current.dreamerEnabled,
36133
36951
  scheduleSummary: summarizeDreamSchedule(current.config.dreamer)
36134
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
+ })(),
36135
36963
  activeProfile: current.config.profile,
36136
36964
  cacheTtlConfig: current.config.cache_ttl,
36137
36965
  cacheTtlConfigured: current.cacheTtlConfigured,
@@ -36161,7 +36989,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36161
36989
  resolveRuntimeDeps: (ctx) => {
36162
36990
  const current = resolveCurrentProjectDeps(ctx);
36163
36991
  const fresh = historianRunConfig(current.config, liveReaderFor(current.projectDir, current.config).poll().effective);
36164
- const historian = resolveHistorianFromConfig(fresh) ?? current.historianConfig;
36992
+ const historian = resolveHistorianFromConfig(fresh, PI_HARNESS_KIND, activeModelRegistry);
36165
36993
  return {
36166
36994
  db,
36167
36995
  runner: recompRunner,
@@ -36196,7 +37024,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36196
37024
  resolveRuntimeDeps: (ctx) => {
36197
37025
  const current = resolveCurrentProjectDeps(ctx);
36198
37026
  const fresh = historianRunConfig(current.config, liveReaderFor(current.projectDir, current.config).poll().effective);
36199
- const historian = resolveHistorianFromConfig(fresh) ?? current.historianConfig;
37027
+ const historian = resolveHistorianFromConfig(fresh, PI_HARNESS_KIND, activeModelRegistry);
36200
37028
  return {
36201
37029
  db,
36202
37030
  runner: wrapupRunner,
@@ -36230,7 +37058,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36230
37058
  dreamerEnabled: bootProjectDeps.dreamerEnabled,
36231
37059
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
36232
37060
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity),
36233
- ensureRegistered: (ctx) => syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx)),
37061
+ ensureRegistered: (ctx) => syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry),
36234
37062
  registrationOwner: dreamerRegistrationOwner
36235
37063
  });
36236
37064
  info("registered /ctx-dream");
@@ -36238,8 +37066,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36238
37066
  db,
36239
37067
  projectDir,
36240
37068
  projectIdentity,
36241
- memoryEnabled: bootProjectDeps.config.memory.enabled,
36242
- resolveMemoryEnabled: (ctx) => resolveCurrentProjectDeps(ctx).config.memory.enabled,
36243
37069
  resolveProject: (ctx) => {
36244
37070
  const current = resolveCurrentProjectDeps(ctx);
36245
37071
  return {
@@ -36283,7 +37109,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36283
37109
  };
36284
37110
  const effectiveConfig = effectiveProjectDeps.config;
36285
37111
  try {
36286
- syncDreamerProjectRegistration(effectiveProjectDeps);
37112
+ activeModelRegistry = ctx.modelRegistry;
37113
+ syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
36287
37114
  } catch (err) {
36288
37115
  warn("before_agent_start: dreamer registration sync threw:", err);
36289
37116
  }
@@ -36483,6 +37310,14 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36483
37310
  piContextWindowSource: "catalog",
36484
37311
  piModel: ctx.model,
36485
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
+ })(),
36486
37321
  notifyIssue: async (message) => {
36487
37322
  const uiNotify = ctx.ui?.notify;
36488
37323
  if (typeof uiNotify === "function") {