@cortexkit/pi-magic-context 0.43.2 → 0.44.1

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
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  setHarness,
3
3
  getHarness,
4
+ harnessOwnsOpenCodeStore,
4
5
  ensureCortexKitArtifactGitignore,
5
6
  getProjectMagicContextHistorianDir,
6
7
  getMagicContextStorageDir,
@@ -9,7 +10,7 @@ import {
9
10
  log,
10
11
  sessionLog,
11
12
  flushLogger
12
- } from "./index-bfd52y8p.js";
13
+ } from "./index-59dmj4zs.js";
13
14
  import {
14
15
  COMPACTION_ENABLED_PATH,
15
16
  isDreamerRunnable,
@@ -43,12 +44,10 @@ import {
43
44
  cortexKitProjectConfigBasePath,
44
45
  migrateMagicContextConfigLocations,
45
46
  withContentLanguageDirective,
46
- buildPrimaryLanguageDirective,
47
47
  parseCron,
48
48
  nextOccurrence,
49
49
  nextDueAtMs,
50
50
  resolveModelConfigValue,
51
- resolveModelConfigOrDefault,
52
51
  DEFAULT_HISTORIAN_TIMEOUT_MS,
53
52
  LIVE_RELOAD_CONFIG_PATHS,
54
53
  getProtectedTokensTierOverrides,
@@ -61,6 +60,7 @@ import {
61
60
  ProjectIdentityError,
62
61
  resolveProjectIdentityStrict,
63
62
  resolveProjectIdentity,
63
+ isUsableProjectIdentity,
64
64
  resolveProjectIdentityForSession,
65
65
  beginBootQuietPeriod,
66
66
  scheduleAfterBootQuiet,
@@ -74,40 +74,6 @@ import {
74
74
  indexSingleMessage,
75
75
  indexMessagesAfterOrdinal,
76
76
  sweepOrphanedOpenCodeMessageIndexes,
77
- recordSessionProjectIdentity,
78
- piHarnessKindFromExecutable,
79
- COMPARTMENT_LEASE_RENEWAL_MS,
80
- acquireCompartmentLease,
81
- renewCompartmentLease,
82
- releaseCompartmentLease,
83
- releaseCompartmentLeaseBestEffort,
84
- isCompartmentLeaseHeld,
85
- isNoContentCompartment,
86
- HAS_COMPARTMENT_CONTENT_SQL,
87
- persistCachedM0,
88
- clearCachedM0M1,
89
- getCompartments,
90
- getLastCompartmentEndMessage,
91
- getLastCompartmentEndMessageId,
92
- getCompartmentsByEndMessageId,
93
- appendCompartments,
94
- saveRecompStagingPass,
95
- getRecompStaging,
96
- clearRecompStaging,
97
- getRecompPartialRange,
98
- setRecompPartialRange,
99
- escapeXmlAttr,
100
- escapeXmlContent,
101
- getModuleNoteEvaluationBridge,
102
- getContextStoreUuid,
103
- drainMirrorPages,
104
- parseCompartmentOutput,
105
- resolveWorkspaceShareCategories,
106
- resolveWorkspaceIdentitySet,
107
- expandWorkspaceIdentitySetWithAliases,
108
- sourceNameForMemory,
109
- computeWorkspaceEpochFingerprint,
110
- readProjectDocsCanonical,
111
77
  MAX_EXECUTE_THRESHOLD,
112
78
  escalationBands,
113
79
  HYGIENE_PROVIDER_UNITS_VERSION,
@@ -116,6 +82,10 @@ import {
116
82
  computeProtectionWindow,
117
83
  readEpochFloorSnapshot,
118
84
  getProtectionWindowForSession,
85
+ persistCachedM0,
86
+ clearCachedM0M1,
87
+ readReplayDocument,
88
+ updateReplayDocument,
119
89
  isProviderOverflowFailClosedProven,
120
90
  describeProtectedTailDrainBudgetSkip,
121
91
  loadProtectedTailMeta,
@@ -172,8 +142,8 @@ import {
172
142
  clearDetectedContextLimit,
173
143
  getStrippedPlaceholderIds,
174
144
  applyStrippedPlaceholderDelta,
175
- NEWEST_REASONING_BEARING_ASSISTANT,
176
145
  THINKING_BINDING_RECOVERY_FROZEN_PREFIX,
146
+ THINKING_BINDING_STRIP_ORDER_END_MARKER,
177
147
  thinkingBindingRecoveryFrozenId,
178
148
  getThinkingBindingRecoveryTarget,
179
149
  armThinkingBindingRecovery,
@@ -192,6 +162,45 @@ import {
192
162
  setSessionWorkMetrics,
193
163
  getSessionWorkMetrics,
194
164
  resolveEpochFloorForPass,
165
+ getOrCreateSessionMeta,
166
+ updateSessionMeta,
167
+ advanceToolReclaimWatermark,
168
+ retryPendingSessionCleanups,
169
+ retryPendingRustSessionCleanupsForProject,
170
+ recordSessionProjectIdentity,
171
+ piHarnessKindFromExecutable,
172
+ COMPARTMENT_LEASE_RENEWAL_MS,
173
+ acquireCompartmentLease,
174
+ renewCompartmentLease,
175
+ releaseCompartmentLease,
176
+ releaseCompartmentLeaseBestEffort,
177
+ isCompartmentLeaseHeld,
178
+ isNoContentCompartment,
179
+ HAS_COMPARTMENT_CONTENT_SQL,
180
+ getCompartments,
181
+ getLastCompartmentEndMessage,
182
+ getLastCompartmentEndMessageId,
183
+ getCompartmentsByEndMessageId,
184
+ appendCompartments,
185
+ saveRecompStagingPass,
186
+ getRecompStaging,
187
+ clearRecompStaging,
188
+ getRecompPartialRange,
189
+ setRecompPartialRange,
190
+ escapeXmlAttr,
191
+ escapeXmlContent,
192
+ getModuleNoteEvaluationBridge,
193
+ getContextStoreUuid,
194
+ getAuthorityManagedMarker,
195
+ drainMirrorPages,
196
+ V2_MEMORY_CATEGORIES,
197
+ parseCompartmentOutput,
198
+ resolveWorkspaceShareCategories,
199
+ resolveWorkspaceIdentitySet,
200
+ expandWorkspaceIdentitySetWithAliases,
201
+ sourceNameForMemory,
202
+ computeWorkspaceEpochFingerprint,
203
+ readProjectDocsCanonical,
195
204
  BoundedSessionMap,
196
205
  registerLkgPersistence,
197
206
  signatureForFields,
@@ -229,11 +238,6 @@ import {
229
238
  getMemoryMutationsForRenderByProjects,
230
239
  getMaxMemoryMutationId,
231
240
  getMaxMemoryMutationIdForProjects,
232
- getOrCreateSessionMeta,
233
- updateSessionMeta,
234
- advanceToolReclaimWatermark,
235
- retryPendingSessionCleanups,
236
- retryPendingRustSessionCleanupsForProject,
237
241
  getNotes,
238
242
  getSessionNotes,
239
243
  getPendingSmartNotes,
@@ -372,6 +376,8 @@ import {
372
376
  ensureProjectRegisteredFromPiDirectory,
373
377
  resolvePiHarnessDetection,
374
378
  resolvePiHarnessKind,
379
+ MEMORY_MURAL_BLOCK,
380
+ buildMagicContextSection,
375
381
  resolveMuralWire,
376
382
  updateCompactionMarkerAfterPublication,
377
383
  COMPARTMENT_RENDER_EPOCH,
@@ -402,7 +408,7 @@ import {
402
408
  registerTodoStateLifecycle,
403
409
  syncCtxMemoryToolEnabled,
404
410
  registerMagicContextTools
405
- } from "./index-bkrh6jhp.js";
411
+ } from "./index-mwpk0wn9.js";
406
412
  import {
407
413
  CTX_REDUCE_KEEP,
408
414
  newestCtxReduceTagNumbers,
@@ -481,6 +487,7 @@ import {
481
487
  claimOpenCodeDbDiagnosticOnce,
482
488
  Database,
483
489
  closeQuietly,
490
+ RAW_SUMMARY_TEXT_MAX_CHARS,
484
491
  completedToolArcCrossesBoundary,
485
492
  estimateTrueRawMessageTokens,
486
493
  buildToolArcs,
@@ -497,12 +504,15 @@ import {
497
504
  localBudget,
498
505
  calibrationForModelKey,
499
506
  historyLocalBudget,
507
+ SKELETON_REAL_INPUT_MAX_BYTES,
508
+ toolInputStringBytes,
500
509
  setRawMessageProvider2,
501
510
  withRawMessageProvider2,
502
511
  cleanUserText2,
503
512
  withRawSessionMessageCache2,
504
513
  readRawSessionMessages2,
505
514
  getRawSessionMessageOrdinalCount2,
515
+ visitRawSessionMessages2,
506
516
  readRawSessionMessageRange2,
507
517
  primeTailRawMessageCache2,
508
518
  getCachedAbsoluteMessageCount2,
@@ -512,14 +522,14 @@ import {
512
522
  getRawSessionTagKeysThrough2,
513
523
  getLegacyProtectedTailStartOrdinal2,
514
524
  readSessionChunk2
515
- } from "./index-mea04n1j.js";
525
+ } from "./index-nm3c0sn8.js";
516
526
  import {
517
527
  pushNotification2
518
528
  } from "./index-b3eqj1g6.js";
519
- import"./index-5v60m046.js";
529
+ import"./index-5fw91enm.js";
520
530
  import {
521
531
  logSlowWriteTransaction
522
- } from "./index-17102c1m.js";
532
+ } from "./index-97e5fzy3.js";
523
533
  import {
524
534
  __toESM
525
535
  } from "./index-22jkk6wg.js";
@@ -527,7 +537,7 @@ import {
527
537
  // src/index.ts
528
538
  import { AsyncLocalStorage } from "node:async_hooks";
529
539
  import { createRequire as createRequire2 } from "node:module";
530
- import { join as join8, resolve as resolve3 } from "node:path";
540
+ import { join as join11, resolve as resolve4 } from "node:path";
531
541
 
532
542
  // ../plugin/src/config/live-snapshot.ts
533
543
  import { createHash } from "node:crypto";
@@ -1141,7 +1151,9 @@ var OVERFLOW_PATTERNS = [
1141
1151
  /prompt too long; exceeded (?:max )?context length/i,
1142
1152
  /too large for model with \d+ maximum context length/i,
1143
1153
  /model_context_window_exceeded/i,
1144
- /context size has been exceeded/i
1154
+ /context size has been exceeded/i,
1155
+ /prepared prompt exceeds engine max_context/i,
1156
+ /prompt exceeds (?:the )?.{0,32}\bmax_context\b/i
1145
1157
  ];
1146
1158
  var LIMIT_EXTRACTION_PATTERNS = [
1147
1159
  { pattern: /maximum prompt length is (\d+)/i, provenance: "prompt_only" },
@@ -1162,11 +1174,15 @@ var LIMIT_EXTRACTION_PATTERNS = [
1162
1174
  pattern: />\s*(\d+)\s*(?:tokens?\s*)?(?:maximum|max|limit)\b/i,
1163
1175
  provenance: "prompt_only"
1164
1176
  },
1177
+ { pattern: /prepared prompt exceeds engine max_context\s+(\d+)/i, provenance: "unknown" },
1178
+ { pattern: /prompt exceeds (?:the )?.{0,32}\bmax_context\s+(\d+)/i, provenance: "unknown" },
1165
1179
  { pattern: /max(?:imum)?.*context.*?(\d+)/i, provenance: "unknown" }
1166
1180
  ];
1167
1181
  var MIN_PLAUSIBLE_LIMIT = 1024;
1168
1182
  var MAX_PLAUSIBLE_LIMIT = 1e7;
1169
1183
  var THINKING_BINDING_MISMATCH_PATTERN = /bound to a different conversation/i;
1184
+ var THINKING_BINDING_FAILING_PATH_PATTERN = /^\s*(messages\.\d+\.content\.\d+):/;
1185
+ var THINKING_BINDING_FIRST_CHANGED_PATTERN = /first at `?(messages\.\d+\.content\.\d+)`?/;
1170
1186
  function extractErrorMessage(error) {
1171
1187
  if (!error)
1172
1188
  return "";
@@ -1195,29 +1211,6 @@ function extractErrorMessage(error) {
1195
1211
  }
1196
1212
  return String(error);
1197
1213
  }
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
1214
  function extractExplicitHttpStatus(error) {
1222
1215
  if (!error || typeof error !== "object")
1223
1216
  return;
@@ -1247,13 +1240,20 @@ function detectThinkingBindingMismatch(error) {
1247
1240
  if (explicitStatus !== undefined && explicitStatus !== 400 || !THINKING_BINDING_MISMATCH_PATTERN.test(message)) {
1248
1241
  return { isBindingMismatch: false };
1249
1242
  }
1250
- const messageId = extractThinkingBindingMessageId(error);
1243
+ const failingBlockPath = THINKING_BINDING_FAILING_PATH_PATTERN.exec(message)?.[1];
1244
+ const firstChangedPath = THINKING_BINDING_FIRST_CHANGED_PATTERN.exec(message)?.[1];
1251
1245
  return {
1252
1246
  isBindingMismatch: true,
1253
1247
  matchedPattern: "bound to a different conversation",
1254
- ...messageId ? { messageId } : {}
1248
+ ...failingBlockPath ? { failingBlockPath } : {},
1249
+ ...firstChangedPath ? { firstChangedPath } : {}
1255
1250
  };
1256
1251
  }
1252
+ function isPrefixBoundThinkingModel(providerID, modelID) {
1253
+ if (providerID?.toLowerCase() !== "anthropic" || !modelID)
1254
+ return false;
1255
+ return isFable51ThinkingBindingModel(providerID, modelID) || /(?:^|[-_.])opus[-_.]?5[-_.]5(?:$|[-_.])/i.test(modelID);
1256
+ }
1257
1257
  function isFable51ThinkingBindingModel(providerID, modelID) {
1258
1258
  if (providerID?.toLowerCase() !== "anthropic" || !modelID)
1259
1259
  return false;
@@ -1325,6 +1325,67 @@ function parseReportedLimit(message) {
1325
1325
  return;
1326
1326
  }
1327
1327
 
1328
+ // ../plugin/src/shared/model-cache-ttl.ts
1329
+ var MODEL_CACHE_LIFETIMES = [
1330
+ {
1331
+ source: "OpenAI GPT-5.6+ default",
1332
+ value: "30m",
1333
+ matches(model) {
1334
+ const version = /^gpt-(\d+)(?:\.(\d+))?(?:$|[^\d.])/.exec(model);
1335
+ if (!version)
1336
+ return false;
1337
+ const major = Number(version[1]);
1338
+ const minor = Number(version[2] ?? 0);
1339
+ return major > 5 || major === 5 && minor >= 6;
1340
+ }
1341
+ }
1342
+ ];
1343
+ function resolveModelCacheTtl(config, modelKey) {
1344
+ if (config && typeof config !== "string") {
1345
+ const match = modelKey && !modelKey.includes("/") && Object.hasOwn(config, modelKey) && modelKey !== "default" ? config[modelKey] : resolveModelConfigValue(config, modelKey)?.value;
1346
+ if (match !== undefined)
1347
+ return { value: match, source: "config", modelKey };
1348
+ }
1349
+ if (typeof config === "string" && config !== "5m")
1350
+ return { value: config, source: "config", modelKey };
1351
+ const model = canonicalModelIdentity(modelKey ?? "").toLowerCase().split("/").at(-1) ?? "";
1352
+ const known = MODEL_CACHE_LIFETIMES.find((entry) => entry.matches(model));
1353
+ if (known)
1354
+ return { value: known.value, source: known.source, modelKey };
1355
+ return {
1356
+ value: (typeof config === "object" ? config.default : config) ?? "5m",
1357
+ source: "default",
1358
+ modelKey
1359
+ };
1360
+ }
1361
+
1362
+ // ../plugin/src/features/magic-context/session-cache-ttl.ts
1363
+ function readSessionCacheTtl(db, sessionId) {
1364
+ const saved = readReplayDocument(db, sessionId).cacheTtlPolicy;
1365
+ return saved && typeof saved.value === "string" && saved.config !== undefined ? saved : undefined;
1366
+ }
1367
+ function resolveSessionCacheTtl(db, sessionId, config, modelKey) {
1368
+ const meta = getOrCreateSessionMeta(db, sessionId);
1369
+ let saved = readSessionCacheTtl(db, sessionId);
1370
+ if (!modelKey)
1371
+ return saved ?? resolveModelCacheTtl(config ?? meta.cacheTtl, undefined);
1372
+ if (!saved || saved.modelKey !== modelKey) {
1373
+ const frozenConfig = saved?.config ?? config ?? meta.cacheTtl;
1374
+ const resolved = resolveModelCacheTtl(frozenConfig, modelKey);
1375
+ const next = { ...resolved, config: frozenConfig };
1376
+ if (!updateReplayDocument(db, sessionId, (doc) => {
1377
+ doc.version = 2;
1378
+ doc.cacheTtlPolicy = next;
1379
+ return true;
1380
+ }))
1381
+ throw new Error("cannot persist session cache TTL policy");
1382
+ saved = next;
1383
+ }
1384
+ if (meta.cacheTtl !== saved.value)
1385
+ updateSessionMeta(db, sessionId, { cacheTtl: saved.value });
1386
+ return saved;
1387
+ }
1388
+
1328
1389
  // ../plugin/src/features/magic-context/session-project-backfill.ts
1329
1390
  import { randomUUID } from "node:crypto";
1330
1391
  import { existsSync } from "node:fs";
@@ -1586,6 +1647,31 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1586
1647
  return result;
1587
1648
  }
1588
1649
 
1650
+ // ../plugin/src/features/magic-context/storage-unavailable-reason.ts
1651
+ function describeStorageUnavailability(fallbackCause) {
1652
+ const migration = getMigrationOnOpenRefusal();
1653
+ const blockingProcesses = migration?.blockingProcesses ?? migration?.serverPids.map((pid) => ({ kind: "process", pid })) ?? [];
1654
+ if (migration && (blockingProcesses.length > 0 || migration.unreadableFile)) {
1655
+ return {
1656
+ kind: "migration_guard",
1657
+ persistedVersion: migration.persistedVersion,
1658
+ supportedVersion: migration.supportedVersion,
1659
+ blockingProcesses,
1660
+ ...migration.unreadableFile ? { unreadableFile: migration.unreadableFile } : {},
1661
+ ...migration.unreadableArm ? { unreadableArm: migration.unreadableArm } : {}
1662
+ };
1663
+ }
1664
+ const fence = getSchemaFenceRejection();
1665
+ if (fence) {
1666
+ return {
1667
+ kind: "schema_fence",
1668
+ persistedVersion: fence.persistedVersion,
1669
+ supportedVersion: fence.supportedVersion
1670
+ };
1671
+ }
1672
+ return { kind: "storage_failure", cause: fallbackCause };
1673
+ }
1674
+
1589
1675
  // ../plugin/src/features/magic-context/v22-deferred-backfill.ts
1590
1676
  import { createHash as createHash2 } from "node:crypto";
1591
1677
  import { realpathSync } from "node:fs";
@@ -1877,141 +1963,6 @@ function producerSourceLocalBudget(providerTokens, modelKey) {
1877
1963
  return ratio === 1 ? providerTokens : localBudget(providerTokens, ratio);
1878
1964
  }
1879
1965
 
1880
- // ../plugin/src/hooks/magic-context/event-resolvers.ts
1881
- var DEFAULT_CONTEXT_LIMIT = 200000;
1882
- function resolveCacheTtl(cacheTtl, modelKey) {
1883
- if (typeof cacheTtl === "string") {
1884
- return cacheTtl;
1885
- }
1886
- return resolveModelConfigOrDefault(cacheTtl, modelKey, cacheTtl.default ?? "5m");
1887
- }
1888
- var clampWarnSeen = new Set;
1889
- function isFinitePositive(v) {
1890
- return typeof v === "number" && Number.isFinite(v) && v > 0;
1891
- }
1892
- function* modelKeyLookupOrder(modelKey) {
1893
- const slash = modelKey.indexOf("/");
1894
- const providerRefs = slash >= 0 ? modelRefLookupOrder(modelKey) : [];
1895
- let modelId = slash >= 0 ? modelKey.slice(slash + 1) : modelKey;
1896
- while (modelId.length > 0) {
1897
- for (const providerRef of providerRefs) {
1898
- const providerSlash = providerRef.indexOf("/");
1899
- yield `${providerRef.slice(0, providerSlash)}/${modelId}`;
1900
- }
1901
- yield modelId;
1902
- const lastDash = modelId.lastIndexOf("-");
1903
- if (lastDash <= 0)
1904
- break;
1905
- modelId = modelId.slice(0, lastDash);
1906
- }
1907
- }
1908
- function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
1909
- if (options?.tokensConfig && isFinitePositive(options.contextLimit)) {
1910
- const contextLimit = options.contextLimit;
1911
- const tokenMatch = resolveTokensMatchWithKey(options.tokensConfig, modelKey);
1912
- if (tokenMatch && isFinitePositive(tokenMatch.value)) {
1913
- const cap = contextLimit * (MAX_EXECUTE_THRESHOLD / 100);
1914
- const effectiveTokens = Math.min(tokenMatch.value, cap);
1915
- if (effectiveTokens < tokenMatch.value) {
1916
- const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
1917
- if (!clampWarnSeen.has(dedupeKey)) {
1918
- clampWarnSeen.add(dedupeKey);
1919
- const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (${MAX_EXECUTE_THRESHOLD}% of ${contextLimit}) for ${modelKey ?? "default"}`;
1920
- if (options.sessionId) {
1921
- sessionLog(options.sessionId, `WARN: ${msg}`);
1922
- } else {
1923
- log(`[magic-context] WARN: ${msg}`);
1924
- }
1925
- }
1926
- }
1927
- const percentage = effectiveTokens / contextLimit * 100;
1928
- const detail = {
1929
- percentage: Math.min(percentage, MAX_EXECUTE_THRESHOLD),
1930
- mode: "tokens",
1931
- absoluteTokens: Math.floor(effectiveTokens),
1932
- matchedKey: tokenMatch.matchedKey
1933
- };
1934
- if (effectiveTokens < tokenMatch.value) {
1935
- detail.clamped = true;
1936
- detail.configuredValue = tokenMatch.value;
1937
- }
1938
- return detail;
1939
- }
1940
- }
1941
- let resolved;
1942
- let matchedKey;
1943
- if (typeof config === "number") {
1944
- resolved = config;
1945
- } else if (modelKey) {
1946
- let matched;
1947
- for (const candidate of modelKeyLookupOrder(modelKey)) {
1948
- if (typeof config[candidate] === "number") {
1949
- matched = config[candidate];
1950
- matchedKey = candidate;
1951
- break;
1952
- }
1953
- }
1954
- if (matched === undefined && typeof config.default === "number") {
1955
- resolved = config.default;
1956
- matchedKey = "default";
1957
- } else {
1958
- resolved = matched ?? fallback;
1959
- }
1960
- } else if (typeof config.default === "number") {
1961
- resolved = config.default;
1962
- matchedKey = "default";
1963
- } else {
1964
- resolved = fallback;
1965
- }
1966
- if (!Number.isFinite(resolved) || resolved < 0) {
1967
- resolved = fallback;
1968
- }
1969
- const cappedPercentage = Math.min(resolved, MAX_EXECUTE_THRESHOLD);
1970
- const percentageClamped = cappedPercentage < resolved;
1971
- if (percentageClamped) {
1972
- const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
1973
- if (!clampWarnSeen.has(dedupeKey)) {
1974
- clampWarnSeen.add(dedupeKey);
1975
- const msg = `execute_threshold clamped ${resolved}% → ${MAX_EXECUTE_THRESHOLD}% for ${modelKey ?? "default"} (capped against the output-reserved safe window; 10% remains for mid-turn growth before the absolute 95% wall)`;
1976
- if (options?.sessionId) {
1977
- sessionLog(options.sessionId, `WARN: ${msg}`);
1978
- } else {
1979
- log(`[magic-context] WARN: ${msg}`);
1980
- }
1981
- }
1982
- }
1983
- const detail = {
1984
- percentage: cappedPercentage,
1985
- mode: "percentage",
1986
- matchedKey
1987
- };
1988
- if (percentageClamped) {
1989
- detail.clamped = true;
1990
- detail.configuredValue = resolved;
1991
- }
1992
- return detail;
1993
- }
1994
- function resolveExecuteThreshold(config, modelKey, fallback, options) {
1995
- return resolveExecuteThresholdDetail(config, modelKey, fallback, options).percentage;
1996
- }
1997
- function resolveTokensMatchWithKey(tokensConfig, modelKey) {
1998
- if (!tokensConfig) {
1999
- return;
2000
- }
2001
- if (modelKey) {
2002
- for (const candidate of modelKeyLookupOrder(modelKey)) {
2003
- const value = tokensConfig[candidate];
2004
- if (typeof value === "number") {
2005
- return { value, matchedKey: candidate };
2006
- }
2007
- }
2008
- }
2009
- if (typeof tokensConfig.default === "number") {
2010
- return { value: tokensConfig.default, matchedKey: "default" };
2011
- }
2012
- return;
2013
- }
2014
-
2015
1966
  // ../plugin/src/hooks/magic-context/note-nudger.ts
2016
1967
  var NOTE_NUDGE_COOLDOWN_MS = 15 * 60 * 1000;
2017
1968
  var lastDeliveredAt = new Map;
@@ -2802,6 +2753,11 @@ var USER_FACING_FAILURES = {
2802
2753
  sentence: "Memory maintenance could not reach its model.",
2803
2754
  action: "Check the model connection, then run /ctx-dream again."
2804
2755
  },
2756
+ dream_step_limit: {
2757
+ code: "MC-D10",
2758
+ sentence: "Memory maintenance stopped at its hidden agent step limit.",
2759
+ action: "This task needs less work per run; changing the model connection will not help."
2760
+ },
2805
2761
  dream_empty_completion: {
2806
2762
  code: "MC-D03",
2807
2763
  sentence: "Memory maintenance received no usable response.",
@@ -2967,6 +2923,11 @@ var USER_FACING_FAILURES = {
2967
2923
  sentence: "Magic Context is temporarily unavailable.",
2968
2924
  action: "Retry in a moment."
2969
2925
  },
2926
+ store_ahead_of_binary: {
2927
+ code: "MC-C13",
2928
+ sentence: "Magic Context refused to start: its store (store.db) was migrated by a newer ck-mc build than the one running.",
2929
+ action: "Update ck-mc, or roll back by restoring ck-mc together with context.db and store.db from the same backup."
2930
+ },
2970
2931
  compaction_marker_missing: {
2971
2932
  code: "MC-C11",
2972
2933
  sentence: "The history boundary marker is missing from the OpenCode store, so requests carry the full session.",
@@ -2982,11 +2943,6 @@ var USER_FACING_FAILURES = {
2982
2943
  sentence: "Memory authority is inconsistent between the host and module.",
2983
2944
  action: "Run `ck doctor drain-authority` before changing Rust mode."
2984
2945
  },
2985
- rust_mode_unsupported: {
2986
- code: "MC-S06",
2987
- sentence: "Experimental Rust transform mode is not available on this OpenCode host.",
2988
- action: 'Magic Context is running its TypeScript mode instead; set `transform_mode` to "ts" to stop requesting Rust.'
2989
- },
2990
2946
  dreamer_task_failing: {
2991
2947
  code: "MC-S05",
2992
2948
  sentence: "A background maintenance task keeps failing on its schedule.",
@@ -3018,6 +2974,7 @@ function renderCapabilityRefusal(capability) {
3018
2974
  var DREAM_FAILURE_KEYS = {
3019
2975
  provider_timeout: "dream_provider_timeout",
3020
2976
  provider_error: "dream_provider_error",
2977
+ step_limit: "dream_step_limit",
3021
2978
  empty_completion: "dream_empty_completion",
3022
2979
  no_models: "dream_no_models",
3023
2980
  child_aborted: "dream_child_aborted",
@@ -3047,8 +3004,14 @@ function renderEmbeddingFailure(failureClass, style = "markdown") {
3047
3004
  return renderUserFacingFailure(EMBEDDING_FAILURE_KEYS[failureClass], style);
3048
3005
  }
3049
3006
 
3007
+ // src/dreamer/index.ts
3008
+ import { homedir as homedir2 } from "node:os";
3009
+ import { parse, resolve as resolve3 } from "node:path";
3010
+
3050
3011
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
3051
3012
  function openOpenCodeDb() {
3013
+ if (!harnessOwnsOpenCodeStore())
3014
+ return null;
3052
3015
  const resolution = resolveOpenCodeDbPath();
3053
3016
  const dbPath = resolution.path;
3054
3017
  if (!openCodeDbPathExists(resolution)) {
@@ -3204,6 +3167,37 @@ class HiddenCompletionRefusal extends Error {
3204
3167
  this.name = "HiddenCompletionRefusal";
3205
3168
  }
3206
3169
  }
3170
+ // ../plugin/src/v2/hooks/hidden-child.ts
3171
+ var HIDDEN_HISTORIAN_AGENT = "historian";
3172
+ var HIDDEN_DREAMER_AGENT = "dreamer-classifier";
3173
+ var HIDDEN_CURATE_AGENT = DREAMER_AGENT;
3174
+ var READ_TOOLS = ["read", "grep", "glob"];
3175
+ var AGENT_TOOLS = {
3176
+ [HIDDEN_HISTORIAN_AGENT]: [],
3177
+ [HIDDEN_DREAMER_AGENT]: [],
3178
+ [HIDDEN_CURATE_AGENT]: ["ctx_memory", "ctx_memory_list"],
3179
+ [DREAMER_MEMORY_MAPPER_AGENT]: READ_TOOLS,
3180
+ [DREAMER_PRIMER_INVESTIGATOR_AGENT]: [...READ_TOOLS, "ctx_search"],
3181
+ [DREAMER_RETROSPECTIVE_AGENT]: ["ctx_search"]
3182
+ };
3183
+ var AGENT_STEPS = {
3184
+ [HIDDEN_CURATE_AGENT]: 150,
3185
+ [DREAMER_MEMORY_MAPPER_AGENT]: 60,
3186
+ [DREAMER_DOCS_AGENT]: 60,
3187
+ [DREAMER_PRIMER_INVESTIGATOR_AGENT]: 40,
3188
+ [DREAMER_RETROSPECTIVE_AGENT]: 40
3189
+ };
3190
+ class HiddenAgentStepLimit extends Error {
3191
+ agent;
3192
+ cap;
3193
+ constructor(agent, cap) {
3194
+ super(`Hidden agent ${agent} exceeded its ${cap}-step limit`);
3195
+ this.name = "HiddenAgentStepLimit";
3196
+ this.agent = agent;
3197
+ this.cap = cap;
3198
+ }
3199
+ }
3200
+
3207
3201
  // ../plugin/src/shared/assistant-message-extractor.ts
3208
3202
  function asSessionMessage(value) {
3209
3203
  if (!isRecord(value))
@@ -3302,6 +3296,14 @@ function copyPromptArgs(args, body) {
3302
3296
  return { ...args, body: { ...body } };
3303
3297
  }
3304
3298
  var promptFailureDetails = new WeakMap;
3299
+ function isHostTimeoutError(error) {
3300
+ return error !== null && typeof error === "object" && error.name === "TimeoutError";
3301
+ }
3302
+ function isPromptTimeoutError(error) {
3303
+ if (isHostTimeoutError(error))
3304
+ return true;
3305
+ return error instanceof Error && /^prompt timed out after \d+ms$/.test(error.message);
3306
+ }
3305
3307
  function getPromptFailureDetail(error) {
3306
3308
  return error !== null && typeof error === "object" ? promptFailureDetails.get(error) ?? null : null;
3307
3309
  }
@@ -3358,9 +3360,13 @@ function parseModelSuggestion(error) {
3358
3360
  suggestion: suggestionMatch[1].trim()
3359
3361
  };
3360
3362
  }
3363
+ function externalAbortMessage(signal) {
3364
+ const reason = signal.reason;
3365
+ return `prompt aborted by external signal${reason instanceof Error && /^lease_(?:lost|expired):/.test(reason.message) ? `: ${reason.message}` : ""}`;
3366
+ }
3361
3367
  async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3362
3368
  if (signal?.aborted) {
3363
- throw new Error("prompt aborted by external signal");
3369
+ throw new Error(externalAbortMessage(signal));
3364
3370
  }
3365
3371
  const controller = new AbortController;
3366
3372
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -3380,7 +3386,7 @@ async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3380
3386
  if (!transport || transport.childSessionId) {
3381
3387
  await abortChildRun(client, transport?.childSessionId ?? args.path.id);
3382
3388
  }
3383
- throw new Error("prompt aborted by external signal");
3389
+ throw new Error(externalAbortMessage(signal));
3384
3390
  }
3385
3391
  if (controller.signal.aborted) {
3386
3392
  if (!transport || transport.childSessionId) {
@@ -3388,6 +3394,11 @@ async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3388
3394
  }
3389
3395
  throw new Error(`prompt timed out after ${timeoutMs}ms`);
3390
3396
  }
3397
+ if (isHostTimeoutError(error)) {
3398
+ if (!transport || transport.childSessionId) {
3399
+ await abortChildRun(client, transport?.childSessionId ?? args.path.id);
3400
+ }
3401
+ }
3391
3402
  throw error;
3392
3403
  } finally {
3393
3404
  clearTimeout(timeout);
@@ -3397,7 +3408,7 @@ async function promptWithTimeout(client, args, timeoutMs, signal, transport) {
3397
3408
  if (!transport || transport.childSessionId) {
3398
3409
  await abortChildRun(client, transport?.childSessionId ?? args.path.id);
3399
3410
  }
3400
- throw new Error(signal?.aborted ? "prompt aborted by external signal" : `prompt timed out after ${timeoutMs}ms`);
3411
+ throw new Error(signal?.aborted ? externalAbortMessage(signal) : `prompt timed out after ${timeoutMs}ms`);
3401
3412
  }
3402
3413
  }
3403
3414
  async function abortChildRun(client, sessionId) {
@@ -3413,7 +3424,7 @@ async function abortChildRun(client, sessionId) {
3413
3424
  }
3414
3425
  }
3415
3426
  function isNonRetryable(error, externalSignal) {
3416
- if (externalSignal?.aborted)
3427
+ if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit)
3417
3428
  return true;
3418
3429
  if (error instanceof Error) {
3419
3430
  if (error.name === "AbortError")
@@ -3423,6 +3434,8 @@ function isNonRetryable(error, externalSignal) {
3423
3434
  if (/^prompt timed out after \d+ms$/.test(error.message))
3424
3435
  return true;
3425
3436
  }
3437
+ if (isHostTimeoutError(error))
3438
+ return true;
3426
3439
  if (detectOverflow(error).isOverflow)
3427
3440
  return true;
3428
3441
  return false;
@@ -3435,11 +3448,14 @@ function shortErr(error) {
3435
3448
  }
3436
3449
  function classifyPromptFailure(error, phase, externalSignal) {
3437
3450
  const message = extractMessage(error);
3451
+ if (error instanceof HiddenAgentStepLimit)
3452
+ return "step_limit";
3438
3453
  if (externalSignal?.aborted || message === "prompt aborted by external signal") {
3439
3454
  return "child_aborted";
3440
3455
  }
3441
- if (/^prompt timed out after \d+ms$/.test(message))
3456
+ if (/^prompt timed out after \d+ms$/.test(message) || isHostTimeoutError(error)) {
3442
3457
  return "provider_timeout";
3458
+ }
3443
3459
  if (phase === "validation") {
3444
3460
  if (/returned no (?:assistant )?output|no assistant output/i.test(message)) {
3445
3461
  return "empty_completion";
@@ -3656,7 +3672,10 @@ async function promptSyncWithValidatedOutputRetry(client, args, options) {
3656
3672
  }
3657
3673
  }
3658
3674
  log(`[${callContext}] all models exhausted; tried: ${failedAttempts.map((failure) => failure.attempt.label).join(", ")}; original error: ${shortErr(firstError)}; last error: ${shortErr(lastError)}`);
3659
- throwWithPromptFailure(firstError ?? lastError ?? new Error("All fallback models failed validation"), failedAttempts, args, timeoutMs, options.transport);
3675
+ const cause = lastError ?? firstError;
3676
+ const exhausted = cause instanceof Error ? cause : new Error(String(cause ?? "All fallback models failed validation"));
3677
+ exhausted.message = `All models exhausted (${failedAttempts.map((failure) => failure.attempt.label).join(", ")}): ${exhausted.message}`;
3678
+ throwWithPromptFailure(exhausted, failedAttempts, args, timeoutMs, options.transport);
3660
3679
  }
3661
3680
  // ../plugin/src/shared/normalize-sdk-response.ts
3662
3681
  function normalizeSDKResponse(response, fallback, options) {
@@ -3681,6 +3700,149 @@ function normalizeSDKResponse(response, fallback, options) {
3681
3700
  }
3682
3701
  return fallback;
3683
3702
  }
3703
+ // ../plugin/src/shared/prompt-async-transport.ts
3704
+ var DEFAULT_POLL_INTERVAL_MS = 1000;
3705
+ var DEFAULT_START_GRACE_MS = 30000;
3706
+ var MESSAGE_WINDOW = 20;
3707
+ function sessionApi(client) {
3708
+ return client?.session;
3709
+ }
3710
+ function supportsPromptAsync(client) {
3711
+ const session = sessionApi(client);
3712
+ return typeof session?.promptAsync === "function" && typeof session?.status === "function" && typeof session?.messages === "function";
3713
+ }
3714
+ function createPromptAsyncTransport(client, childSessionId, options = {}) {
3715
+ if (!supportsPromptAsync(client))
3716
+ return;
3717
+ return Object.assign((request) => promptAsyncAndWaitForIdle(client, request, options), { childSessionId });
3718
+ }
3719
+ function infoOf(message) {
3720
+ const info = message?.info;
3721
+ return info && typeof info === "object" ? info : {};
3722
+ }
3723
+ function messageId(message) {
3724
+ const id = infoOf(message).id;
3725
+ return typeof id === "string" ? id : null;
3726
+ }
3727
+ function isSettledAssistant(message) {
3728
+ const info = infoOf(message);
3729
+ if (info.role !== "assistant")
3730
+ return false;
3731
+ return info.time?.completed != null || info.error != null || info.finish != null;
3732
+ }
3733
+ function unwrapData(response) {
3734
+ if (response && typeof response === "object" && "data" in response) {
3735
+ return response.data;
3736
+ }
3737
+ return response;
3738
+ }
3739
+ function describeRejection(error) {
3740
+ if (error instanceof Error)
3741
+ return error.message;
3742
+ try {
3743
+ return JSON.stringify(error);
3744
+ } catch {
3745
+ return String(error);
3746
+ }
3747
+ }
3748
+ function abortError(signal) {
3749
+ return signal.reason ?? new Error("prompt_async wait aborted");
3750
+ }
3751
+ function sleep(ms, signal) {
3752
+ return new Promise((resolve, reject) => {
3753
+ if (signal?.aborted) {
3754
+ reject(abortError(signal));
3755
+ return;
3756
+ }
3757
+ const onAbort = () => {
3758
+ clearTimeout(timer);
3759
+ reject(abortError(signal));
3760
+ };
3761
+ const timer = setTimeout(() => {
3762
+ signal?.removeEventListener("abort", onAbort);
3763
+ resolve();
3764
+ }, ms);
3765
+ signal?.addEventListener("abort", onAbort, { once: true });
3766
+ });
3767
+ }
3768
+ async function readMessages(session, sessionId, directory, signal) {
3769
+ const response = await session.messages?.({
3770
+ path: { id: sessionId },
3771
+ query: { ...directory ? { directory } : {}, limit: MESSAGE_WINDOW },
3772
+ ...signal ? { signal } : {}
3773
+ });
3774
+ const data = unwrapData(response);
3775
+ return Array.isArray(data) ? data : [];
3776
+ }
3777
+ async function readStatus(session, sessionId, directory, signal) {
3778
+ try {
3779
+ const response = await session.status?.({
3780
+ ...directory ? { query: { directory } } : {},
3781
+ ...signal ? { signal } : {}
3782
+ });
3783
+ if (response && typeof response === "object" && "error" in response) {
3784
+ if (response.error)
3785
+ return null;
3786
+ }
3787
+ const data = unwrapData(response);
3788
+ if (!data || typeof data !== "object")
3789
+ return null;
3790
+ const entry = data[sessionId];
3791
+ const type = entry?.type;
3792
+ return typeof type === "string" ? type : "idle";
3793
+ } catch (error) {
3794
+ if (signal?.aborted)
3795
+ throw error;
3796
+ return null;
3797
+ }
3798
+ }
3799
+ async function promptAsyncAndWaitForIdle(client, request, options = {}) {
3800
+ const session = sessionApi(client);
3801
+ if (!session || !supportsPromptAsync(client)) {
3802
+ throw new Error("prompt_async transport is unavailable on this client");
3803
+ }
3804
+ const sessionId = request.path.id;
3805
+ const directory = request.query?.directory;
3806
+ const dir = typeof directory === "string" ? directory : undefined;
3807
+ const signal = request.signal;
3808
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
3809
+ const startGraceMs = options.startGraceMs ?? DEFAULT_START_GRACE_MS;
3810
+ const baseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((id) => id !== null));
3811
+ const sent = await session.promptAsync?.(request);
3812
+ if (sent && typeof sent === "object" && "error" in sent) {
3813
+ const rejection = sent.error;
3814
+ if (rejection)
3815
+ throw new Error(`prompt_async was rejected: ${describeRejection(rejection)}`);
3816
+ }
3817
+ const sentAt = Date.now();
3818
+ let sawBusy = false;
3819
+ for (;; ) {
3820
+ await sleep(pollIntervalMs, signal);
3821
+ const status = await readStatus(session, sessionId, dir, signal);
3822
+ if (status === "busy" || status === "retry") {
3823
+ sawBusy = true;
3824
+ continue;
3825
+ }
3826
+ if (status === null)
3827
+ continue;
3828
+ const messages = await readMessages(session, sessionId, dir, signal);
3829
+ const last = messages.at(-1);
3830
+ const lastId = messageId(last);
3831
+ if (last && lastId !== null && !baseline.has(lastId) && isSettledAssistant(last))
3832
+ return;
3833
+ const fresh = messages.some((message) => {
3834
+ const id = messageId(message);
3835
+ return id !== null && !baseline.has(id);
3836
+ });
3837
+ if (sawBusy && fresh)
3838
+ return;
3839
+ if (Date.now() - sentAt >= startGraceMs) {
3840
+ if (fresh)
3841
+ return;
3842
+ throw new Error(`prompt_async did not start a run in child session ${sessionId} within ${startGraceMs}ms`);
3843
+ }
3844
+ }
3845
+ }
3684
3846
  // ../plugin/src/shared/summarize-child-stderr.ts
3685
3847
  var MAX_CHILD_STDERR_SUMMARY_LENGTH = 500;
3686
3848
  var CHILD_ERROR_LINE_PATTERN = /^(?:[A-Za-z]*Error|SqliteError|Error)\b[^\n]*/;
@@ -3779,6 +3941,15 @@ var HISTORIAN_AGENT = "historian";
3779
3941
  var HISTORIAN_RECOMP_AGENT = "historian-recomp";
3780
3942
  var HISTORIAN_EDITOR_AGENT = "historian-editor";
3781
3943
 
3944
+ // ../plugin/src/config/live-child-output-cap.ts
3945
+ var historianCaps = new BoundedSessionMap(4096);
3946
+ function rememberHistorianOutputCap(sessionId, cap) {
3947
+ historianCaps.set(sessionId, { value: cap });
3948
+ }
3949
+ function forgetHistorianOutputCap(sessionId) {
3950
+ historianCaps.delete(sessionId);
3951
+ }
3952
+
3782
3953
  // ../plugin/src/features/magic-context/subagent-token-capture.ts
3783
3954
  function asNumber(value) {
3784
3955
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -3820,6 +3991,9 @@ function failedInvocationStatus(error) {
3820
3991
  const message = describeError(error).brief;
3821
3992
  if (/timed out after \d+ms|prompt timed out/i.test(message))
3822
3993
  return "timed_out";
3994
+ if (error !== null && typeof error === "object" && error.name === "TimeoutError") {
3995
+ return "timed_out";
3996
+ }
3823
3997
  if (/length-capped|no (?:assistant )?output|empty (?:assistant )?(?:output|text)/i.test(message)) {
3824
3998
  return "empty";
3825
3999
  }
@@ -4881,7 +5055,7 @@ function shouldDiscardLastHistorianCompartment(compartments, chunk) {
4881
5055
  function validateHistorianOutput(text, sessionId, chunk, _priorCompartments, sequenceOffset) {
4882
5056
  const parseStarted = startHistorianPublishStage(sessionId, "parse", `response_chars=${text.length}`);
4883
5057
  const parsed = parseCompartmentOutput(text);
4884
- finishHistorianPublishStage(sessionId, "parse", parseStarted, "completed", `compartments=${parsed.compartments.length}`);
5058
+ finishHistorianPublishStage(sessionId, "parse", parseStarted, "completed", `compartments=${parsed.compartments.length} dropped_fact_blocks=${parsed.droppedFactBlocks} dropped_facts=${parsed.droppedFacts}`);
4885
5059
  const validationStarted = startHistorianPublishStage(sessionId, "validate");
4886
5060
  const finish = (result) => {
4887
5061
  finishHistorianPublishStage(sessionId, "validate", validationStarted, result.ok ? "completed" : "failed", result.ok ? `compartments=${result.compartments.length}` : `reason=${JSON.stringify(result.error)}`);
@@ -4920,6 +5094,7 @@ function validateHistorianOutput(text, sessionId, chunk, _priorCompartments, seq
4920
5094
  ok: true,
4921
5095
  compartments: mapped.compartments,
4922
5096
  facts: parsed.facts,
5097
+ ...parsed.droppedFactBlocks > 0 ? { droppedFactBlocks: parsed.droppedFactBlocks, droppedFacts: parsed.droppedFacts } : {},
4923
5098
  userObservations: parsed.userObservations.length > 0 ? parsed.userObservations : undefined,
4924
5099
  primerCandidates: parsed.primerCandidates.length > 0 ? parsed.primerCandidates.slice(0, 1) : undefined,
4925
5100
  events: parsed.events.length > 0 ? parsed.events : undefined
@@ -5172,6 +5347,7 @@ function resolveHiddenCompletionExecutor(executor, client, db, directory, entryP
5172
5347
  return createV1HiddenCompletionExecutor(client, db, directory);
5173
5348
  }
5174
5349
  function createV1HiddenCompletionExecutor(client, db, directory) {
5350
+ const asyncChildren = new Set;
5175
5351
  return {
5176
5352
  capabilities: { tools: true, harness: "opencode" },
5177
5353
  async open(run) {
@@ -5188,11 +5364,19 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5188
5364
  preferResponseOnMissingData: true
5189
5365
  });
5190
5366
  const id = typeof created?.id === "string" ? created.id : "";
5367
+ if (id && run.kind === "dreamer-task")
5368
+ asyncChildren.add(id);
5369
+ if (id && run.kind !== "dreamer-task")
5370
+ rememberHistorianOutputCap(id, run.maxOutputTokens);
5191
5371
  return { id, childSessionId: id || undefined };
5192
5372
  },
5193
- async attempt(_handle, request) {
5373
+ async attempt(handle, request) {
5194
5374
  if (!client)
5195
5375
  throw new Error("Hidden completion client is unavailable");
5376
+ if (asyncChildren.has(handle.id) && supportsPromptAsync(client)) {
5377
+ await promptAsyncAndWaitForIdle(client, request);
5378
+ return;
5379
+ }
5196
5380
  await client.session.prompt(request);
5197
5381
  },
5198
5382
  async collect(handle, limit) {
@@ -5220,6 +5404,10 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5220
5404
  };
5221
5405
  },
5222
5406
  async close(handle, settlement) {
5407
+ if (handle?.id) {
5408
+ asyncChildren.delete(handle.id);
5409
+ forgetHistorianOutputCap(handle.id);
5410
+ }
5223
5411
  if (!client)
5224
5412
  return;
5225
5413
  await teardownChildSession({
@@ -5231,6 +5419,9 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5231
5419
  }
5232
5420
  };
5233
5421
  }
5422
+ function historianReasoningBudgetDiagnostic(outputTokens) {
5423
+ 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`;
5424
+ }
5234
5425
  async function runValidatedHistorianPass(args) {
5235
5426
  const firstRun = await runHistorianPrompt({
5236
5427
  ...args,
@@ -5457,14 +5648,14 @@ async function runHistorianPrompt(args) {
5457
5648
  }
5458
5649
  const backoffMs = getHistorianRetryBackoffMs(retryIndex);
5459
5650
  sessionLog(parentSessionId, `historian retry ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES} after ${backoffMs}ms: ${errorMsg}`);
5460
- await sleep(backoffMs);
5651
+ await sleep2(backoffMs);
5461
5652
  }
5462
5653
  }
5463
5654
  completion = await executor.collect(handle, 50);
5464
5655
  const lengthCapped = completion.lengthCapped;
5465
5656
  const textResult = completion.text;
5466
5657
  const reasoningResult = textResult ? null : completion.reasoning;
5467
- const emptyError = !textResult && reasoningResult && lengthCapped ? `historian output length-capped at ${completion.usage.output} tokens (all reasoning, no text) — set historian.maxTokens or route historian.model to a low-reasoning lane/variant` : !textResult && !reasoningResult ? "Historian returned no assistant output." : !textResult ? "Historian returned reasoning but no assistant text." : lengthCapped ? "Historian returned length-capped output." : null;
5658
+ const emptyError = !textResult && reasoningResult && lengthCapped ? historianReasoningBudgetDiagnostic(completion.usage.output) : !textResult && !reasoningResult ? "Historian returned no assistant output." : !textResult ? "Historian returned reasoning but no assistant text." : lengthCapped ? "Historian returned length-capped output." : null;
5468
5659
  const invocationId = recordInvocation({
5469
5660
  status: emptyError ? "empty" : "completed",
5470
5661
  messages: completion.messages,
@@ -5594,7 +5785,7 @@ function isTransientHistorianPromptError(message) {
5594
5785
  "overloaded"
5595
5786
  ].some((token) => normalized.includes(token));
5596
5787
  }
5597
- function sleep(ms) {
5788
+ function sleep2(ms) {
5598
5789
  return new Promise((resolve) => {
5599
5790
  setTimeout(resolve, ms);
5600
5791
  });
@@ -5791,13 +5982,25 @@ function runLeaseGuardedWrite(db, holderId, leaseKey, fn, slowWriteThresholdMs)
5791
5982
  return fn();
5792
5983
  }, `lease-guarded-write:${leaseKey}`, slowWriteThresholdMs);
5793
5984
  }
5794
- var LEASE_HEARTBEAT_INTERVAL_MS = 60 * 1000;
5985
+ var LEASE_HEARTBEAT_INTERVAL_MS = 40 * 1000;
5986
+ function reacquireOwnedLease(db, holderId, leaseKey, generation) {
5987
+ const keys = rowKeys(leaseKey);
5988
+ return runImmediate(db, () => {
5989
+ if (getLeaseHolder(db, leaseKey) !== holderId || getLeaseGeneration(db, leaseKey) !== generation) {
5990
+ return false;
5991
+ }
5992
+ const now = Date.now();
5993
+ setDreamState(db, keys.heartbeat, String(now));
5994
+ setDreamState(db, keys.expiry, String(now + LEASE_DURATION_MS));
5995
+ return true;
5996
+ });
5997
+ }
5795
5998
  function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisition = LEASE_HEARTBEAT_INTERVAL_MS) {
5796
5999
  const intervalMs = typeof intervalOrAcquisition === "number" ? intervalOrAcquisition : LEASE_HEARTBEAT_INTERVAL_MS;
5797
6000
  const acquisition = typeof intervalOrAcquisition === "number" ? undefined : intervalOrAcquisition;
5798
6001
  let lost = false;
5799
- let expectedGeneration = acquisition?.generation ?? getLeaseGeneration(db, leaseKey);
5800
- let lastConfirmedAt = acquisition?.acquiredAt ?? Date.now();
6002
+ const expectedGeneration = acquisition?.generation ?? getLeaseGeneration(db, leaseKey);
6003
+ let renewalFailures = 0;
5801
6004
  const declareLost = (reason) => {
5802
6005
  if (lost)
5803
6006
  return;
@@ -5809,32 +6012,20 @@ function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisiti
5809
6012
  return;
5810
6013
  try {
5811
6014
  if (renewLease(db, holderId, leaseKey, expectedGeneration === null ? undefined : expectedGeneration)) {
5812
- lastConfirmedAt = Date.now();
5813
- return;
5814
- }
5815
- if (expectedGeneration !== null && getLeaseGeneration(db, leaseKey) !== expectedGeneration) {
5816
- declareLost("lease generation changed — another holder acquired it");
5817
- return;
5818
- }
5819
- if (Date.now() - lastConfirmedAt > LEASE_DURATION_MS) {
5820
- declareLost("lease lapsed past TTL — another holder may have run");
5821
6015
  return;
5822
6016
  }
5823
- const reacquired = acquireLeaseWithAcquisition(db, holderId, leaseKey);
5824
- if (reacquired) {
5825
- if (expectedGeneration !== null && reacquired.generation !== expectedGeneration) {
5826
- declareLost("lease generation changed during reacquisition");
5827
- return;
5828
- }
5829
- expectedGeneration = reacquired.generation;
5830
- lastConfirmedAt = Date.now();
6017
+ if (expectedGeneration !== null && reacquireOwnedLease(db, holderId, leaseKey, expectedGeneration))
5831
6018
  return;
6019
+ const holder = getLeaseHolder(db, leaseKey);
6020
+ if (holder !== null && holder !== holderId) {
6021
+ declareLost(`lease_lost: taken by ${holder}`);
6022
+ } else if (getLeaseGeneration(db, leaseKey) !== expectedGeneration) {
6023
+ declareLost("lease_lost: generation changed");
6024
+ } else {
6025
+ declareLost("lease_expired: reacquire failed (holder missing)");
5832
6026
  }
5833
- declareLost("lease acquired by another holder");
5834
6027
  } catch {
5835
- if (Date.now() - lastConfirmedAt > LEASE_DURATION_MS) {
5836
- declareLost("lease renewal unconfirmed past TTL");
5837
- }
6028
+ renewalFailures += 1;
5838
6029
  }
5839
6030
  };
5840
6031
  beat();
@@ -5846,6 +6037,9 @@ function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisiti
5846
6037
  },
5847
6038
  get lost() {
5848
6039
  return lost;
6040
+ },
6041
+ get renewalFailures() {
6042
+ return renewalFailures;
5849
6043
  }
5850
6044
  };
5851
6045
  }
@@ -5923,6 +6117,14 @@ function assertParsedManifestNonEmpty(parsedCount, expectedCount, text, expected
5923
6117
  }
5924
6118
 
5925
6119
  // ../plugin/src/features/magic-context/dreamer/module-apply.ts
6120
+ var loggedNotOwnerProjects = new Set;
6121
+ function logDreamerNotOwnerOnce(projectIdentity) {
6122
+ if (loggedNotOwnerProjects.has(projectIdentity))
6123
+ return;
6124
+ loggedNotOwnerProjects.add(projectIdentity);
6125
+ log(`[dreamer] ${projectIdentity}: module-managed project; skipping TS writes without an owner route`);
6126
+ }
6127
+
5926
6128
  class DreamerModuleBusyError extends Error {
5927
6129
  state;
5928
6130
  transient = true;
@@ -6547,7 +6749,7 @@ async function runHiddenSingleShotPrompt(args) {
6547
6749
  fetchOutput: () => args.executor.collect(handle, 50),
6548
6750
  validateOutput: (completion) => {
6549
6751
  if (completion.lengthCapped) {
6550
- throw new Error(`${args.callContext} returned length-capped output`);
6752
+ throw new Error(completion.reasoning && !completion.text ? `${args.callContext} ran out of output budget while reasoning (length-capped at ${completion.usage.output} tokens, no text) — set dreamer.maxTokens or use a low-reasoning model` : `${args.callContext} returned length-capped output`);
6551
6753
  }
6552
6754
  const text = completion.text;
6553
6755
  if (!text && !args.allowEmpty)
@@ -7120,12 +7322,45 @@ function validateClassifyManifest(text, expectedIds) {
7120
7322
  return parsed;
7121
7323
  }
7122
7324
 
7325
+ // ../plugin/src/features/magic-context/dreamer/failed-invocation-evidence.ts
7326
+ var FAILED_CHILD_READ_TIMEOUT_MS = 5000;
7327
+ var FAILED_CHILD_MESSAGE_LIMIT = 200;
7328
+ async function readFailedChildMessages(client, sessionId, directory) {
7329
+ if (!client || !sessionId)
7330
+ return;
7331
+ let timer;
7332
+ try {
7333
+ const response = await Promise.race([
7334
+ client.session.messages({
7335
+ path: { id: sessionId },
7336
+ query: { directory, limit: FAILED_CHILD_MESSAGE_LIMIT }
7337
+ }),
7338
+ new Promise((resolve) => {
7339
+ timer = setTimeout(() => resolve(null), FAILED_CHILD_READ_TIMEOUT_MS);
7340
+ })
7341
+ ]);
7342
+ if (response === null)
7343
+ return;
7344
+ const messages = normalizeSDKResponse(response, [], {
7345
+ preferResponseOnMissingData: true
7346
+ });
7347
+ return Array.isArray(messages) ? messages : undefined;
7348
+ } catch (error) {
7349
+ log(`[dreamer] failed-child evidence read failed for ${sessionId}: ${String(error)}`);
7350
+ return;
7351
+ } finally {
7352
+ if (timer)
7353
+ clearTimeout(timer);
7354
+ }
7355
+ }
7356
+
7123
7357
  // ../plugin/src/features/magic-context/dreamer/classify.ts
7124
7358
  var MIN_POOL_TO_CLASSIFY = 10;
7125
7359
  var FULL_POOL_CEILING = 100;
7126
7360
  var STAGE3_ANCHOR_COUNT = 30;
7127
7361
  var CLASSIFY_CHUNK_SIZE = 100;
7128
7362
  var CLASSIFY_MODULE_RUN_TIMEOUT_MS = 660000;
7363
+ var HOST_COMPLETION_REQUIRED = "host_completion_required";
7129
7364
 
7130
7365
  class ClassifyModuleFailureError extends Error {
7131
7366
  transient = true;
@@ -7209,13 +7444,12 @@ async function runClassify(args) {
7209
7444
  let stage;
7210
7445
  let toClassify;
7211
7446
  let anchors = [];
7447
+ const unclassifiedIds = new Set(getUnclassifiedMemoryIds(args.db, active.map((candidate) => candidate.contextMemory.id)));
7448
+ toClassify = active.filter((candidate) => unclassifiedIds.has(candidate.contextMemory.id));
7212
7449
  if (active.length <= FULL_POOL_CEILING) {
7213
7450
  stage = 2;
7214
- toClassify = active;
7215
7451
  } else {
7216
7452
  stage = 3;
7217
- const unclassifiedIds = new Set(getUnclassifiedMemoryIds(args.db, active.map((candidate) => candidate.contextMemory.id)));
7218
- toClassify = active.filter((candidate) => unclassifiedIds.has(candidate.contextMemory.id));
7219
7453
  const classified = active.filter((candidate) => !unclassifiedIds.has(candidate.contextMemory.id));
7220
7454
  anchors = stratifiedAnchors(classified, STAGE3_ANCHOR_COUNT);
7221
7455
  }
@@ -7236,7 +7470,7 @@ async function runClassify(args) {
7236
7470
  chunks.push(toClassify.slice(i, i + CLASSIFY_CHUNK_SIZE));
7237
7471
  }
7238
7472
  const abortController = new AbortController;
7239
- const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => abortController.abort(), args.leaseAcquisition);
7473
+ const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
7240
7474
  try {
7241
7475
  for (let i = 0;i < chunks.length; i += 1) {
7242
7476
  const remainingMs = Math.max(0, args.deadline - Date.now());
@@ -7271,68 +7505,89 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
7271
7505
  memories: chunk.map(toPromptMemory2),
7272
7506
  anchors
7273
7507
  });
7508
+ const runOnHostCarrier = async (system) => {
7509
+ const executor = resolveHiddenCompletionExecutor(args.hiddenCompletionExecutor, args.client, args.db, args.sessionDirectory, "classify-memories");
7510
+ closeExecutor = executor;
7511
+ handle = await executor.open({
7512
+ parentSessionId: args.parentSessionId,
7513
+ agent: DREAMER_CLASSIFIER_AGENT,
7514
+ kind: "dreamer-task",
7515
+ system,
7516
+ model: args.model,
7517
+ configuredModels: [
7518
+ ...args.model ? [args.model] : [],
7519
+ ...args.fallbackModels ?? []
7520
+ ],
7521
+ timeoutMs: sliceMs,
7522
+ title: "magic-context-dream-classify",
7523
+ directory: args.sessionDirectory,
7524
+ metadata: { task: "classify-memories" }
7525
+ });
7526
+ agentSessionId = handle.id || null;
7527
+ if (!agentSessionId)
7528
+ throw new Error("Could not create classify session.");
7529
+ const opened = handle;
7530
+ const run = await promptSyncWithValidatedOutputRetry(args.client, {
7531
+ path: { id: agentSessionId },
7532
+ query: { directory: args.sessionDirectory },
7533
+ body: {
7534
+ agent: DREAMER_CLASSIFIER_AGENT,
7535
+ system,
7536
+ ...modelBodyField(args.model),
7537
+ parts: [{ type: "text", text: prompt, synthetic: true }]
7538
+ }
7539
+ }, {
7540
+ transport: Object.assign((request) => executor.attempt(opened, request), { childSessionId: opened.childSessionId }),
7541
+ timeoutMs: sliceMs,
7542
+ signal,
7543
+ fallbackModels: args.fallbackModels,
7544
+ callContext: "dreamer:classify-memories",
7545
+ fetchOutput: () => executor.collect(opened, 50),
7546
+ validateOutput: (completion) => {
7547
+ const messages = completion.messages ?? [];
7548
+ if (completion.lengthCapped) {
7549
+ 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");
7550
+ }
7551
+ const text = completion.text;
7552
+ if (!text)
7553
+ throw new Error("classify returned no output");
7554
+ try {
7555
+ validateClassifyManifest(text, new Set(chunk.map((candidate) => candidate.id)));
7556
+ } catch (error) {
7557
+ const providerFailure = providerOutputFailureFromInvalidManifest(messages, text);
7558
+ if (providerFailure)
7559
+ throw providerFailure;
7560
+ throw error;
7561
+ }
7562
+ return text;
7563
+ }
7564
+ });
7565
+ promptSettled = true;
7566
+ return run;
7567
+ };
7274
7568
  if (moduleRoute) {
7275
- const { accounting, ...run } = await runClassifyThroughModule(args, chunk, anchors, signal);
7569
+ const { accounting, ...run } = await runClassifyThroughModule(args, chunk, anchors, signal, async (system) => {
7570
+ const run = await runOnHostCarrier(system);
7571
+ const output = run.output;
7572
+ return {
7573
+ text: run.validated,
7574
+ model: output.providerId && output.modelId ? `${output.providerId}/${output.modelId}` : run.attempt.label,
7575
+ length_capped: output.lengthCapped === true,
7576
+ usage: {
7577
+ input: output.usage?.input ?? 0,
7578
+ output: output.usage?.output ?? 0,
7579
+ cache_read: output.usage?.cacheRead ?? 0,
7580
+ cache_write: output.usage?.cacheWrite ?? 0
7581
+ }
7582
+ };
7583
+ });
7276
7584
  recordInvocation(args, startedAt, {
7277
7585
  status: "completed",
7278
7586
  moduleAccounting: accounting
7279
7587
  });
7280
7588
  return run;
7281
7589
  }
7282
- const executor = resolveHiddenCompletionExecutor(args.hiddenCompletionExecutor, args.client, args.db, args.sessionDirectory, "classify-memories");
7283
- closeExecutor = executor;
7284
- handle = await executor.open({
7285
- parentSessionId: args.parentSessionId,
7286
- agent: DREAMER_CLASSIFIER_AGENT,
7287
- kind: "dreamer-task",
7288
- system: withContentLanguageDirective(CLASSIFY_SYSTEM_PROMPT, args.language),
7289
- model: args.model,
7290
- configuredModels: [...args.model ? [args.model] : [], ...args.fallbackModels ?? []],
7291
- timeoutMs: sliceMs,
7292
- title: "magic-context-dream-classify",
7293
- directory: args.sessionDirectory,
7294
- metadata: { task: "classify-memories" }
7295
- });
7296
- agentSessionId = handle.id || null;
7297
- if (!agentSessionId)
7298
- throw new Error("Could not create classify session.");
7299
- const opened = handle;
7300
- const run = await promptSyncWithValidatedOutputRetry(args.client, {
7301
- path: { id: agentSessionId },
7302
- query: { directory: args.sessionDirectory },
7303
- body: {
7304
- agent: DREAMER_CLASSIFIER_AGENT,
7305
- system: withContentLanguageDirective(CLASSIFY_SYSTEM_PROMPT, args.language),
7306
- ...modelBodyField(args.model),
7307
- parts: [{ type: "text", text: prompt, synthetic: true }]
7308
- }
7309
- }, {
7310
- transport: Object.assign((request) => executor.attempt(opened, request), { childSessionId: opened.childSessionId }),
7311
- timeoutMs: sliceMs,
7312
- signal,
7313
- fallbackModels: args.fallbackModels,
7314
- callContext: "dreamer:classify-memories",
7315
- fetchOutput: () => executor.collect(opened, 50),
7316
- validateOutput: (completion) => {
7317
- const messages = completion.messages ?? [];
7318
- if (completion.lengthCapped) {
7319
- throw new Error("classify returned length-capped output");
7320
- }
7321
- const text = completion.text;
7322
- if (!text)
7323
- throw new Error("classify returned no output");
7324
- try {
7325
- validateClassifyManifest(text, new Set(chunk.map((candidate) => candidate.id)));
7326
- } catch (error) {
7327
- const providerFailure = providerOutputFailureFromInvalidManifest(messages, text);
7328
- if (providerFailure)
7329
- throw providerFailure;
7330
- throw error;
7331
- }
7332
- return text;
7333
- }
7334
- });
7335
- promptSettled = true;
7590
+ const run = await runOnHostCarrier(withContentLanguageDirective(CLASSIFY_SYSTEM_PROMPT, args.language));
7336
7591
  recordInvocation(args, startedAt, {
7337
7592
  status: "completed",
7338
7593
  messages: run.output.messages,
@@ -7345,7 +7600,8 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
7345
7600
  log(`[dreamer] classify chunk failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
7346
7601
  recordInvocation(args, startedAt, {
7347
7602
  status: failedInvocationStatus(failure),
7348
- error: failure
7603
+ error: failure,
7604
+ messages: args.hiddenCompletionExecutor ? undefined : await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
7349
7605
  });
7350
7606
  if (moduleRoute || failure instanceof HiddenCompletionRefusal || signal.aborted || failure instanceof DreamerProviderOutputFailureError || getPromptFailureDetail(failure)?.failureClass !== "parse_failed" && getPromptFailureDetail(failure) !== null)
7351
7607
  throw failure;
@@ -7359,7 +7615,7 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
7359
7615
  });
7360
7616
  }
7361
7617
  }
7362
- async function runClassifyThroughModule(args, chunk, anchors, signal) {
7618
+ async function runClassifyThroughModule(args, chunk, anchors, signal, runOnHost) {
7363
7619
  const prompt = buildClassifyPrompt({
7364
7620
  projectPath: args.projectIdentity,
7365
7621
  memories: chunk.map(toPromptMemory2),
@@ -7367,7 +7623,8 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
7367
7623
  });
7368
7624
  const modelChain = [args.model, ...args.fallbackModels ?? []].map(toModelEntry).filter((entry) => entry !== undefined).map((entry) => entry.model);
7369
7625
  const resolvedModelChain = [...new Set(modelChain)];
7370
- const response = await args.moduleClient?.call({
7626
+ const commandId = `classify:${args.moduleCommandId ?? Date.now()}:${createHash5("sha256").update(chunk.map((candidate) => candidate.id).join(",")).digest("hex").slice(0, 24)}`;
7627
+ const runTask = (hostCompletion) => args.moduleClient?.call({
7371
7628
  sessionId: args.moduleSessionId,
7372
7629
  projectRoot: args.moduleProjectRoot,
7373
7630
  method: "dreamer.run_task",
@@ -7376,7 +7633,7 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
7376
7633
  v: 1,
7377
7634
  session_id: args.moduleSessionId,
7378
7635
  task: "classify",
7379
- command_id: `classify:${args.moduleCommandId ?? Date.now()}:${createHash5("sha256").update(chunk.map((candidate) => candidate.id).join(",")).digest("hex").slice(0, 24)}`,
7636
+ command_id: commandId,
7380
7637
  authority_generation: args.moduleAuthorityGeneration,
7381
7638
  model_chain: resolvedModelChain,
7382
7639
  payload: {
@@ -7385,12 +7642,20 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
7385
7642
  memory_id: candidate.id,
7386
7643
  content_hash: candidate.normalizedHash
7387
7644
  }))
7388
- }
7645
+ },
7646
+ ...hostCompletion ? { host_completion: hostCompletion } : {}
7389
7647
  },
7390
7648
  signal,
7391
- timeoutMs: CLASSIFY_MODULE_RUN_TIMEOUT_MS
7649
+ timeoutMs: Math.max(1, resolvedModelChain.length) * CLASSIFY_MODULE_RUN_TIMEOUT_MS + 30000
7392
7650
  });
7393
- const result = response?.result ?? response;
7651
+ const unwrap = (response) => response?.result ?? response;
7652
+ let result = unwrap(await runTask());
7653
+ const asked = result;
7654
+ if (asked?.code === HOST_COMPLETION_REQUIRED) {
7655
+ if (typeof asked.system_prompt !== "string")
7656
+ throw new Error("module asked for a host classify completion without a system prompt");
7657
+ result = unwrap(await runTask(await runOnHost(asked.system_prompt)));
7658
+ }
7394
7659
  if (!result || typeof result !== "object")
7395
7660
  throw new Error("module returned invalid classify result");
7396
7661
  const manifestText = result.manifest_text;
@@ -8152,7 +8417,7 @@ async function guardedReadFile(projectRoot, repoRelativePath, signal, fileLimitB
8152
8417
  const body = guardedReadFileBody(projectRoot, repoRelativePath, signal, fileLimitBytes);
8153
8418
  let onAbort;
8154
8419
  const abort = new Promise((_, reject) => {
8155
- onAbort = () => reject(abortError(signal));
8420
+ onAbort = () => reject(abortError2(signal));
8156
8421
  if (signal.aborted)
8157
8422
  onAbort();
8158
8423
  else
@@ -8222,9 +8487,9 @@ async function closeLateOpenOnAbort(openPromise, signal) {
8222
8487
  return handle;
8223
8488
  if (handle)
8224
8489
  handle.close().catch(() => {});
8225
- throw abortError(signal);
8490
+ throw abortError2(signal);
8226
8491
  }
8227
- function abortError(signal) {
8492
+ function abortError2(signal) {
8228
8493
  return signal.reason instanceof SmartNoteNetworkError ? signal.reason : new SmartNoteNetworkError("SMART_NOTE_NETWORK: aborted");
8229
8494
  }
8230
8495
  function isPathInside(root, target) {
@@ -9043,9 +9308,9 @@ async function evaluateSmartNotes(args) {
9043
9308
  throw new Error(`Dream lease lost during smart-notes ${phase}`);
9044
9309
  }
9045
9310
  };
9046
- const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => {
9311
+ const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => {
9047
9312
  leaseLost = true;
9048
- leaseAbortController.abort(new Error("Dream lease lost during smart notes"));
9313
+ leaseAbortController.abort(new Error(reason));
9049
9314
  log("[dreamer] smart notes: lease lost — aborting");
9050
9315
  args.onLeaseLost?.("smart notes");
9051
9316
  }, args.leaseAcquisition);
@@ -9360,6 +9625,7 @@ async function confirmReadOnly(args, noteId, content, surfaceCondition, leaseSig
9360
9625
  parts: [{ type: "text", text: prompt, synthetic: true }]
9361
9626
  }
9362
9627
  }, {
9628
+ transport: createPromptAsyncTransport(client, childSessionId),
9363
9629
  timeoutMs: Math.max(1000, args.deadline - Date.now()),
9364
9630
  signal: promptSignal.signal,
9365
9631
  fallbackModels: args.fallbackModels,
@@ -9667,7 +9933,7 @@ function computeMapBatchSliceMs(remainingMs, batchesRemaining) {
9667
9933
  return Math.min(remainingMs, Math.max(MAP_BATCH_FLOOR_MS, Math.floor(remainingMs / batchesRemaining)));
9668
9934
  }
9669
9935
  function isTimeoutClassError2(error) {
9670
- return error instanceof Error && /^prompt timed out after \d+ms$/.test(error.message);
9936
+ return isPromptTimeoutError(error);
9671
9937
  }
9672
9938
  function shouldRequeueIndependentMapping(state, content, repoDir) {
9673
9939
  if (!state.hasSentinel || state.files.length > 0)
@@ -9722,7 +9988,7 @@ async function mapMemories(args) {
9722
9988
  }
9723
9989
  result.remaining = inputs.length;
9724
9990
  const abortController = new AbortController;
9725
- const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => abortController.abort(), args.leaseAcquisition);
9991
+ const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
9726
9992
  try {
9727
9993
  let consecutiveTimeouts = 0;
9728
9994
  let timeoutStreakElapsedMs = [];
@@ -9837,6 +10103,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
9837
10103
  parts: [{ type: "text", text: prompt, synthetic: true }]
9838
10104
  }
9839
10105
  }, {
10106
+ transport: createPromptAsyncTransport(client, agentSessionId),
9840
10107
  timeoutMs: sliceMs,
9841
10108
  signal,
9842
10109
  fallbackModels: args.fallbackModels,
@@ -9871,7 +10138,11 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
9871
10138
  } catch (error) {
9872
10139
  const desc = describeError(error);
9873
10140
  log(`[dreamer] map-memories batch failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
9874
- recordInvocation2(args, startedAt, { status: failedInvocationStatus(error), error });
10141
+ recordInvocation2(args, startedAt, {
10142
+ status: failedInvocationStatus(error),
10143
+ error,
10144
+ messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
10145
+ });
9875
10146
  if (error instanceof DreamerModuleFailureError)
9876
10147
  throw error;
9877
10148
  if (signal.aborted)
@@ -10196,7 +10467,7 @@ function pruneExpiredPrimerCandidatesForProject(db, projectIdentity, now = Date.
10196
10467
  db.transaction(() => {
10197
10468
  for (const id of toDelete)
10198
10469
  stmt.run(id, projectIdentity);
10199
- })();
10470
+ }).immediate();
10200
10471
  return toDelete.length;
10201
10472
  }
10202
10473
  async function promotePrimers(args) {
@@ -10279,12 +10550,12 @@ async function promotePrimers(args) {
10279
10550
 
10280
10551
  // ../plugin/src/features/magic-context/dreamer/primer-seed.ts
10281
10552
  var PRIMER_SEED_CAP_TOKENS = 4000;
10282
- function renderUserAndToolOrientation(messages, startOrdinal, endOrdinal, capTokens) {
10553
+ function renderUserAndToolOrientation(sessionId, startOrdinal, endOrdinal, capTokens) {
10283
10554
  const lines = [];
10284
10555
  let tokens = 0;
10285
- for (const msg of messages) {
10286
- if (msg.ordinal < startOrdinal || msg.ordinal > endOrdinal)
10287
- continue;
10556
+ let sawMessage = false;
10557
+ visitRawSessionMessages2(sessionId, startOrdinal, endOrdinal, (msg) => {
10558
+ sawMessage = true;
10288
10559
  const out = [];
10289
10560
  if (msg.role === "user" && hasMeaningfulUserText2(msg.parts)) {
10290
10561
  const text = extractTexts2(msg.parts).map((t) => cleanUserText2(t)).map(normalizeText).filter((t) => t.length > 0).join(" / ");
@@ -10297,22 +10568,27 @@ function renderUserAndToolOrientation(messages, startOrdinal, endOrdinal, capTok
10297
10568
  const lineTokens = estimateTokens(line);
10298
10569
  if (tokens + lineTokens > capTokens && lines.length > 0) {
10299
10570
  lines.push("… (orientation truncated; investigate the current source directly)");
10300
- return lines.join(`
10301
- `);
10571
+ return false;
10302
10572
  }
10303
10573
  lines.push(line);
10304
10574
  tokens += lineTokens;
10305
10575
  }
10306
- }
10307
- return lines.join(`
10308
- `);
10576
+ return true;
10577
+ }, { summary: true });
10578
+ return sawMessage ? lines.join(`
10579
+ `) : null;
10580
+ }
10581
+ function selectPrimerOriginCandidate(db, primer) {
10582
+ return getPrimerCandidatesByIds(db, primer.sourceCandidateIds).filter((candidate) => candidate.projectPath === primer.projectPath).sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
10309
10583
  }
10310
10584
  function loadPrePostP1(db, sessionId, originStartMessage) {
10311
10585
  const origin = db.prepare("SELECT sequence FROM compartments WHERE session_id = ? AND start_message = ? ORDER BY sequence ASC LIMIT 1").get(sessionId, originStartMessage);
10312
10586
  if (typeof origin?.sequence !== "number")
10313
10587
  return "";
10314
10588
  const originSeq = origin.sequence;
10315
- const rows = db.prepare(`SELECT sequence, start_message, end_message, title, p1, content
10589
+ const rows = db.prepare(`SELECT sequence, start_message, end_message,
10590
+ substr(title, 1, 512) AS title,
10591
+ substr(p1, 1, 1200) AS p1, substr(content, 1, 1200) AS content
10316
10592
  FROM compartments
10317
10593
  WHERE session_id = ? AND sequence IN (?, ?)
10318
10594
  ORDER BY sequence ASC`).all(sessionId, originSeq - 1, originSeq + 1);
@@ -10326,28 +10602,28 @@ function loadPrePostP1(db, sessionId, originStartMessage) {
10326
10602
  `);
10327
10603
  }
10328
10604
  function closedBookOriginP1(db, sessionId, originStartMessage) {
10329
- const row = db.prepare("SELECT title, p1, content FROM compartments WHERE session_id = ? AND start_message = ? ORDER BY sequence ASC LIMIT 1").get(sessionId, originStartMessage);
10605
+ const row = db.prepare(`SELECT substr(title, 1, 512) AS title,
10606
+ substr(p1, 1, 2000) AS p1, substr(content, 1, 2000) AS content
10607
+ FROM compartments WHERE session_id = ? AND start_message = ? ORDER BY sequence ASC LIMIT 1`).get(sessionId, originStartMessage);
10330
10608
  const body = (row?.p1 ?? row?.content ?? "").slice(0, 2000);
10331
10609
  const orientation = row?.title ? `${row.title}: ${body}` : body;
10332
10610
  return { orientation, sessionId };
10333
10611
  }
10334
10612
  function buildPrimerSeed(db, primer) {
10335
- const candidates = getPrimerCandidatesByIds(db, primer.sourceCandidateIds);
10336
- const mostRecent = candidates.slice().sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
10613
+ const mostRecent = selectPrimerOriginCandidate(db, primer);
10337
10614
  if (!mostRecent || typeof mostRecent.sourceCompartmentStart !== "number" || typeof mostRecent.sourceCompartmentEnd !== "number") {
10338
10615
  return { kind: "closed-book", orientation: "", prePost: "", sessionId: null };
10339
10616
  }
10340
10617
  const sessionId = mostRecent.sessionId;
10341
10618
  const start = mostRecent.sourceCompartmentStart;
10342
10619
  const end = mostRecent.sourceCompartmentEnd;
10343
- let raw = [];
10620
+ let orientation = null;
10344
10621
  try {
10345
- raw = readRawSessionMessages2(sessionId);
10622
+ orientation = renderUserAndToolOrientation(sessionId, start, end, PRIMER_SEED_CAP_TOKENS);
10346
10623
  } catch {
10347
- raw = [];
10624
+ orientation = null;
10348
10625
  }
10349
- const inRange = raw.some((m) => m.ordinal >= start && m.ordinal <= end);
10350
- if (!inRange) {
10626
+ if (orientation === null) {
10351
10627
  const closed = closedBookOriginP1(db, sessionId, start);
10352
10628
  return {
10353
10629
  kind: "closed-book",
@@ -10356,7 +10632,6 @@ function buildPrimerSeed(db, primer) {
10356
10632
  sessionId
10357
10633
  };
10358
10634
  }
10359
- const orientation = renderUserAndToolOrientation(raw, start, end, PRIMER_SEED_CAP_TOKENS);
10360
10635
  return {
10361
10636
  kind: "raw",
10362
10637
  orientation,
@@ -10590,9 +10865,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
10590
10865
  }
10591
10866
  }
10592
10867
  function originSessionIdForPrimer(args, primer) {
10593
- const candidates = getPrimerCandidatesByIds(args.db, primer.sourceCandidateIds);
10594
- const mostRecent = candidates.slice().sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
10595
- return mostRecent?.sessionId ?? null;
10868
+ return selectPrimerOriginCandidate(args.db, primer)?.sessionId ?? null;
10596
10869
  }
10597
10870
  function recordInvocation3(args, startedAt, params) {
10598
10871
  if (!args.parentSessionId)
@@ -10615,13 +10888,7 @@ var FRUSTRATION_MARKER_REGEX = /\b(?:not what i asked|i already (?:said|told you
10615
10888
  var LEARNINGS_BLOCK_REGEX = /<learnings\b[^>]*>(.*?)<\/learnings>/is;
10616
10889
  var LEARNING_REGEX = /<learning\b([^>]*)>(.*?)<\/learning>/gis;
10617
10890
  var ATTR_REGEX = /([a-zA-Z_:-]+)\s*=\s*"([^"]*)"/g;
10618
- var VALID_MEMORY_CATEGORIES = new Set([
10619
- "PROJECT_RULES",
10620
- "ARCHITECTURE",
10621
- "CONSTRAINTS",
10622
- "CONFIG_VALUES",
10623
- "NAMING"
10624
- ]);
10891
+ var VALID_MEMORY_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
10625
10892
  var RAW_QUOTE_REGEX = /["“”][^"“”]{4,}["“”]|'[^']{4,}'/;
10626
10893
  var DATE_REGEX = /\b(?:20\d{2}[-/]\d{1,2}[-/]\d{1,2}|\d{1,2}\/\d{1,2}\/20\d{2}|(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\s+\d{1,2},?\s+20\d{2})\b/i;
10627
10894
  function parseRetrospectiveLearnings(text) {
@@ -10996,11 +11263,48 @@ function normalizeOpenCodeRows(db, sessionId, rows) {
10996
11263
  return [];
10997
11264
  const messageIds = rows.map((row) => row.id);
10998
11265
  const placeholders = messageIds.map(() => "?").join(", ");
10999
- const partRows = db.prepare(`SELECT message_id, data
11266
+ const userIds = rows.filter((row) => parseJsonRecord(row.data)?.role === "user").map((row) => row.id);
11267
+ const userPlaceholders = userIds.map(() => "?").join(", ") || "NULL";
11268
+ const output = `CASE json_type(data, '$.state.output')
11269
+ WHEN 'text' THEN json_extract(data, '$.state.output')
11270
+ WHEN 'null' THEN '' ELSE COALESCE(data -> '$.state.output', '') END`;
11271
+ const error = `CASE json_type(data, '$.state.error')
11272
+ WHEN 'text' THEN json_extract(data, '$.state.error')
11273
+ WHEN 'null' THEN '' ELSE COALESCE(data -> '$.state.error', '') END`;
11274
+ const containsError = (value) => ["error", "failed", "exception", "traceback"].map((word) => `(' ' || lower(${value}) || ' ') GLOB '*[^a-z0-9_]${word}[^a-z0-9_]*'`).join(" OR ");
11275
+ const errorWords = `CASE WHEN json_type(data, '$.state.output') IN ('object', 'array')
11276
+ THEN EXISTS (SELECT 1 FROM json_tree(data, '$.state.output') AS leaf
11277
+ WHERE (leaf.type = 'text' AND (${containsError("json_quote(leaf.atom)")}))
11278
+ OR (typeof(leaf.key) = 'text' AND (${containsError("json_quote(leaf.key)")})))
11279
+ WHEN instr(CAST(${output} AS BLOB), x'00') > 0 THEN EXISTS (
11280
+ WITH RECURSIVE segments(rest, piece) AS (
11281
+ SELECT CAST(${output} AS BLOB), x''
11282
+ UNION ALL
11283
+ SELECT CASE WHEN instr(rest, x'00') > 0
11284
+ THEN substr(rest, instr(rest, x'00') + 1) ELSE NULL END,
11285
+ CASE WHEN instr(rest, x'00') > 0
11286
+ THEN substr(rest, 1, instr(rest, x'00') - 1) ELSE rest END
11287
+ FROM segments WHERE rest IS NOT NULL
11288
+ ) SELECT 1 FROM segments WHERE (${containsError("CAST(piece AS TEXT)")}))
11289
+ ELSE (${containsError(output)}) END`;
11290
+ const whitespace = `
11291
+ \v\f\r              \u2028\u2029   \uFEFF`;
11292
+ const partRows = db.prepare(`SELECT message_id, CASE json_extract(data, '$.type')
11293
+ WHEN 'text' THEN CASE WHEN message_id IN (${userPlaceholders}) THEN json_object(
11294
+ 'type', 'text', 'text', data -> '$.text',
11295
+ 'synthetic', data -> '$.synthetic', 'ignored', data -> '$.ignored') ELSE '{}' END
11296
+ WHEN 'tool' THEN json_object(
11297
+ 'type', 'tool', 'tool', data -> '$.tool',
11298
+ 'state', json_object('isError', json(CASE WHEN
11299
+ json_type(data, '$.state.isError') = 'true'
11300
+ OR lower(json_extract(data, '$.state.status')) = 'error'
11301
+ OR length(CAST(trim(${error}, ?) AS BLOB)) > 0 OR ${errorWords}
11302
+ THEN 'true' ELSE 'false' END)))
11303
+ ELSE '{}' END AS data
11000
11304
  FROM part
11001
- WHERE +session_id = ?
11305
+ WHERE +session_id = ? AND json_valid(data)
11002
11306
  AND likelihood(message_id IN (${placeholders}), 0.000001)
11003
- ORDER BY time_created ASC, id ASC`).all(sessionId, ...messageIds);
11307
+ ORDER BY time_created ASC, id ASC`).all(...userIds, whitespace, sessionId, ...messageIds);
11004
11308
  const partsByMessageId = new Map;
11005
11309
  for (const row of partRows) {
11006
11310
  const parts = partsByMessageId.get(row.message_id) ?? [];
@@ -11380,6 +11684,10 @@ function validateVerifyManifest(text, expectedIds) {
11380
11684
  // ../plugin/src/features/magic-context/dreamer/verify.ts
11381
11685
  var VERIFY_BATCH_SIZE = 50;
11382
11686
  var IDENTICAL_PROVIDER_FAILURE_BATCH_LIMIT = 2;
11687
+ var VERIFY_BATCH_FLOOR_MS = 240000;
11688
+ function computeVerifyBatchSliceMs(remainingMs, batchesRemaining) {
11689
+ return Math.min(remainingMs, Math.max(VERIFY_BATCH_FLOOR_MS, Math.floor(remainingMs / batchesRemaining)));
11690
+ }
11383
11691
  function closeBroadCycle(args, cycleStartAt) {
11384
11692
  if (!args.forceBroad || cycleStartAt === undefined)
11385
11693
  return;
@@ -11434,17 +11742,19 @@ async function runVerify(args) {
11434
11742
  batches.push(gate.inScope.slice(i, i + VERIFY_BATCH_SIZE));
11435
11743
  }
11436
11744
  const abortController = new AbortController;
11437
- const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => abortController.abort(), args.leaseAcquisition);
11745
+ const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
11438
11746
  let consecutiveProviderFailures = 0;
11439
11747
  let priorProviderFailureFingerprint = null;
11440
11748
  let lastProviderFailure = null;
11441
11749
  try {
11442
11750
  for (let i = 0;i < batches.length; i += 1) {
11443
11751
  const remainingMs = Math.max(0, args.deadline - Date.now());
11444
- if (remainingMs <= 0)
11752
+ if (remainingMs < VERIFY_BATCH_FLOOR_MS) {
11753
+ result.stopReason = "deadline";
11754
+ log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"}: stopping before batch ${i + 1}/${batches.length} — remaining budget ${remainingMs}ms is below the ${VERIFY_BATCH_FLOOR_MS}ms batch floor`);
11445
11755
  break;
11446
- const batchesRemaining = batches.length - i;
11447
- const sliceMs = Math.max(1, Math.floor(remainingMs / batchesRemaining));
11756
+ }
11757
+ const sliceMs = computeVerifyBatchSliceMs(remainingMs, batches.length - i);
11448
11758
  const counts = await verifyOneBatch(args, batches[i], sliceMs, abortController.signal);
11449
11759
  result.verified += counts.verified;
11450
11760
  result.updated += counts.updated;
@@ -11455,6 +11765,11 @@ async function runVerify(args) {
11455
11765
  result.remaining -= batchProcessed;
11456
11766
  result.batches += 1;
11457
11767
  args.onProgress?.(result.verified + result.updated + result.archived + result.skipped + result.refused, result.refused);
11768
+ if (counts.timedOut) {
11769
+ result.stopReason = "batch-timeout";
11770
+ log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"}: batch ${i + 1}/${batches.length} timed out after its ${sliceMs}ms slice; stopping with ${result.remaining} remaining`);
11771
+ break;
11772
+ }
11458
11773
  if (counts.providerFailure) {
11459
11774
  lastProviderFailure = counts.providerFailure;
11460
11775
  if (counts.providerFailure.fingerprint === priorProviderFailureFingerprint) {
@@ -11543,6 +11858,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11543
11858
  parts: [{ type: "text", text: prompt, synthetic: true }]
11544
11859
  }
11545
11860
  }, {
11861
+ transport: createPromptAsyncTransport(client, agentSessionId),
11546
11862
  timeoutMs: sliceMs,
11547
11863
  signal,
11548
11864
  fallbackModels: args.fallbackModels,
@@ -11577,8 +11893,24 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11577
11893
  const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
11578
11894
  const promptFailure = getPromptFailureDetail(error);
11579
11895
  log(`[dreamer] verify batch ${providerFailure ? "provider failure" : "failed"}: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
11580
- recordInvocation4(args, startedAt, { status: failedInvocationStatus(error), error });
11581
- if (error instanceof DreamerModuleFailureError || signal.aborted || promptFailure !== null && promptFailure.failureClass !== "parse_failed" && !providerFailure)
11896
+ recordInvocation4(args, startedAt, {
11897
+ status: failedInvocationStatus(error),
11898
+ error,
11899
+ messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
11900
+ });
11901
+ if (error instanceof DreamerModuleFailureError || signal.aborted)
11902
+ throw error;
11903
+ if (promptFailure?.failureClass === "provider_timeout" || isPromptTimeoutError(error)) {
11904
+ return {
11905
+ verified: 0,
11906
+ updated: 0,
11907
+ archived: 0,
11908
+ skipped: 0,
11909
+ refused: 0,
11910
+ timedOut: true
11911
+ };
11912
+ }
11913
+ if (promptFailure !== null && promptFailure.failureClass !== "parse_failed" && !providerFailure)
11582
11914
  throw error;
11583
11915
  return {
11584
11916
  verified: 0,
@@ -11820,6 +12152,16 @@ function dreamRunFailureDetail(error) {
11820
12152
  }
11821
12153
  const described = describeError(error);
11822
12154
  const message = described.brief;
12155
+ if (error instanceof Error && error.name === "HiddenAgentStepLimit") {
12156
+ return {
12157
+ failure_class: "step_limit",
12158
+ model_attempted: null,
12159
+ models_tried: [],
12160
+ provider_error: null,
12161
+ timeout_ms: null,
12162
+ child_session_id: null
12163
+ };
12164
+ }
11823
12165
  const providerFailure = error instanceof HiddenCompletionRefusal || error instanceof Error && error.name === "DreamerProviderOutputFailureError";
11824
12166
  return {
11825
12167
  failure_class: providerFailure ? "provider_error" : /no models?|model chain is empty/i.test(message) ? "no_models" : "unknown",
@@ -11959,11 +12301,16 @@ function createDreamTaskExecutor(deps) {
11959
12301
  });
11960
12302
  };
11961
12303
  reportProgress(0);
12304
+ if (config.modelChainUnavailable) {
12305
+ return {
12306
+ status: "completed",
12307
+ detail: "skipped: Pi model chain is empty (no configured model resolves in Pi)"
12308
+ };
12309
+ }
11962
12310
  const incompleteMessage = (remaining) => {
11963
12311
  const processed = processedDreamTaskItems(backlogAtStart.pending, remaining);
11964
12312
  return `${config.task} incomplete: ${remaining} remain (was ${backlogAtStart.pending} at run start; processed ${processed} this run)`;
11965
12313
  };
11966
- const parent = await resolveParentSessionId();
11967
12314
  let moduleRoute;
11968
12315
  if (config.task === "curate" || config.task === "map-memories" || config.task === "compress-cues" || config.task === "classify-memories" || config.task === "verify" || config.task === "verify-broad" || config.task === "retrospective") {
11969
12316
  try {
@@ -11979,6 +12326,19 @@ function createDreamTaskExecutor(deps) {
11979
12326
  throw new DreamerModuleFailureError("authority.status", error);
11980
12327
  }
11981
12328
  }
12329
+ if (!moduleRoute && getAuthorityManagedMarker(db, projectIdentity) && [
12330
+ "curate",
12331
+ "map-memories",
12332
+ "compress-cues",
12333
+ "classify-memories",
12334
+ "verify",
12335
+ "verify-broad",
12336
+ "retrospective"
12337
+ ].includes(config.task)) {
12338
+ logDreamerNotOwnerOnce(projectIdentity);
12339
+ return { status: "completed", detail: "skipped: module-managed memory; not owner" };
12340
+ }
12341
+ const parent = await resolveParentSessionId();
11982
12342
  if (!leaseOwnershipMatches(db, holderId, leaseAcquisition.generation, leaseKey)) {
11983
12343
  throw new Error("Dream lease lost during executor setup");
11984
12344
  }
@@ -12168,13 +12528,14 @@ function createDreamTaskExecutor(deps) {
12168
12528
  });
12169
12529
  return { status: "completed" };
12170
12530
  }
12171
- const error = incompleteMessage(result.remaining);
12531
+ const batchTimedOut = result.stopReason === "batch-timeout";
12532
+ const error = batchTimedOut ? `${config.task}: a batch timed out within its time slice; ${result.remaining} remain` : incompleteMessage(result.remaining);
12172
12533
  recordRun("failed", error, {
12173
12534
  progress: verificationProgress,
12174
12535
  memoryChanges: computeMemoryDelta(memoryBefore),
12175
12536
  backlogAfter
12176
12537
  });
12177
- return { status: "failed", transient: true, error };
12538
+ return { status: "failed", transient: !batchTimedOut, error };
12178
12539
  }
12179
12540
  recordRun("completed", null, {
12180
12541
  progress: verificationProgress,
@@ -13000,25 +13361,45 @@ function ensureSeeded(db, projectIdentity, config, now) {
13000
13361
  const nextDueAt = nextDueAtMs(config.schedule, now);
13001
13362
  seedTaskScheduleState(db, projectIdentity, config.task, nextDueAt, lastRunAt, config.schedule);
13002
13363
  }
13364
+ function firstRepeatedCivilMinute(candidateMs) {
13365
+ const date = new Date(candidateMs);
13366
+ const first = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes()).getTime();
13367
+ return first < candidateMs ? first : null;
13368
+ }
13003
13369
  function reconcileSchedule(db, projectIdentity, config, now) {
13004
13370
  ensureSeeded(db, projectIdentity, config, now);
13005
- const stored = getTaskScheduleState(db, projectIdentity, config.task);
13006
- if (!stored || stored.schedule === config.schedule)
13007
- return;
13008
- if (config.schedule.trim() === "") {
13009
- writeTaskScheduleState(db, { ...stored, schedule: config.schedule, nextDueAt: null });
13010
- return;
13011
- }
13012
- if (stored.schedule === null && stored.nextDueAt !== null) {
13013
- writeTaskScheduleState(db, { ...stored, schedule: config.schedule });
13371
+ if (getTaskScheduleState(db, projectIdentity, config.task)?.schedule === config.schedule)
13014
13372
  return;
13373
+ const candidate = nextDueAtMs(config.schedule, now);
13374
+ const repeatedFirst = candidate === null ? null : firstRepeatedCivilMinute(candidate);
13375
+ const afterRepeatedFirst = repeatedFirst === null ? candidate : nextDueAtMs(config.schedule, now, repeatedFirst);
13376
+ db.exec("BEGIN IMMEDIATE");
13377
+ let committed = false;
13378
+ try {
13379
+ const stored = getTaskScheduleState(db, projectIdentity, config.task);
13380
+ if (stored && stored.schedule !== config.schedule) {
13381
+ if (stored.schedule === null && stored.nextDueAt !== null) {
13382
+ writeTaskScheduleState(db, { ...stored, schedule: config.schedule });
13383
+ } else {
13384
+ const nextDueAt = repeatedFirst !== null && candidate !== null && stored.nextDueAt !== null && stored.nextDueAt > candidate && stored.lastStatus !== null ? afterRepeatedFirst : candidate;
13385
+ const reconciledNextDueAt = stored.nextDueAt === null ? nextDueAt : nextDueAt === null ? stored.nextDueAt : Math.min(stored.nextDueAt, nextDueAt);
13386
+ writeTaskScheduleState(db, {
13387
+ ...stored,
13388
+ schedule: config.schedule,
13389
+ nextDueAt: reconciledNextDueAt,
13390
+ retryCount: reconciledNextDueAt === stored.nextDueAt ? stored.retryCount : 0
13391
+ });
13392
+ }
13393
+ }
13394
+ db.exec("COMMIT");
13395
+ committed = true;
13396
+ } finally {
13397
+ if (!committed) {
13398
+ try {
13399
+ db.exec("ROLLBACK");
13400
+ } catch {}
13401
+ }
13015
13402
  }
13016
- writeTaskScheduleState(db, {
13017
- ...stored,
13018
- schedule: config.schedule,
13019
- nextDueAt: nextDueAtMs(config.schedule, now),
13020
- retryCount: 0
13021
- });
13022
13403
  }
13023
13404
  function planDueTasks(db, projectIdentity, tasks, now) {
13024
13405
  const pruned = pruneNonCanonicalTaskRows(db, projectIdentity, CANONICAL_DREAM_TASKS);
@@ -13027,6 +13408,8 @@ function planDueTasks(db, projectIdentity, tasks, now) {
13027
13408
  }
13028
13409
  const due = [];
13029
13410
  for (const config of tasks) {
13411
+ if (config.schedule.trim() === "")
13412
+ continue;
13030
13413
  reconcileSchedule(db, projectIdentity, config, now);
13031
13414
  const state = getTaskScheduleState(db, projectIdentity, config.task);
13032
13415
  if (!state || state.nextDueAt === null)
@@ -13112,10 +13495,11 @@ async function runDomainGroup(deps, group, cb) {
13112
13495
  }
13113
13496
  try {
13114
13497
  for (const due of [...group].sort((a, b) => compareTaskOrder(a.config.task, b.config.task))) {
13115
- if (!leaseOwnershipMatches(db, holderId, acquisition.generation, leaseKey)) {
13498
+ if (!reacquireOwnedLease(db, holderId, leaseKey, acquisition.generation)) {
13116
13499
  log(`[dreamer] domain lease lost (${leaseKey}) — stopping remaining task(s)`);
13117
13500
  break;
13118
13501
  }
13502
+ acquisition = { acquiredAt: Date.now(), generation: acquisition.generation };
13119
13503
  if (!cb?.forceGate) {
13120
13504
  const gatePass = evaluateTaskGate(due.config.task, {
13121
13505
  db,
@@ -13170,6 +13554,8 @@ async function runManualDream(deps) {
13170
13554
  backlogBefore: {},
13171
13555
  backlogAfter: {}
13172
13556
  };
13557
+ if (!isUsableProjectIdentity(deps.projectIdentity))
13558
+ return result;
13173
13559
  let selected;
13174
13560
  let forceGate = false;
13175
13561
  if (deps.task) {
@@ -13243,6 +13629,8 @@ async function runManualDream(deps) {
13243
13629
  return result;
13244
13630
  }
13245
13631
  async function runDueTasksForProject(deps) {
13632
+ if (!isUsableProjectIdentity(deps.projectIdentity))
13633
+ return 0;
13246
13634
  const now = deps.now ?? Date.now();
13247
13635
  const due = planDueTasks(deps.db, deps.projectIdentity, deps.tasks, now);
13248
13636
  if (due.length === 0)
@@ -13488,6 +13876,13 @@ function openTimerDatabaseOrNull(context) {
13488
13876
  }
13489
13877
  return db;
13490
13878
  }
13879
+ var refusedEmptyIdentityDirectories = new Set;
13880
+ function logEmptyIdentityRefusalOnce(directory) {
13881
+ if (refusedEmptyIdentityDirectories.has(directory))
13882
+ return;
13883
+ refusedEmptyIdentityDirectories.add(directory);
13884
+ log(`[dreamer] not registering ${directory}: it has no project identity, so no project-scoped work runs for it`);
13885
+ }
13491
13886
  var registeredProjects = new Map;
13492
13887
  function stopDreamScheduleTimerIfIdle() {
13493
13888
  if (registeredProjects.size !== 0)
@@ -13507,6 +13902,10 @@ function stopDreamScheduleTimerIfIdle() {
13507
13902
  log("[dreamer] stopped dream schedule timer (no projects left)");
13508
13903
  }
13509
13904
  async function startDreamScheduleTimer(args) {
13905
+ if (!isUsableProjectIdentity(args.projectIdentity)) {
13906
+ logEmptyIdentityRefusalOnce(args.directory);
13907
+ return;
13908
+ }
13510
13909
  beginBootQuietPeriod();
13511
13910
  const db = openTimerDatabaseOrNull("schedule timer registration");
13512
13911
  if (!db)
@@ -13704,7 +14103,8 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
13704
14103
  }
13705
14104
  const dreamerConfig = reg.dreamerConfig;
13706
14105
  const dreamingEnabled = Boolean(dreamerConfig && dreamerConfig.disable !== true);
13707
- const runtimeConfigs = dreamingEnabled && dreamerConfig ? buildDreamTaskRuntimeConfigs(dreamerConfig, reg.harness, reg.language, reg.mural?.model) : [];
14106
+ const configuredTasks = dreamingEnabled && dreamerConfig ? buildDreamTaskRuntimeConfigs(dreamerConfig, reg.harness, reg.language, reg.mural?.model) : [];
14107
+ const runtimeConfigs = reg.validateTaskModels?.(configuredTasks) ?? configuredTasks;
13708
14108
  await sweepOrphanedInternalChildren(reg, runtimeConfigs.filter((config) => PRIVACY_SENSITIVE_CHILD_TASKS.includes(config.task)).map((config) => config.timeoutMinutes));
13709
14109
  if (commitIndexingEnabled && reg.gitCommitIndexing) {
13710
14110
  await sweepGitCommits({
@@ -13719,6 +14119,10 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
13719
14119
  }
13720
14120
  try {
13721
14121
  await runCompiledSmartNoteSweep(reg, db);
14122
+ } catch (error) {
14123
+ log(`[dreamer] compiled smart-note sweep failed for ${reg.projectIdentity}:`, error);
14124
+ }
14125
+ try {
13722
14126
  const executor = createDreamTaskExecutor({
13723
14127
  client: reg.client,
13724
14128
  sessionDirectory: reg.directory,
@@ -13773,6 +14177,10 @@ async function sweepOrphanedInternalChildren(reg, privacyTimeoutMinutes) {
13773
14177
  }
13774
14178
  }
13775
14179
  async function runCompiledSmartNoteSweep(reg, db) {
14180
+ if (getAuthorityManagedMarker(db, reg.projectIdentity)) {
14181
+ logDreamerNotOwnerOnce(reg.projectIdentity);
14182
+ return;
14183
+ }
13776
14184
  const leaseKey = leaseKeyFor("evaluate-smart-notes", reg.projectIdentity);
13777
14185
  const holderId = crypto.randomUUID();
13778
14186
  if (!acquireLease(db, holderId, leaseKey))
@@ -15062,13 +15470,101 @@ function terminateChild(child) {
15062
15470
  }
15063
15471
  }
15064
15472
 
15473
+ // src/dreamer/primer-raw-provider-pi.ts
15474
+ import { join as join8 } from "node:path";
15475
+
15476
+ // src/dreamer/bounded-session-reader.ts
15477
+ import { closeSync, opendirSync, openSync, readSync } from "node:fs";
15478
+ import { join as join6 } from "node:path";
15479
+ var MAX_ENTRY_BYTES = 1024 * 1024;
15480
+ function* lines(path) {
15481
+ const fd = openSync(path, "r");
15482
+ const buffer = Buffer.alloc(64 * 1024);
15483
+ let pending = Buffer.alloc(0);
15484
+ try {
15485
+ while (true) {
15486
+ const bytes = readSync(fd, buffer, 0, buffer.length, null);
15487
+ if (bytes === 0)
15488
+ break;
15489
+ let start = 0;
15490
+ for (let i = 0;i < bytes; i++) {
15491
+ if (buffer[i] !== 10)
15492
+ continue;
15493
+ if (pending.length + i - start > MAX_ENTRY_BYTES)
15494
+ throw new Error("Pi session entry exceeds bounded reader capacity");
15495
+ yield Buffer.concat([pending, buffer.subarray(start, i)]).toString("utf8");
15496
+ pending = Buffer.alloc(0);
15497
+ start = i + 1;
15498
+ }
15499
+ if (pending.length + bytes - start > MAX_ENTRY_BYTES)
15500
+ throw new Error("Pi session entry exceeds bounded reader capacity");
15501
+ pending = Buffer.concat([pending, buffer.subarray(start, bytes)]);
15502
+ }
15503
+ if (pending.length)
15504
+ yield pending.toString("utf8");
15505
+ } finally {
15506
+ closeSync(fd);
15507
+ }
15508
+ }
15509
+ function record(value) {
15510
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
15511
+ }
15512
+ function* sessionFiles(directory, nested) {
15513
+ let dir;
15514
+ try {
15515
+ dir = opendirSync(directory);
15516
+ } catch {
15517
+ return;
15518
+ }
15519
+ try {
15520
+ while (true) {
15521
+ const entry = dir.readSync();
15522
+ if (!entry)
15523
+ break;
15524
+ const path = join6(directory, entry.name);
15525
+ if (entry.isFile() && entry.name.endsWith(".jsonl"))
15526
+ yield path;
15527
+ else if (nested && entry.isDirectory())
15528
+ yield* sessionFiles(path, false);
15529
+ }
15530
+ } finally {
15531
+ dir.closeSync();
15532
+ }
15533
+ }
15534
+ function findSession(directory, nested, sessionId) {
15535
+ for (const header of sessionHeaders(directory, nested)) {
15536
+ if (header.id === sessionId)
15537
+ return header.path;
15538
+ }
15539
+ return null;
15540
+ }
15541
+ function* sessionHeaders(directory, nested) {
15542
+ for (const path of sessionFiles(directory, nested)) {
15543
+ try {
15544
+ for (const line of lines(path)) {
15545
+ const header = record(JSON.parse(line));
15546
+ if (header.type === "session")
15547
+ yield { ...header, path };
15548
+ break;
15549
+ }
15550
+ } catch {}
15551
+ }
15552
+ }
15553
+ function* sessionEntries(path) {
15554
+ for (const line of lines(path)) {
15555
+ try {
15556
+ yield JSON.parse(line);
15557
+ } catch {}
15558
+ }
15559
+ }
15560
+
15065
15561
  // src/dreamer/pi-session-api.ts
15066
15562
  import { existsSync as existsSync8, readFileSync as readFileSync6, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
15067
15563
  import {
15068
15564
  basename as basename2,
15069
15565
  dirname as dirname2,
15070
15566
  extname,
15071
- join as join6,
15567
+ join as join7,
15072
15568
  relative,
15073
15569
  resolve,
15074
15570
  sep as sep2
@@ -15118,11 +15614,11 @@ function resolveRunningEntry() {
15118
15614
  function findPackageRoot(startDir) {
15119
15615
  let dir = startDir;
15120
15616
  while (dir !== dirname2(dir)) {
15121
- const pkg = readManifest(join6(dir, "package.json"));
15617
+ const pkg = readManifest(join7(dir, "package.json"));
15122
15618
  if (isCodingAgentPackageName(pkg?.name)) {
15123
15619
  if (basename2(dir) === "dist") {
15124
15620
  const parentDir = dirname2(dir);
15125
- const parentPkg = readManifest(join6(parentDir, "package.json"));
15621
+ const parentPkg = readManifest(join7(parentDir, "package.json"));
15126
15622
  if (parentPkg?.name === pkg.name) {
15127
15623
  return { dir: parentDir, pkg: parentPkg };
15128
15624
  }
@@ -15181,7 +15677,7 @@ function toSourceEntry(entryPath, pkgDir) {
15181
15677
  if (srcRel === rel) {
15182
15678
  return null;
15183
15679
  }
15184
- return join6(pkgDir, srcRel);
15680
+ return join7(pkgDir, srcRel);
15185
15681
  }
15186
15682
  var defaultLoaders = [
15187
15683
  {
@@ -15266,35 +15762,108 @@ async function loadDefaultPiSessionApi(loaders) {
15266
15762
  }
15267
15763
 
15268
15764
  // src/dreamer/primer-raw-provider-pi.ts
15269
- function createPiPrimerRawProviderFactory(deps = {}) {
15270
- let resolved = null;
15271
- const resolveDeps = async () => {
15272
- if (deps.listSessions && deps.loadEntriesFromFile) {
15273
- return {
15274
- listSessions: deps.listSessions,
15275
- loadEntriesFromFile: deps.loadEntriesFromFile
15276
- };
15765
+ var MAX_PARTS = 256;
15766
+ var TOOL_KEYS = [
15767
+ "description",
15768
+ "filePath",
15769
+ "path",
15770
+ "pattern",
15771
+ "query",
15772
+ "symbol",
15773
+ "module",
15774
+ "action"
15775
+ ];
15776
+ function record2(value) {
15777
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
15778
+ }
15779
+ function short(value, max = 512) {
15780
+ if (typeof value !== "string")
15781
+ return;
15782
+ return value.length <= max || estimateTokens(value) <= PRIMER_SEED_CAP_TOKENS ? value : value.slice(0, max);
15783
+ }
15784
+ function projectEntry(value) {
15785
+ const entry = record2(value);
15786
+ if (entry.type !== "message")
15787
+ return { type: entry.type };
15788
+ const message = record2(entry.message);
15789
+ let content = [];
15790
+ if (message.role === "user" && typeof message.content === "string") {
15791
+ content = short(message.content, RAW_SUMMARY_TEXT_MAX_CHARS);
15792
+ } else if (message.role !== "toolResult" && Array.isArray(message.content)) {
15793
+ if (message.content.length > MAX_PARTS)
15794
+ throw new Error("Pi primer entry has too many parts");
15795
+ content = message.content.flatMap((part) => {
15796
+ const p = record2(part);
15797
+ if (p.type === "text")
15798
+ return [
15799
+ { type: "text", text: short(p.text, RAW_SUMMARY_TEXT_MAX_CHARS) }
15800
+ ];
15801
+ if (p.type !== "toolCall")
15802
+ return [];
15803
+ const args = record2(p.arguments);
15804
+ return [
15805
+ {
15806
+ type: "toolCall",
15807
+ id: short(p.id),
15808
+ name: short(p.name),
15809
+ arguments: Object.fromEntries(TOOL_KEYS.map((key) => [key, short(args[key])]))
15810
+ }
15811
+ ];
15812
+ });
15813
+ }
15814
+ return {
15815
+ type: entry.type,
15816
+ id: entry.id,
15817
+ timestamp: entry.timestamp,
15818
+ message: {
15819
+ role: message.role,
15820
+ content,
15821
+ toolCallId: short(message.toolCallId),
15822
+ toolName: short(message.toolName)
15277
15823
  }
15278
- resolved ??= loadDefaultPiSessionApi();
15279
- return resolved;
15280
15824
  };
15825
+ }
15826
+ function* summaryEntries(path) {
15827
+ let pendingResults = 0;
15828
+ for (const line of lines(path)) {
15829
+ let parsed;
15830
+ try {
15831
+ parsed = JSON.parse(line);
15832
+ } catch {
15833
+ continue;
15834
+ }
15835
+ const entry = projectEntry(parsed);
15836
+ const e = record2(entry);
15837
+ if (e.type === "message") {
15838
+ const m = record2(e.message);
15839
+ if (m.role === "toolResult" && m.toolCallId) {
15840
+ if (++pendingResults > MAX_PARTS)
15841
+ throw new Error("Pi primer has too many consecutive tool results");
15842
+ } else if (m.role === "user" || m.role === "assistant")
15843
+ pendingResults = 0;
15844
+ }
15845
+ yield entry;
15846
+ }
15847
+ }
15848
+ function createPiPrimerRawProviderFactory(deps = {}) {
15281
15849
  return async (sessionId) => {
15282
15850
  try {
15283
- const { listSessions, loadEntriesFromFile } = await resolveDeps();
15284
- const sessions = await listSessions(deps.sessionDir);
15285
- const match = sessions.find((s) => s && typeof s === "object" && s.id === sessionId && typeof s.path === "string");
15286
- if (!match || typeof match.path !== "string")
15287
- return null;
15288
- const entries = await loadEntriesFromFile(match.path);
15289
- const messages = convertEntriesToRawMessages(entries);
15290
- if (messages.length === 0)
15851
+ let directory = deps.sessionDir;
15852
+ if (!directory) {
15853
+ const mod = await resolvePiCodingAgentModule();
15854
+ if (!mod.getAgentDir)
15855
+ return null;
15856
+ directory = join8(mod.getAgentDir(), "sessions");
15857
+ }
15858
+ const path = findSession(directory, !deps.sessionDir, sessionId);
15859
+ if (!path)
15291
15860
  return null;
15292
15861
  return {
15293
15862
  readMessages() {
15294
- return messages;
15863
+ throw new Error("Pi primer history requires bounded pages");
15295
15864
  },
15296
- getMessageCount() {
15297
- return messages.length;
15865
+ readMessagePage(after, limit, watermark) {
15866
+ return convertEntriesToRawMessagePage(summaryEntries(path), after, Math.min(50, limit), watermark);
15298
15867
  }
15299
15868
  };
15300
15869
  } catch {
@@ -15304,17 +15873,22 @@ function createPiPrimerRawProviderFactory(deps = {}) {
15304
15873
  }
15305
15874
 
15306
15875
  // src/dreamer/retrospective-raw-provider-pi.ts
15307
- import { resolve as resolve2 } from "node:path";
15876
+ import { join as join9, resolve as resolve2 } from "node:path";
15308
15877
  class PiRetrospectiveRawProvider {
15309
15878
  deps;
15310
15879
  sessionPathById = new Map;
15311
- resolvedDefaultDeps = null;
15312
15880
  constructor(deps) {
15313
15881
  this.deps = deps;
15314
15882
  }
15315
15883
  async listProjectSessions(_projectIdentity) {
15316
- const deps = await this.resolveDeps();
15317
- const sessions = await deps.listSessions(this.deps.sessionDir);
15884
+ let directory = this.deps.sessionDir;
15885
+ if (!directory && !this.deps.listSessions) {
15886
+ const mod = await resolvePiCodingAgentModule();
15887
+ if (!mod.getAgentDir)
15888
+ return [];
15889
+ directory = join9(mod.getAgentDir(), "sessions");
15890
+ }
15891
+ const sessions = this.deps.listSessions ? await this.deps.listSessions(directory) : directory ? sessionHeaders(directory, !this.deps.sessionDir) : [];
15318
15892
  const projectCwd = resolve2(this.deps.projectCwd);
15319
15893
  const result = [];
15320
15894
  this.sessionPathById.clear();
@@ -15336,51 +15910,50 @@ class PiRetrospectiveRawProvider {
15336
15910
  return result.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0));
15337
15911
  }
15338
15912
  async readUserMessagesSince(sessionId, sinceMs, capPerSession) {
15339
- const all = await this.loadUserEntries(sessionId);
15340
15913
  const limit = Math.max(1, Math.floor(capPerSession));
15341
- const eligible = all.filter((entry) => entry.ts > sinceMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal);
15914
+ const messages = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, limit + 1, false);
15342
15915
  return {
15343
- messages: eligible.slice(0, limit),
15344
- truncated: eligible.length > limit
15916
+ messages: messages.slice(0, limit),
15917
+ truncated: messages.length > limit
15345
15918
  };
15346
15919
  }
15347
15920
  async readOldestMessageTimesSince(sessionIds, sinceMs) {
15348
15921
  const out = new Map;
15349
15922
  for (const sessionId of sessionIds) {
15350
- const oldest = (await this.loadUserEntries(sessionId)).filter((message) => message.ts > sinceMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal)[0];
15923
+ const [oldest] = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, 1, false);
15351
15924
  if (oldest)
15352
15925
  out.set(sessionId, oldest.ts);
15353
15926
  }
15354
15927
  return out;
15355
15928
  }
15356
15929
  async readUserMessagesBefore(sessionId, beforeMs, count) {
15357
- const all = await this.loadUserEntries(sessionId);
15358
- return all.filter((entry) => entry.ts <= beforeMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal).slice(-Math.max(1, Math.floor(count)));
15930
+ return this.selectUserEntries(sessionId, (ts) => ts <= beforeMs, Math.max(1, Math.floor(count)), true);
15359
15931
  }
15360
- async loadUserEntries(sessionId) {
15932
+ async selectUserEntries(sessionId, eligible, limit, newest) {
15361
15933
  const filePath = this.sessionPathById.get(sessionId);
15362
15934
  if (!filePath)
15363
15935
  return [];
15364
- const deps = await this.resolveDeps();
15365
- let entries;
15936
+ const kept = [];
15366
15937
  try {
15367
- entries = await deps.loadEntriesFromFile(filePath);
15938
+ const entries = this.deps.loadEntriesFromFile ? await this.deps.loadEntriesFromFile(filePath) : sessionEntries(filePath);
15939
+ let ordinal = 0;
15940
+ for (const entry of entries) {
15941
+ const row = normalizePiUserEntry(entry, sessionId, ++ordinal);
15942
+ if (!row || !eligible(row.ts))
15943
+ continue;
15944
+ kept.push(row);
15945
+ kept.sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal);
15946
+ if (kept.length > limit) {
15947
+ if (newest)
15948
+ kept.shift();
15949
+ else
15950
+ kept.pop();
15951
+ }
15952
+ }
15368
15953
  } catch {
15369
15954
  return [];
15370
15955
  }
15371
- if (!Array.isArray(entries))
15372
- return [];
15373
- return entries.map((entry, index) => normalizePiUserEntry(entry, sessionId, index + 1)).filter((entry) => entry !== null);
15374
- }
15375
- async resolveDeps() {
15376
- if (this.deps.listSessions && this.deps.loadEntriesFromFile) {
15377
- return {
15378
- listSessions: this.deps.listSessions,
15379
- loadEntriesFromFile: this.deps.loadEntriesFromFile
15380
- };
15381
- }
15382
- this.resolvedDefaultDeps ??= loadDefaultPiSessionApi();
15383
- return this.resolvedDefaultDeps;
15956
+ return kept;
15384
15957
  }
15385
15958
  }
15386
15959
  function normalizePiUserEntry(entry, sessionId, ordinal) {
@@ -15418,6 +15991,42 @@ function extractPiTextContent(content) {
15418
15991
  }
15419
15992
 
15420
15993
  // src/dreamer/index.ts
15994
+ var warnedUnknownModels = new Set;
15995
+ var modelAvailability = new WeakMap;
15996
+ function validatePiDreamerModels(tasks, registry, harness = "pi") {
15997
+ let resolved = modelAvailability.get(registry);
15998
+ if (!resolved) {
15999
+ resolved = new Map;
16000
+ modelAvailability.set(registry, resolved);
16001
+ }
16002
+ return tasks.map((task) => {
16003
+ const entries = [task.model, ...task.fallbackModels ?? []].filter((entry) => entry !== undefined);
16004
+ const valid = entries.filter((entry) => {
16005
+ const model = typeof entry === "string" ? entry : entry.model;
16006
+ if (harness === "omp" && model.startsWith("@"))
16007
+ return true;
16008
+ let available = resolved.get(model);
16009
+ if (available === undefined) {
16010
+ const separator = model.indexOf("/");
16011
+ available = separator > 0 && Boolean(registry.find(model.slice(0, separator), model.slice(separator + 1)));
16012
+ resolved.set(model, available);
16013
+ }
16014
+ if (available)
16015
+ return true;
16016
+ if (!warnedUnknownModels.has(model)) {
16017
+ log(`[dreamer] WARNING: dropping Pi model not found: ${model}`);
16018
+ warnedUnknownModels.add(model);
16019
+ }
16020
+ return false;
16021
+ });
16022
+ return {
16023
+ ...task,
16024
+ model: valid[0],
16025
+ fallbackModels: valid.slice(1),
16026
+ modelChainUnavailable: entries.length > 0 && valid.length === 0
16027
+ };
16028
+ });
16029
+ }
15421
16030
  var PI_DREAMER_PROJECTS = Symbol.for("magic-context.pi.dreamer-registered-projects");
15422
16031
  function getRegisteredProjects() {
15423
16032
  const globals = globalThis;
@@ -15446,7 +16055,8 @@ var sessionCounter = 0;
15446
16055
  var piSubagentRunnerFactory = () => new PiSubagentRunner;
15447
16056
  var startDreamScheduleTimerFn = startDreamScheduleTimer;
15448
16057
  function registerPiDreamerProject(opts) {
15449
- if (opts.config.disable === true) {
16058
+ const directory = resolve3(opts.projectDir);
16059
+ if (opts.config.disable === true || !isUsableProjectIdentity(opts.projectIdentity) || directory === parse(directory).root || directory === homedir2()) {
15450
16060
  return;
15451
16061
  }
15452
16062
  const existing = registeredProjects2.get(opts.projectIdentity);
@@ -15459,7 +16069,7 @@ function registerPiDreamerProject(opts) {
15459
16069
  callback(projectIdentity);
15460
16070
  };
15461
16071
  if (existing) {
15462
- if (existing.generation && existing.projectDir === opts.projectDir) {
16072
+ if (existing.generation && existing.projectDir === opts.projectDir && (!opts.modelRegistry || existing.modelRegistry)) {
15463
16073
  return;
15464
16074
  }
15465
16075
  existing.cleanup();
@@ -15469,15 +16079,17 @@ function registerPiDreamerProject(opts) {
15469
16079
  const client = createPiDreamerClient(opts, notifyOwnersOfAdjunctRefresh, () => {
15470
16080
  const current = registeredProjects2.get(opts.projectIdentity);
15471
16081
  return current?.generation === generation && current.owners.get(opts.registrationOwner)?.projectDir === opts.projectDir;
15472
- });
16082
+ }, () => opts.sampleDreamRun ? opts.sampleDreamRun().dreamerConfig?.maxTokens : opts.config.maxTokens);
15473
16083
  let cleanup;
15474
16084
  let cancelled = false;
16085
+ const modelRegistry = opts.modelRegistry;
15475
16086
  startDreamScheduleTimerFn({
15476
16087
  directory: opts.projectDir,
15477
16088
  projectIdentity: opts.projectIdentity,
15478
16089
  harness: opts.harness,
15479
16090
  client,
15480
16091
  dreamerConfig: opts.config,
16092
+ validateTaskModels: modelRegistry ? (tasks) => validatePiDreamerModels(tasks, modelRegistry, opts.harness) : undefined,
15481
16093
  sampleDreamRun: opts.sampleDreamRun,
15482
16094
  language: opts.language,
15483
16095
  gitCommitIndexing: opts.gitCommitIndexing,
@@ -15505,11 +16117,11 @@ function registerPiDreamerProject(opts) {
15505
16117
  const sampled = manualOpts.sampleDreamRun?.();
15506
16118
  const dreamerConfig = sampled?.dreamerConfig ?? manualOpts.config;
15507
16119
  const mural = sampled?.mural ?? manualOpts.mural;
15508
- const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir);
16120
+ const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir, () => dreamerConfig.maxTokens);
15509
16121
  const manualRun = runManualDream({
15510
16122
  db: manualOpts.db,
15511
16123
  projectIdentity: manualOpts.projectIdentity,
15512
- tasks: buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model),
16124
+ tasks: manualOpts.modelRegistry ? validatePiDreamerModels(buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model), manualOpts.modelRegistry, manualOpts.harness) : buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model),
15513
16125
  executor: createDreamTaskExecutor({
15514
16126
  client: manualClient,
15515
16127
  sessionDirectory: manualOpts.projectDir,
@@ -15542,7 +16154,8 @@ function registerPiDreamerProject(opts) {
15542
16154
  cleanup?.();
15543
16155
  },
15544
16156
  runManual,
15545
- projectDir: opts.projectDir
16157
+ projectDir: opts.projectDir,
16158
+ modelRegistry: opts.modelRegistry
15546
16159
  });
15547
16160
  }
15548
16161
  async function runPiDreamForProject(projectIdentity, task, registrationOwner) {
@@ -15584,7 +16197,7 @@ async function awaitInFlightDreamers(registrationOwner) {
15584
16197
  return;
15585
16198
  await Promise.allSettled(runs);
15586
16199
  }
15587
- function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true) {
16200
+ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true, getMaxOutputTokens = () => opts.config.maxTokens) {
15588
16201
  const runner = piSubagentRunnerFactory();
15589
16202
  const assertRegistrationOwnerActive = () => {
15590
16203
  if (!isRegistrationOwnerActive()) {
@@ -15625,6 +16238,7 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
15625
16238
  cwd: dreamSession.directory,
15626
16239
  signal: args.signal ?? undefined,
15627
16240
  thinkingLevel: extractBodyVariant(args),
16241
+ maxOutputTokens: getMaxOutputTokens(),
15628
16242
  accountingSessionId: opts.projectIdentity,
15629
16243
  accountingSubagent: "dreamer",
15630
16244
  accountingTask: accountingTaskFromTitle(dreamSession.title)
@@ -16054,7 +16668,7 @@ function formatEmbedStatusText(coverage, drain) {
16054
16668
  lines.push(`Synapse — ${formatSynapseLaneDescriptor(coverage.synapseDescriptor)}`);
16055
16669
  }
16056
16670
  lines.push(`This session: ${coverage.session.embedded} / ${coverage.session.total} compartments embedded`);
16057
- lines.push(`Project memories: ${coverage.memories.embedded} / ${coverage.memories.total} embedded`);
16671
+ lines.push(coverage.memories.memoryEnabled === false ? "Project memories: off (memory disabled)" : `Project memories: ${coverage.memories.embedded} / ${coverage.memories.total} embedded`);
16058
16672
  if (coverage.commits.gitEnabled) {
16059
16673
  lines.push(`Git commits: ${coverage.commits.embedded} / ${coverage.commits.total}`);
16060
16674
  } else {
@@ -16230,7 +16844,6 @@ No active Pi session is available.`,
16230
16844
  projectDir: deps.projectDir,
16231
16845
  projectIdentity: deps.projectIdentity
16232
16846
  };
16233
- const memoryEnabled = deps.resolveMemoryEnabled?.(ctx) ?? deps.memoryEnabled;
16234
16847
  const sub = args.trim().toLowerCase();
16235
16848
  if (sub === "pause") {
16236
16849
  embedPauseBySession.add(sessionId);
@@ -16247,16 +16860,6 @@ Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`,
16247
16860
  });
16248
16861
  return;
16249
16862
  }
16250
- if (memoryEnabled === false) {
16251
- sendStatus({
16252
- title: "/ctx-embed",
16253
- text: `## /ctx-embed
16254
-
16255
- Memory is disabled for this project, so there is no semantic embedding to backfill.`,
16256
- level: "info"
16257
- });
16258
- return;
16259
- }
16260
16863
  await ensureProjectRegisteredFromPiDirectory(project.projectDir, deps.db);
16261
16864
  if (sub === "start") {
16262
16865
  const { text, level } = await runEmbedDrain(deps.db, project.projectIdentity, sessionId, {
@@ -16289,13 +16892,13 @@ ${statusText}`,
16289
16892
  }
16290
16893
  });
16291
16894
  }
16292
- function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity, _notify) {
16895
+ function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity) {
16896
+ if (!isUsableProjectIdentity(projectIdentity))
16897
+ return;
16293
16898
  if (autoEmbedAttemptedBySession.has(sessionId))
16294
16899
  return;
16295
16900
  if (embedPauseBySession.has(sessionId))
16296
16901
  return;
16297
- if (deps.memoryEnabled === false)
16298
- return;
16299
16902
  autoEmbedAttemptedBySession.add(sessionId);
16300
16903
  (async () => {
16301
16904
  let drainReachedTerminal = false;
@@ -16331,7 +16934,7 @@ function executeFlush(db, sessionId) {
16331
16934
  removePendingOp(db, sessionId, op.tagId);
16332
16935
  dropped++;
16333
16936
  }
16334
- })();
16937
+ }).immediate();
16335
16938
  const parts = [];
16336
16939
  if (dropped > 0)
16337
16940
  parts.push(`${dropped} dropped`);
@@ -16440,6 +17043,135 @@ function commitPiCompactionModeRecord(db, sessionId, record) {
16440
17043
  // src/context-handler.ts
16441
17044
  import * as crypto3 from "node:crypto";
16442
17045
 
17046
+ // ../plugin/src/hooks/magic-context/event-resolvers.ts
17047
+ var DEFAULT_CONTEXT_LIMIT = 200000;
17048
+ var clampWarnSeen = new Set;
17049
+ function isFinitePositive(v) {
17050
+ return typeof v === "number" && Number.isFinite(v) && v > 0;
17051
+ }
17052
+ function* modelKeyLookupOrder(modelKey) {
17053
+ const slash = modelKey.indexOf("/");
17054
+ const providerRefs = slash >= 0 ? modelRefLookupOrder(modelKey) : [];
17055
+ let modelId = slash >= 0 ? modelKey.slice(slash + 1) : modelKey;
17056
+ while (modelId.length > 0) {
17057
+ for (const providerRef of providerRefs) {
17058
+ const providerSlash = providerRef.indexOf("/");
17059
+ yield `${providerRef.slice(0, providerSlash)}/${modelId}`;
17060
+ }
17061
+ yield modelId;
17062
+ const lastDash = modelId.lastIndexOf("-");
17063
+ if (lastDash <= 0)
17064
+ break;
17065
+ modelId = modelId.slice(0, lastDash);
17066
+ }
17067
+ }
17068
+ function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
17069
+ if (options?.tokensConfig && isFinitePositive(options.contextLimit)) {
17070
+ const contextLimit = options.contextLimit;
17071
+ const tokenMatch = resolveTokensMatchWithKey(options.tokensConfig, modelKey);
17072
+ if (tokenMatch && isFinitePositive(tokenMatch.value)) {
17073
+ const cap = contextLimit * (MAX_EXECUTE_THRESHOLD / 100);
17074
+ const effectiveTokens = Math.min(tokenMatch.value, cap);
17075
+ if (effectiveTokens < tokenMatch.value) {
17076
+ const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
17077
+ if (!clampWarnSeen.has(dedupeKey)) {
17078
+ clampWarnSeen.add(dedupeKey);
17079
+ const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (${MAX_EXECUTE_THRESHOLD}% of ${contextLimit}) for ${modelKey ?? "default"}`;
17080
+ if (options.sessionId) {
17081
+ sessionLog(options.sessionId, `WARN: ${msg}`);
17082
+ } else {
17083
+ log(`[magic-context] WARN: ${msg}`);
17084
+ }
17085
+ }
17086
+ }
17087
+ const percentage = effectiveTokens / contextLimit * 100;
17088
+ const detail = {
17089
+ percentage: Math.min(percentage, MAX_EXECUTE_THRESHOLD),
17090
+ mode: "tokens",
17091
+ absoluteTokens: Math.floor(effectiveTokens),
17092
+ matchedKey: tokenMatch.matchedKey
17093
+ };
17094
+ if (effectiveTokens < tokenMatch.value) {
17095
+ detail.clamped = true;
17096
+ detail.configuredValue = tokenMatch.value;
17097
+ }
17098
+ return detail;
17099
+ }
17100
+ }
17101
+ let resolved;
17102
+ let matchedKey;
17103
+ if (typeof config === "number") {
17104
+ resolved = config;
17105
+ } else if (modelKey) {
17106
+ let matched;
17107
+ for (const candidate of modelKeyLookupOrder(modelKey)) {
17108
+ if (typeof config[candidate] === "number") {
17109
+ matched = config[candidate];
17110
+ matchedKey = candidate;
17111
+ break;
17112
+ }
17113
+ }
17114
+ if (matched === undefined && typeof config.default === "number") {
17115
+ resolved = config.default;
17116
+ matchedKey = "default";
17117
+ } else {
17118
+ resolved = matched ?? fallback;
17119
+ }
17120
+ } else if (typeof config.default === "number") {
17121
+ resolved = config.default;
17122
+ matchedKey = "default";
17123
+ } else {
17124
+ resolved = fallback;
17125
+ }
17126
+ if (!Number.isFinite(resolved) || resolved < 0) {
17127
+ resolved = fallback;
17128
+ }
17129
+ const cappedPercentage = Math.min(resolved, MAX_EXECUTE_THRESHOLD);
17130
+ const percentageClamped = cappedPercentage < resolved;
17131
+ if (percentageClamped) {
17132
+ const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
17133
+ if (!clampWarnSeen.has(dedupeKey)) {
17134
+ clampWarnSeen.add(dedupeKey);
17135
+ const msg = `execute_threshold clamped ${resolved}% → ${MAX_EXECUTE_THRESHOLD}% for ${modelKey ?? "default"} (capped against the output-reserved safe window; 10% remains for mid-turn growth before the absolute 95% wall)`;
17136
+ if (options?.sessionId) {
17137
+ sessionLog(options.sessionId, `WARN: ${msg}`);
17138
+ } else {
17139
+ log(`[magic-context] WARN: ${msg}`);
17140
+ }
17141
+ }
17142
+ }
17143
+ const detail = {
17144
+ percentage: cappedPercentage,
17145
+ mode: "percentage",
17146
+ matchedKey
17147
+ };
17148
+ if (percentageClamped) {
17149
+ detail.clamped = true;
17150
+ detail.configuredValue = resolved;
17151
+ }
17152
+ return detail;
17153
+ }
17154
+ function resolveExecuteThreshold(config, modelKey, fallback, options) {
17155
+ return resolveExecuteThresholdDetail(config, modelKey, fallback, options).percentage;
17156
+ }
17157
+ function resolveTokensMatchWithKey(tokensConfig, modelKey) {
17158
+ if (!tokensConfig) {
17159
+ return;
17160
+ }
17161
+ if (modelKey) {
17162
+ for (const candidate of modelKeyLookupOrder(modelKey)) {
17163
+ const value = tokensConfig[candidate];
17164
+ if (typeof value === "number") {
17165
+ return { value, matchedKey: candidate };
17166
+ }
17167
+ }
17168
+ }
17169
+ if (typeof tokensConfig.default === "number") {
17170
+ return { value: tokensConfig.default, matchedKey: "default" };
17171
+ }
17172
+ return;
17173
+ }
17174
+
16443
17175
  // ../plugin/src/features/magic-context/scheduler.ts
16444
17176
  var TTL_PATTERN = /^(\d+)([smh])$/;
16445
17177
  var NUMERIC_PATTERN = /^\d+$/;
@@ -16630,7 +17362,7 @@ function createTagger() {
16630
17362
  db.transaction(() => {
16631
17363
  getUpsertCounterStatement(db).run(sessionId, next, getHarness());
16632
17364
  insertTag(db, sessionId, messageId, type, byteSize, next, reasoningByteSize, toolName, inputByteSize, toolOwnerMessageId, entryFingerprint, tokenCounts);
16633
- })();
17365
+ }).immediate();
16634
17366
  } catch (error) {
16635
17367
  if (!isUniqueConstraintError(error)) {
16636
17368
  throw error;
@@ -16866,7 +17598,8 @@ var canonicalReasons = new Set([
16866
17598
  "ttl_expiry",
16867
17599
  "epoch_change",
16868
17600
  "coverage_fold",
16869
- "profile_transition"
17601
+ "profile_transition",
17602
+ "host_compaction"
16870
17603
  ]);
16871
17604
  var piReasonAliases = {
16872
17605
  project_memory_change: "project_memory_epoch",
@@ -17100,6 +17833,72 @@ var RECENT_TOOL_SKELETON_WINDOW = 20;
17100
17833
  function buildReplacementContent(tagId) {
17101
17834
  return `[dropped §${tagId}§]`;
17102
17835
  }
17836
+ function applyNewToolDrop(target, options) {
17837
+ if (!target)
17838
+ return { result: "absent", mode: "full" };
17839
+ if (options.inWindow && hasSmallToolInput(target) || options.keepSkeleton === true || target.cannotRemove?.() === true) {
17840
+ const strip = target.hasAttachments?.() === true;
17841
+ return {
17842
+ result: (strip ? target.skeletonStripped?.() : target.skeletonReal?.()) ?? "absent",
17843
+ mode: strip ? "skeleton_stripped" : "skeleton_real"
17844
+ };
17845
+ }
17846
+ const result = target.drop?.() ?? "absent";
17847
+ if (result === "truncated" && target.hasAttachments?.() === true) {
17848
+ return { result: target.skeletonStripped?.() ?? "absent", mode: "skeleton_stripped" };
17849
+ }
17850
+ return { result, mode: result === "truncated" ? "skeleton_real" : "full" };
17851
+ }
17852
+ function hasSmallToolInput(target) {
17853
+ const bytes = target?.inputStringBytes?.() ?? null;
17854
+ return bytes !== null && bytes <= SKELETON_REAL_INPUT_MAX_BYTES;
17855
+ }
17856
+ var CACHE_LOSING_FOLD_REASONS = new Set([
17857
+ "model_change",
17858
+ "system_hash",
17859
+ "ttl_idle",
17860
+ "host_compaction"
17861
+ ]);
17862
+ function sameBytes(left, right) {
17863
+ if (left === null || right === null)
17864
+ return left === right;
17865
+ return Buffer.compare(Buffer.from(left), Buffer.from(right)) === 0;
17866
+ }
17867
+ function foldChangesServedPrefix(before, after) {
17868
+ if (!sameBytes(before.m0Bytes, after.m0Bytes))
17869
+ return true;
17870
+ if (!sameBytes(before.m1Bytes, after.m1Bytes))
17871
+ return true;
17872
+ return before.muralDataUrl !== after.muralDataUrl;
17873
+ }
17874
+ function foldBustsServedPrefix(reason, before, after) {
17875
+ return CACHE_LOSING_FOLD_REASONS.has(reason ?? "") || foldChangesServedPrefix(before, after);
17876
+ }
17877
+ function convertLegacyToolSkeletons(db, sessionId, targets) {
17878
+ const rows = db.prepare(`SELECT tag_number AS tagNumber, drop_mode AS dropMode
17879
+ FROM tags
17880
+ WHERE session_id = ? AND type = 'tool' AND status = 'dropped'
17881
+ AND drop_mode IN ('truncated', 'full')
17882
+ ORDER BY tag_number`).all(sessionId);
17883
+ const converted = new Map;
17884
+ for (const row of rows) {
17885
+ const target = targets.get(row.tagNumber);
17886
+ if (target?.canDrop?.() !== true)
17887
+ continue;
17888
+ const cannotRemove = target.cannotRemove?.() === true || target.requiresToolArcSkeleton === true || target.wouldStrandConversationEnd?.() === true;
17889
+ let mode;
17890
+ if (row.dropMode === "full") {
17891
+ if (target.cannotRemove?.() !== true)
17892
+ continue;
17893
+ mode = "skeleton_real";
17894
+ } else {
17895
+ mode = hasSmallToolInput(target) || cannotRemove ? "skeleton_real" : "full";
17896
+ }
17897
+ updateTagDropMode(db, sessionId, row.tagNumber, mode);
17898
+ converted.set(row.tagNumber, mode);
17899
+ }
17900
+ return converted;
17901
+ }
17103
17902
  function applyPendingOperations(sessionId, db, targets, protectedTagIds, preloadedTags, preloadedPendingOps, syntheticPendingOps = [], editMarkerTagIds = new Set, onTagReduced, onBatchComplete) {
17104
17903
  let didMutateMessage = false;
17105
17904
  let diagnostics;
@@ -17159,7 +17958,8 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
17159
17958
  let shouldPersistDrop = false;
17160
17959
  if (isToolTag) {
17161
17960
  if (editMarkerTagIds.has(pendingOp.tagId)) {
17162
- const markResult = target?.editMarker?.() ?? "absent";
17961
+ const strip = target?.hasAttachments?.() === true;
17962
+ const markResult = (strip ? target?.editMarkerStripped?.() : target?.editMarker?.()) ?? "absent";
17163
17963
  if (markResult === "incomplete" || markResult === "absent") {
17164
17964
  reject(`edit_marker_${markResult}`);
17165
17965
  continue;
@@ -17167,34 +17967,23 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
17167
17967
  didMutateMessage = true;
17168
17968
  operationMutated = true;
17169
17969
  onTagReduced?.({ tagNumber: pendingOp.tagId, mode: "edit_marker" });
17170
- updateTagDropMode(db, sessionId, pendingOp.tagId, "edit_marker");
17171
- shouldPersistDrop = true;
17172
- } else if (skeletonWindow.has(pendingOp.tagId)) {
17173
- const truncResult = target?.truncate?.() ?? "absent";
17174
- if (truncResult === "incomplete" || synthetic && truncResult !== "truncated") {
17175
- reject(`truncate_${truncResult}`);
17176
- continue;
17177
- }
17178
- if (truncResult === "truncated") {
17179
- didMutateMessage = true;
17180
- operationMutated = true;
17181
- onTagReduced?.({ tagNumber: pendingOp.tagId, mode: "truncated" });
17182
- } else {
17183
- reject(`truncate_${truncResult}`);
17184
- }
17185
- updateTagDropMode(db, sessionId, pendingOp.tagId, "truncated");
17970
+ updateTagDropMode(db, sessionId, pendingOp.tagId, strip ? "edit_marker_stripped" : "edit_marker");
17186
17971
  shouldPersistDrop = true;
17187
17972
  } else {
17188
- const dropResult = target?.drop?.() ?? "absent";
17973
+ const { result: dropResult, mode: appliedMode } = applyNewToolDrop(target, {
17974
+ inWindow: skeletonWindow.has(pendingOp.tagId)
17975
+ });
17189
17976
  if (dropResult === "incomplete" || synthetic && dropResult !== "removed" && dropResult !== "truncated") {
17190
17977
  reject(`drop_${dropResult}`);
17191
17978
  continue;
17192
17979
  }
17193
- const appliedMode = dropResult === "truncated" ? "truncated" : "full";
17194
17980
  if (dropResult === "removed" || dropResult === "truncated") {
17195
17981
  didMutateMessage = true;
17196
17982
  operationMutated = true;
17197
- onTagReduced?.({ tagNumber: pendingOp.tagId, mode: appliedMode });
17983
+ onTagReduced?.({
17984
+ tagNumber: pendingOp.tagId,
17985
+ mode: appliedMode === "full" ? "full" : "truncated"
17986
+ });
17198
17987
  } else {
17199
17988
  reject(`drop_${dropResult}`);
17200
17989
  }
@@ -17261,11 +18050,24 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
17261
18050
  if (tag.status === "dropped") {
17262
18051
  const target = targets.get(tag.tagNumber);
17263
18052
  if (tag.type === "tool") {
17264
- if (tag.dropMode === "edit_marker") {
18053
+ if (tag.dropMode === "edit_marker_stripped") {
18054
+ const result = target?.editMarkerStripped?.() ?? "absent";
18055
+ if (result === "truncated")
18056
+ didMutateMessage = true;
18057
+ } else if (tag.dropMode === "edit_marker") {
17265
18058
  const markResult = target?.editMarker?.() ?? "absent";
17266
18059
  if (markResult === "truncated") {
17267
18060
  didMutateMessage = true;
17268
18061
  }
18062
+ } else if (tag.dropMode === "skeleton_stripped") {
18063
+ const result = target?.skeletonStripped?.() ?? "absent";
18064
+ if (result === "truncated")
18065
+ didMutateMessage = true;
18066
+ } else if (tag.dropMode === "skeleton_real") {
18067
+ const result = target?.skeletonReal?.() ?? "absent";
18068
+ if (result === "truncated") {
18069
+ didMutateMessage = true;
18070
+ }
17269
18071
  } else if (tag.dropMode === "truncated") {
17270
18072
  const truncResult = target?.truncate?.() ?? "absent";
17271
18073
  if (truncResult === "truncated") {
@@ -17611,7 +18413,7 @@ function applyCavemanCleanup(sessionId, db, targets, tags, config) {
17611
18413
  else if (targetDepth === DEPTH_ULTRA)
17612
18414
  result.compressedToUltra += 1;
17613
18415
  }
17614
- })();
18416
+ }).immediate();
17615
18417
  const total = result.compressedToLite + result.compressedToFull + result.compressedToUltra;
17616
18418
  if (total > 0) {
17617
18419
  sessionLog(sessionId, `caveman cleanup: compressed ${total} text tags (lite=${result.compressedToLite}, full=${result.compressedToFull}, ultra=${result.compressedToUltra})`);
@@ -19533,6 +20335,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19533
20335
  let any = false;
19534
20336
  for (const occ of occurrences) {
19535
20337
  if (occ.kind === "tool_use" && occ.part.setToolInput) {
20338
+ occ.originalInput ??= occ.part.getToolInput?.() ?? undefined;
19536
20339
  if (occ.part.setToolInput(droppedInputMarker(tagId)))
19537
20340
  any = true;
19538
20341
  } else if (setToolContentOrText(occ.part, sentinel)) {
@@ -19541,6 +20344,28 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19541
20344
  }
19542
20345
  return any ? "truncated" : "absent";
19543
20346
  };
20347
+ const complete = () => occurrences.some((occ) => occ.kind === "tool_use") && occurrences.some((occ) => occ.kind === "tool_result");
20348
+ const skeletonReal = () => {
20349
+ if (!complete())
20350
+ return "incomplete";
20351
+ const sentinel = `[dropped §${tagId}§]`;
20352
+ let any = false;
20353
+ for (const occ of occurrences) {
20354
+ if (occ.kind === "tool_use") {
20355
+ if (occ.originalInput && occ.part.setToolInput?.(occ.originalInput))
20356
+ any = true;
20357
+ } else if (setToolContentOrText(occ.part, sentinel)) {
20358
+ any = true;
20359
+ }
20360
+ }
20361
+ return any ? "truncated" : "absent";
20362
+ };
20363
+ const cannotRemove = () => {
20364
+ if (requiresToolArcSkeleton)
20365
+ return true;
20366
+ const authorization = occurrences.map((occ) => occ.part.canRemove?.());
20367
+ return !occurrences.every((occ, index) => occ.part.remove && authorization[index] !== false);
20368
+ };
19544
20369
  return {
19545
20370
  measureReclaim(skeleton) {
19546
20371
  let beforeTools = 0;
@@ -19551,7 +20376,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19551
20376
  const before = occ.kind === "tool_use" ? JSON.stringify(occ.part.getToolInput?.() ?? {}) : occ.part.getText() ?? "";
19552
20377
  beforeTools += estimateTokens(before);
19553
20378
  if (skeleton || !removable) {
19554
- const after = occ.kind === "tool_use" ? JSON.stringify(droppedInputMarker(tagId)) : `[dropped §${tagId}§]`;
20379
+ const after = occ.kind === "tool_use" ? before : `[dropped §${tagId}§]`;
19555
20380
  afterTools += estimateTokens(after);
19556
20381
  }
19557
20382
  }
@@ -19580,8 +20405,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19580
20405
  return occurrences[0]?.part.getText() ?? null;
19581
20406
  },
19582
20407
  drop() {
19583
- const complete = occurrences.some((occ) => occ.kind === "tool_use") && occurrences.some((occ) => occ.kind === "tool_result");
19584
- if (!complete)
20408
+ if (!complete())
19585
20409
  return "incomplete";
19586
20410
  if (!requiresToolArcSkeleton) {
19587
20411
  const authorization = occurrences.map((occ) => occ.part.canRemove?.());
@@ -19589,7 +20413,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19589
20413
  return "incomplete";
19590
20414
  if (occurrences.every((occ, index) => occ.part.remove && authorization[index] !== false)) {
19591
20415
  if (conversationEnd?.wouldStrand(occurrences))
19592
- return truncate();
20416
+ return skeletonReal();
19593
20417
  let removed = false;
19594
20418
  for (const occ of occurrences)
19595
20419
  removed = occ.part.remove?.() || removed;
@@ -19607,6 +20431,19 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19607
20431
  return any ? "removed" : "absent";
19608
20432
  },
19609
20433
  truncate,
20434
+ skeletonReal,
20435
+ cannotRemove,
20436
+ inputStringBytes() {
20437
+ for (const occ of occurrences) {
20438
+ if (occ.kind !== "tool_use")
20439
+ continue;
20440
+ return toolInputStringBytes(occ.originalInput ?? occ.part.getToolInput?.() ?? null);
20441
+ }
20442
+ return null;
20443
+ },
20444
+ wouldStrandConversationEnd() {
20445
+ return conversationEnd?.wouldStrand(occurrences) ?? false;
20446
+ },
19610
20447
  editMarker() {
19611
20448
  const sentinel = `[dropped §${tagId}§]`;
19612
20449
  let any = false;
@@ -19625,9 +20462,7 @@ function buildAggregateTarget(tagId, occurrences, requiresToolArcSkeleton, conve
19625
20462
  }
19626
20463
  return any ? "truncated" : "absent";
19627
20464
  },
19628
- canDrop() {
19629
- return occurrences.some((occ) => occ.kind === "tool_use") && occurrences.some((occ) => occ.kind === "tool_result");
19630
- },
20465
+ canDrop: complete,
19631
20466
  requiresToolArcSkeleton,
19632
20467
  readInput() {
19633
20468
  for (const occ of occurrences) {
@@ -19673,6 +20508,15 @@ function buildToolTarget(part, message, tagId) {
19673
20508
  const changed = part.setToolInput ? part.setToolInput(droppedInputMarker(tagId)) : setToolContentOrText(part, `[dropped §${tagId}§]`);
19674
20509
  return changed ? "truncated" : "absent";
19675
20510
  },
20511
+ skeletonReal() {
20512
+ if (part.setToolInput)
20513
+ return "truncated";
20514
+ return setToolContentOrText(part, `[dropped §${tagId}§]`) ? "truncated" : "absent";
20515
+ },
20516
+ cannotRemove: () => part.setToolInput !== undefined,
20517
+ inputStringBytes() {
20518
+ return part.setToolInput ? toolInputStringBytes(part.getToolInput?.() ?? null) : 0;
20519
+ },
19676
20520
  message: {
19677
20521
  info: { id: message.info.id, role: message.info.role },
19678
20522
  parts: []
@@ -19917,6 +20761,8 @@ async function runAutoSearchHintForPi(args) {
19917
20761
  }
19918
20762
  if (found.index !== messages.length - 1)
19919
20763
  return messages;
20764
+ if (!isUsableProjectIdentity(options.projectPath))
20765
+ return messages;
19920
20766
  await args.ensureProjectRegistered?.();
19921
20767
  const writeNoHintAndReconcile = (reason) => {
19922
20768
  const outcome = appendAutoSearchHintDecision(db, sessionId, {
@@ -19946,7 +20792,7 @@ async function runAutoSearchHintForPi(args) {
19946
20792
  try {
19947
20793
  const snapshot = getProjectEmbeddingSnapshot(options.projectPath);
19948
20794
  const memoryEnabled = snapshot?.features.memoryEnabled ?? true;
19949
- const embeddingEnabled = snapshot ? snapshot.enabled || snapshot.gitCommitEnabled : true;
20795
+ const embeddingEnabled = snapshot ? snapshot.historyEnabled : true;
19950
20796
  const gitCommitsEnabled = snapshot?.gitCommitEnabled ?? false;
19951
20797
  const searchOptions = {
19952
20798
  limit: 10,
@@ -20010,6 +20856,8 @@ import * as crypto2 from "node:crypto";
20010
20856
  async function embedAndStoreCompartmentChunks(db, sessionId, projectPath, compartments) {
20011
20857
  if (compartments.length === 0)
20012
20858
  return;
20859
+ if (getProjectChunkEmbeddingModelId(projectPath) === "off")
20860
+ return;
20013
20861
  const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectPath);
20014
20862
  for (const compartment of compartments) {
20015
20863
  try {
@@ -20166,7 +21014,7 @@ function persistFilteredNoise(db, sessionId, chunk, eligibleEnd) {
20166
21014
  }
20167
21015
  ]);
20168
21016
  return true;
20169
- })();
21017
+ }).immediate();
20170
21018
  if (saved)
20171
21019
  sessionLog(sessionId, `historian skipped ordinals ${start}-${eligibleEnd - 1}: all ${rows.length} raw rows excluded by chunk filters (no-content boundary marker)`);
20172
21020
  return saved;
@@ -22763,8 +23611,7 @@ ${chunkText}`,
22763
23611
  const discardedLast = newCompartments.length < emittedCompartments.length;
22764
23612
  const weakLookaheadFinalCompartment = forceKeepLastCompartmentForChunk;
22765
23613
  const skipUnanchoredPromotion = discardedLast || weakLookaheadFinalCompartment;
22766
- const embeddingActive = memoryEnabled !== false;
22767
- const promotionActive = embeddingActive && autoPromote !== false;
23614
+ const promotionActive = memoryEnabled !== false && autoPromote !== false;
22768
23615
  const publishableEvents = (validatedPass.events ?? []).filter((e) => {
22769
23616
  if (typeof e.atCompartment !== "number")
22770
23617
  return !weakLookaheadFinalCompartment;
@@ -22897,7 +23744,7 @@ ${chunkText}`,
22897
23744
  sessionLog(sessionId, "failed to store primer candidates:", error);
22898
23745
  }
22899
23746
  }
22900
- if (embeddingActive) {
23747
+ if (newCompartments.length > 0) {
22901
23748
  const chunksToEmbed = newCompartments.map((c, i) => ({
22902
23749
  id: persistedIds[i],
22903
23750
  startMessage: c.startMessage,
@@ -23280,7 +24127,7 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23280
24127
  const candidateTags = tags.filter((t) => t.status === "active" && t.type === "tool" && targets.get(t.tagNumber)?.canDrop?.());
23281
24128
  const recentTags = new Set(candidateTags.slice().sort((a, b) => b.tagNumber - a.tagNumber).slice(0, 20).map((tag) => tag.tagNumber));
23282
24129
  const calibration = sessionDecisionCalibration(db, sessionId);
23283
- const activeTags = tags.filter((t) => t.status === "active").map((tag) => measureEmergencyTag(tag, targets.get(tag.tagNumber), calibration, targets.get(tag.tagNumber)?.requiresToolArcSkeleton === true || (emergency.usagePercentage ?? 0) < 95 && recentTags.has(tag.tagNumber)));
24130
+ const activeTags = tags.filter((t) => t.status === "active").map((tag) => measureEmergencyTag(tag, targets.get(tag.tagNumber), calibration, targets.get(tag.tagNumber)?.requiresToolArcSkeleton === true || (emergency.usagePercentage ?? 0) < 95 && recentTags.has(tag.tagNumber) && hasSmallToolInput(targets.get(tag.tagNumber))));
23284
24131
  const byTag = new Map(activeTags.map((tag) => [tag.tagNumber, tag]));
23285
24132
  const droppableTags = candidateTags.flatMap((tag) => {
23286
24133
  const measured = byTag.get(tag.tagNumber);
@@ -23309,18 +24156,18 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23309
24156
  continue;
23310
24157
  const target = targets.get(tag.tagNumber);
23311
24158
  const recent = (emergency.usagePercentage ?? 0) < 95 && newestEmergencyTags.has(tag.tagNumber);
23312
- const reasoningSafeSkeleton = target?.requiresToolArcSkeleton === true;
23313
- const skeleton = recent || reasoningSafeSkeleton;
23314
- const result = reasoningSafeSkeleton ? target?.truncate?.() ?? "absent" : recent ? target?.truncate?.() ?? target?.drop?.() ?? "absent" : target?.drop?.() ?? "absent";
24159
+ const { result, mode } = applyNewToolDrop(target, {
24160
+ inWindow: recent,
24161
+ keepSkeleton: target?.requiresToolArcSkeleton === true
24162
+ });
23315
24163
  if (result === "removed" || result === "truncated") {
23316
- const appliedMode = skeleton || result === "truncated" ? "truncated" : "full";
23317
24164
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
23318
- updateTagDropMode(db, sessionId, tag.tagNumber, appliedMode);
24165
+ updateTagDropMode(db, sessionId, tag.tagNumber, mode);
23319
24166
  droppedTools++;
23320
24167
  emergencyDroppedTools++;
23321
24168
  droppedTokenReductions.push({
23322
24169
  tagNumber: tag.tagNumber,
23323
- mode: appliedMode
24170
+ mode: mode === "full" ? "full" : "truncated"
23324
24171
  });
23325
24172
  }
23326
24173
  }
@@ -23344,10 +24191,10 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23344
24191
  if (!matched)
23345
24192
  continue;
23346
24193
  const target = targets.get(tag.tagNumber);
23347
- const result = target?.drop?.() ?? "absent";
24194
+ const { result, mode } = applyNewToolDrop(target, { inWindow: false });
23348
24195
  if (result === "incomplete")
23349
24196
  continue;
23350
- updateTagDropMode(db, sessionId, tag.tagNumber, result === "truncated" ? "truncated" : "full");
24197
+ updateTagDropMode(db, sessionId, tag.tagNumber, mode);
23351
24198
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
23352
24199
  if (result === "removed" || result === "truncated") {
23353
24200
  droppedStaleReduceCalls++;
@@ -23428,17 +24275,18 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config, pre
23428
24275
  for (let i = 0;i < group.length - 1; i++) {
23429
24276
  const tag = group[i];
23430
24277
  const target = targets.get(tag.tagNumber);
23431
- const result = target?.drop?.() ?? "absent";
24278
+ const { result, mode } = applyNewToolDrop(target, {
24279
+ inWindow: false
24280
+ });
23432
24281
  if (result === "incomplete")
23433
24282
  continue;
23434
- const appliedMode = result === "truncated" ? "truncated" : "full";
23435
- updateTagDropMode(db, sessionId, tag.tagNumber, appliedMode);
24283
+ updateTagDropMode(db, sessionId, tag.tagNumber, mode);
23436
24284
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
23437
24285
  if (result === "removed" || result === "truncated") {
23438
24286
  deduplicatedTools++;
23439
24287
  droppedTokenReductions.push({
23440
24288
  tagNumber: tag.tagNumber,
23441
- mode: appliedMode
24289
+ mode: mode === "full" ? "full" : "truncated"
23442
24290
  });
23443
24291
  }
23444
24292
  }
@@ -24739,9 +25587,7 @@ ${decayed}
24739
25587
  if (memoryBlock)
24740
25588
  sections.push(memoryBlock);
24741
25589
  if (mural?.enabled && mural.supportsVision && mural.dataUrl) {
24742
- sections.push(`<memory-mural>
24743
- The project memory mural image follows.
24744
- </memory-mural>`);
25590
+ sections.push(MEMORY_MURAL_BLOCK);
24745
25591
  }
24746
25592
  return sections.join(`
24747
25593
 
@@ -26204,7 +27050,7 @@ function messageRole(message) {
26204
27050
  const role = recordValue(message, "role");
26205
27051
  return typeof role === "string" ? role : undefined;
26206
27052
  }
26207
- function messageId(message) {
27053
+ function messageId2(message) {
26208
27054
  const id = recordValue(messageInfo(message), "id") ?? recordValue(message, "id");
26209
27055
  return typeof id === "string" && id.length > 0 ? id : null;
26210
27056
  }
@@ -26337,7 +27183,7 @@ function validateLkgSeam(prefix, tail, providerKey) {
26337
27183
  const calls = new Set;
26338
27184
  const results = new Set;
26339
27185
  for (const message of all) {
26340
- const id = messageId(message);
27186
+ const id = messageId2(message);
26341
27187
  if (id !== null) {
26342
27188
  if (ids.has(id))
26343
27189
  return false;
@@ -26880,6 +27726,56 @@ function resolvePiPressureSnapshot(args) {
26880
27726
  ...validInferredLimit ? { contextLimit: inferredLimit } : {}
26881
27727
  };
26882
27728
  }
27729
+ var estimateSetAsideLogged = new Set;
27730
+ function notePiUsageReadingUsed(sessionId) {
27731
+ estimateSetAsideLogged.delete(sessionId);
27732
+ }
27733
+ function entryUsageTokens(usage) {
27734
+ if (!usage || typeof usage !== "object")
27735
+ return 0;
27736
+ const u = usage;
27737
+ const n = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
27738
+ return n(u.totalTokens) || n(u.input) + n(u.output) + n(u.cacheRead) + n(u.cacheWrite);
27739
+ }
27740
+ function isPiLiveUsageRawBranchEstimate(branchEntries) {
27741
+ if (!branchEntries)
27742
+ return false;
27743
+ for (let index = branchEntries.length - 1;index >= 0; index -= 1) {
27744
+ const entry = branchEntries[index];
27745
+ if (!entry || typeof entry !== "object")
27746
+ continue;
27747
+ if (entry.type === "context_edit" || entry.type === "compaction")
27748
+ return true;
27749
+ if (entry.type !== "message")
27750
+ continue;
27751
+ const message = entry.message;
27752
+ if (message?.role === "assistant" && message.stopReason !== "aborted" && message.stopReason !== "error" && entryUsageTokens(message.usage) > 0) {
27753
+ return false;
27754
+ }
27755
+ }
27756
+ return false;
27757
+ }
27758
+ function noteRawBranchEstimateSetAside(sessionId, reading, source) {
27759
+ if (estimateSetAsideLogged.has(sessionId))
27760
+ return;
27761
+ estimateSetAsideLogged.add(sessionId);
27762
+ 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`);
27763
+ }
27764
+ function resolvePiPressureSnapshotWithEstimateGuard(args) {
27765
+ const live = typeof args.liveInputTokens === "number" && Number.isFinite(args.liveInputTokens) ? args.liveInputTokens : 0;
27766
+ const setAsideEstimate = args.liveIsRawBranchEstimate === true && live > 0;
27767
+ const snapshot = resolvePiPressureSnapshot(setAsideEstimate ? {
27768
+ ...args,
27769
+ liveInputTokens: undefined,
27770
+ ...args.persistedFromLive ? { persistedInputTokens: 0, persistedPercentage: 0 } : {}
27771
+ } : args);
27772
+ if (setAsideEstimate) {
27773
+ noteRawBranchEstimateSetAside(args.sessionId, live, args.source);
27774
+ } else if (snapshot.inputTokens > 0) {
27775
+ notePiUsageReadingUsed(args.sessionId);
27776
+ }
27777
+ return snapshot;
27778
+ }
26883
27779
  function formatPiPressureForLog(snapshot) {
26884
27780
  return `usage=${snapshot.percentage.toFixed(1)}% (${snapshot.inputTokens} tokens, limit=${snapshot.contextLimit ?? "?"})`;
26885
27781
  }
@@ -27336,12 +28232,12 @@ function handlePiProviderFailure(args) {
27336
28232
  const model = typeof message.model === "string" ? message.model : undefined;
27337
28233
  const binding = detectThinkingBindingMismatch(message.errorMessage);
27338
28234
  if (binding.isBindingMismatch) {
27339
- const enabled = args.thinkingBindingRecoveryEnabled !== false && !args.compactionOff && isFable51ThinkingBindingModel(provider, model);
28235
+ const enabled = args.thinkingBindingRecoveryEnabled !== false && !args.compactionOff && isPrefixBoundThinkingModel(provider, model);
27340
28236
  if (enabled) {
27341
28237
  armThinkingBindingRecovery(args.db, args.sessionId);
27342
28238
  clearPiLkgSessionState(args.sessionId);
27343
28239
  dropSlot(args.sessionId, "thinking-binding-recovery-arm");
27344
- reportBindingRecovery(args.sessionId, args.report, `Fable thinking-binding recovery armed from message_end target=${NEWEST_REASONING_BEARING_ASSISTANT}`);
28240
+ reportBindingRecovery(args.sessionId, args.report, `thinking-binding recovery armed from message_end (provider paths: failing=${binding.failingBlockPath ?? "?"} firstChanged=${binding.firstChangedPath ?? "?"})`);
27345
28241
  }
27346
28242
  return { kind: "thinking_binding", armed: enabled };
27347
28243
  }
@@ -27390,36 +28286,25 @@ function stripThinkingParts(message) {
27390
28286
  function applyPiThinkingBindingRecovery(args) {
27391
28287
  if (args.provider?.toLowerCase() !== "anthropic")
27392
28288
  return null;
27393
- const frozenEntryIds = new Set;
27394
- for (const frozenId of getMergedReasoningStrippedIds(args.db, args.sessionId)) {
27395
- if (!frozenId.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX))
27396
- continue;
27397
- const entryId = frozenId.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length);
27398
- if (entryId.length > 0)
27399
- frozenEntryIds.add(entryId);
27400
- }
27401
- const flagTarget = isFable51ThinkingBindingModel(args.provider, args.model) ? getThinkingBindingRecoveryTarget(args.db, args.sessionId) : null;
28289
+ const frozenEntryIds = frozenBindingEntryIds(args.db, args.sessionId);
28290
+ const flagTarget = isPrefixBoundThinkingModel(args.provider, args.model) ? getThinkingBindingRecoveryTarget(args.db, args.sessionId) : null;
27402
28291
  let applied = null;
27403
28292
  if (flagTarget) {
27404
- let messageIndex = -1;
27405
- if (flagTarget === NEWEST_REASONING_BEARING_ASSISTANT) {
27406
- for (let index = args.messages.length - 1;index >= 0; index -= 1) {
27407
- if (hasThinkingPart(args.messages[index]) && args.entryIds[index]) {
27408
- messageIndex = index;
27409
- break;
27410
- }
27411
- }
27412
- } else {
27413
- messageIndex = args.entryIds.findIndex((entryId, index) => entryId === flagTarget && hasThinkingPart(args.messages[index]));
27414
- }
27415
- const entryId = messageIndex >= 0 ? args.entryIds[messageIndex] : undefined;
27416
- if (entryId) {
27417
- const frozenId = thinkingBindingRecoveryFrozenId(entryId);
27418
- if (frozenEntryIds.has(entryId) || addMergedReasoningStrippedIds(args.db, args.sessionId, [frozenId])) {
27419
- frozenEntryIds.add(entryId);
27420
- applied = { flagTarget, entryId };
27421
- reportBindingRecovery(args.sessionId, args.report, `Fable thinking-binding recovery consumed on context pass target=${flagTarget} entry=${entryId}`);
27422
- }
28293
+ const entryIds = new Set;
28294
+ for (let index = 0;index < args.messages.length; index += 1) {
28295
+ const entryId = args.entryIds[index];
28296
+ if (entryId && hasThinkingPart(args.messages[index]))
28297
+ entryIds.add(entryId);
28298
+ }
28299
+ const newEntryIds = [...entryIds].filter((id) => !frozenEntryIds.has(id));
28300
+ if (newEntryIds.length === 0 || addMergedReasoningStrippedIds(args.db, args.sessionId, [
28301
+ ...newEntryIds.map(thinkingBindingRecoveryFrozenId),
28302
+ ...args.endOfPassOrder ? [THINKING_BINDING_STRIP_ORDER_END_MARKER] : []
28303
+ ])) {
28304
+ for (const id of newEntryIds)
28305
+ frozenEntryIds.add(id);
28306
+ applied = { flagTarget, entryIds: [...entryIds] };
28307
+ reportBindingRecovery(args.sessionId, args.report, `thinking-binding recovery consumed on context pass target=${flagTarget} entries=${applied.entryIds.length} [${applied.entryIds.join(",")}]`);
27423
28308
  }
27424
28309
  }
27425
28310
  for (let index = 0;index < args.messages.length; index += 1) {
@@ -27429,6 +28314,76 @@ function applyPiThinkingBindingRecovery(args) {
27429
28314
  }
27430
28315
  return applied;
27431
28316
  }
28317
+ function applyPiProactiveThinkingStrip(args) {
28318
+ if (!args.cacheBustingPass)
28319
+ return null;
28320
+ if (!isPrefixBoundThinkingModel(args.provider, args.model))
28321
+ return null;
28322
+ const entryIds = [];
28323
+ const indices = [];
28324
+ for (let index = 0;index < args.messages.length; index += 1) {
28325
+ const entryId = args.entryIds[index];
28326
+ if (!entryId || !hasThinkingPart(args.messages[index]))
28327
+ continue;
28328
+ entryIds.push(entryId);
28329
+ indices.push(index);
28330
+ }
28331
+ if (entryIds.length === 0)
28332
+ return null;
28333
+ let persisted = false;
28334
+ try {
28335
+ persisted = addMergedReasoningStrippedIds(args.db, args.sessionId, [
28336
+ ...entryIds.map(thinkingBindingRecoveryFrozenId),
28337
+ THINKING_BINDING_STRIP_ORDER_END_MARKER
28338
+ ]);
28339
+ } catch {
28340
+ persisted = false;
28341
+ }
28342
+ if (!persisted) {
28343
+ reportBindingRecovery(args.sessionId, args.report, "proactive thinking strip: persistence failed; serving the thinking unchanged");
28344
+ return null;
28345
+ }
28346
+ for (const index of indices)
28347
+ stripThinkingParts(args.messages[index]);
28348
+ reportBindingRecovery(args.sessionId, args.report, `proactive thinking strip: busting pass froze thinking of ${entryIds.length} entr${entryIds.length === 1 ? "y" : "ies"} [${entryIds.join(",")}]`);
28349
+ return { entryIds };
28350
+ }
28351
+ function frozenBindingEntryIds(db, sessionId) {
28352
+ const ids = new Set;
28353
+ for (const entry of getMergedReasoningStrippedIds(db, sessionId)) {
28354
+ if (!entry.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX))
28355
+ continue;
28356
+ const entryId = entry.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length);
28357
+ if (entryId.length > 0)
28358
+ ids.add(entryId);
28359
+ }
28360
+ return ids;
28361
+ }
28362
+ function resolvePiBindingStripOrder(args) {
28363
+ const ledger = getMergedReasoningStrippedIds(args.db, args.sessionId);
28364
+ if (ledger.has(THINKING_BINDING_STRIP_ORDER_END_MARKER))
28365
+ return "end";
28366
+ const frozen = frozenBindingEntryIds(args.db, args.sessionId);
28367
+ if (frozen.size === 0)
28368
+ return "end";
28369
+ let onBranch = false;
28370
+ let carriesThinking = false;
28371
+ for (let index = 0;index < args.messages.length; index += 1) {
28372
+ const entryId = args.entryIds[index];
28373
+ if (!entryId || !frozen.has(entryId))
28374
+ continue;
28375
+ onBranch = true;
28376
+ if (hasThinkingPart(args.messages[index]))
28377
+ carriesThinking = true;
28378
+ }
28379
+ const mayChangeOrder = onBranch && carriesThinking && args.bustPermittedAtStart;
28380
+ if (mayChangeOrder && addMergedReasoningStrippedIds(args.db, args.sessionId, [
28381
+ THINKING_BINDING_STRIP_ORDER_END_MARKER
28382
+ ])) {
28383
+ return "end";
28384
+ }
28385
+ return "start";
28386
+ }
27432
28387
 
27433
28388
  // src/reasoning-replay-pi.ts
27434
28389
  var INLINE_THINKING_PATTERNS = [
@@ -27946,144 +28901,6 @@ function stripPiProcessedImages(args) {
27946
28901
 
27947
28902
  // src/system-prompt.ts
27948
28903
  import { createHash as createHash13 } from "node:crypto";
27949
-
27950
- // ../plugin/src/agents/magic-context-prompt.ts
27951
- var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
27952
-
27953
- Most AI sessions are disposable: one session per task, discarded when it's done — like hiring a developer for a single bug fix and letting them go the moment they finish. Magic Context changes this completely. This session is a durable working relationship: you carry the full history and accumulated knowledge of this project, and you continue across many tasks, bugs, and features — with memory that persists across restarts. This session may continue for weeks, months, or even years.
27954
-
27955
- Because of this:
27956
- - **Your context is continuously managed and effectively unbounded.** As the session grows, earlier turns are automatically compacted into \`<session-history>\` and stay fully retrievable via \`ctx_search\`/\`ctx_expand\`. You will not hit a context wall and you will not lose continuity mid-task.
27957
- - **High context usage is normal and fully handled** — never a reason to wrap up, cut scope, rush, or defer work. Keep going at full depth.
27958
- - **Finishing a task does not end the session.** Continue naturally into the next piece of work, carrying everything forward.
27959
- - **There are no compaction pauses.** Unlike native context management that stops you to summarize and restart, Magic Context compacts in the background — you stay in flow, uninterrupted, so there's no reason to artificially wind down.`;
27960
- var PARTNER_FRAME_CLOSER_REDUCE = `
27961
- Reduction prompts are routine housekeeping to keep the session fast and cheap — act on them as light maintenance, never as scarcity warnings. Keep individual operations efficient, but never let context size change *what* work you take on or *how thoroughly* you do it.`;
27962
- var PARTNER_FRAME_CLOSER_NO_REDUCE = `
27963
- Context is managed for you entirely automatically — there's nothing to prune and no warnings to act on. Stay reasonably concise per operation, and never let context size change *what* work you take on or *how thoroughly* you do it.`;
27964
- var PARTNER_FRAME_CLOSER_REDUCE_LIGHT = `
27965
- When ctx_reduce is available, use it only as routine housekeeping; never cut task scope or depth because context is large.`;
27966
- var PARTNER_FRAME_CLOSER_NO_REDUCE_LIGHT = `
27967
- When ctx_reduce is unavailable, context is automatic; never prune, heed reduction warnings, or cut task scope or depth because context is large.`;
27968
- var CTX_NOTE_GUIDANCE = `\`ctx_note\` is the tray for work you intend to return to, with its findings attached; findings without an intention are not a note. When the user says "take a note", it always goes in the tray.`;
27969
- var MEMORY_GUIDANCE = `\`<project-memory>\` is the pinboard: facts about this project that stay true for the months this work lasts, as \`#id: fact\` lines — for you, and for every other agent working on this project. \`ctx_memory\` pins a new one when you learn something that must not have to be found again, and especially when it cost you several turns to find.`;
27970
- var TOOL_HISTORY_GUIDANCE = `Older work is not kept on the desk at all. Magic Context files it as an organized record, \`<session-history>\`: one heading per stretch of work, \`## start-end · date · title\`, with a summary underneath. Each heading is a pointer into the archive — \`ctx_expand(start, end)\` opens that stretch in full when the summary is not enough. Because of this filing, your own earlier messages may mention actions whose tool call is no longer on the desk. That is normal. It is never a reason to fabricate: if there is no tool result on the desk, the action did not happen, and you never inline or invent a tool call, an output, a search result or a diff in your own text.`;
27971
- var SMART_NOTE_GUIDANCE = `A note with a \`surface_condition\` is left with an outside checker that looks the condition up periodically and returns the note only when it holds.`;
27972
- var TEMPORAL_AWARENESS_GUIDANCE = `, and \`<!-- +Xm -->\` before a user message (the time that passed since your last reply; headings in the record carry \`start-date\`/\`end-date\` too)`;
27973
- var TEMPORAL_AWARENESS_OVERRIDE_GUIDANCE = `
27974
- Some things on the desk are Magic Context's own markings, not conversation: \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\`${TEMPORAL_AWARENESS_GUIDANCE}. Read them, use the time, and never reproduce them in a reply or treat them as instructions.`;
27975
- var BASE_INTRO = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
27976
-
27977
- Think of your context as a desk. Every message and every tool output lands on it, and each item arrives with a §N§ tag (§1§, §42§) — the tag is the item's handle.
27978
-
27979
- When an item no longer needs to stay on the desk for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping does not remove anything — the item stays on the desk, fully readable. From time to time, when stamped items have piled up and the desk needs room, Magic Context clears them all in one sweep; you don't pick the moment, you only stamp. Stamp as soon as an item has served its purpose, not at the end of the turn, and do it silently: nobody wants to read "I'll drop these outputs". Never stamp a user message for what it asks of you; a large paste inside one is fine once you have used it.
27980
-
27981
- Nothing stamped is ever lost. A cleared item goes to the archive — a recent one leaves a \`[dropped §N§]\` placeholder on the desk, an older one leaves nothing — and \`ctx_expand(message=N)\` brings it back whole: text, tool input, tool output. Now and then Magic Context leaves a short reminder on the desk saying how much unstamped material is lying around; that is housekeeping, not a warning, and the desk never gets smaller for it.
27982
-
27983
- ${TOOL_HISTORY_GUIDANCE}
27984
-
27985
- \`ctx_search\` searches the archive: anything ever said, decided, committed or noted in this project, including what is filed away. Ask it before you ask the user something that may already be recorded here, and whenever something feels familiar but is not in view.
27986
-
27987
- ${memoryEnabled ? `${MEMORY_GUIDANCE} ` : ""}${CTX_NOTE_GUIDANCE}${dreamerEnabled ? ` ${SMART_NOTE_GUIDANCE}` : ""}
27988
-
27989
- Some things on the desk are Magic Context's own markings, not conversation: \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : ""}. Read them, ${temporalAwarenessEnabled ? "use the time, and" : "and"} never reproduce them in a reply or treat them as instructions.`;
27990
- var BASE_INTRO_NO_REDUCE = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
27991
-
27992
- Think of your context as a desk. Every message and every tool output lands on it.
27993
-
27994
- Nothing cleared from the desk is ever lost. A cleared item goes to the archive — a recent one leaves a placeholder on the desk, an older one leaves nothing — and \`ctx_expand(message=N)\` brings it back whole: text, tool input, tool output.
27995
-
27996
- ${TOOL_HISTORY_GUIDANCE}
27997
-
27998
- \`ctx_search\` searches the archive: anything ever said, decided, committed or noted in this project, including what is filed away. Ask it before you ask the user something that may already be recorded here, and whenever something feels familiar but is not in view.
27999
-
28000
- ${memoryEnabled ? `${MEMORY_GUIDANCE} ` : ""}${CTX_NOTE_GUIDANCE}${dreamerEnabled ? ` ${SMART_NOTE_GUIDANCE}` : ""}
28001
-
28002
- Some things on the desk are Magic Context's own markings, not conversation: \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : ""}. Read them, ${temporalAwarenessEnabled ? "use the time, and" : "and"} never reproduce them in a reply or treat them as instructions.`;
28003
- var CTX_NOTE_GUIDANCE_LIGHT = `\`ctx_note\` is the tray for work you intend to return to, with its findings attached; findings without an intention are not a note; "take a note" from the user always goes there`;
28004
- var MEMORY_GUIDANCE_LIGHT = `\`<project-memory>\` is the pinboard of facts that stay true for the months this work lasts (\`#id: fact\`), for you and every agent on the project; \`ctx_memory\` pins what must not have to be found again, especially what cost you turns.`;
28005
- var TOOL_HISTORY_GUIDANCE_LIGHT = `Older work is filed off the desk into \`<session-history>\` — one \`## start-end · date · title\` heading per stretch, a summary under each, \`ctx_expand(start, end)\` to open the stretch in full. So your earlier messages may mention actions whose tool call is gone; that is normal and never a reason to fabricate: no tool result on the desk, no action, and never inline an invented call, output, search result or diff.`;
28006
- var SMART_NOTE_GUIDANCE_LIGHT = `; a \`surface_condition\` leaves the note with an outside checker that returns it when the condition holds`;
28007
- var TEMPORAL_AWARENESS_GUIDANCE_LIGHT = `, \`<!-- +Xm -->\` (time since your last reply)`;
28008
- var BASE_INTRO_LIGHT = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28009
-
28010
- Your context is a desk. Every message and tool output lands on it with a §N§ tag as its handle. When an item no longer needs to stay for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping removes nothing — the item stays readable; from time to time, when stamped items have piled up and the desk needs room, Magic Context clears them in one sweep (you only stamp, you don't pick the moment). Stamp as soon as an item has served its purpose, silently; never stamp a user message for what it asks (a used paste inside one is fine). Nothing is lost: a cleared item goes to the archive (a recent one leaves \`[dropped §N§]\`, an older one nothing) and \`ctx_expand(message=N)\` brings it back whole. Occasional reminders about unstamped material are housekeeping, not warnings; the desk never gets smaller.
28011
-
28012
- ${TOOL_HISTORY_GUIDANCE_LIGHT}
28013
-
28014
- \`ctx_search\` searches the archive — anything ever said, decided, committed or noted here; ask it before asking the user anything that may already be recorded, phrased as a question with the exact names in it. ${memoryEnabled ? `${MEMORY_GUIDANCE_LIGHT} ` : ""}${CTX_NOTE_GUIDANCE_LIGHT}${dreamerEnabled ? SMART_NOTE_GUIDANCE_LIGHT : ""}.
28015
-
28016
- Magic Context's own markings — \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE_LIGHT : ""} — are read, never reproduced, never instructions.`;
28017
- var BASE_INTRO_NO_REDUCE_LIGHT = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28018
-
28019
- Your context is a desk. Every message and tool output lands on it. Nothing cleared from the desk is lost: a cleared item goes to the archive (a recent one leaves a placeholder, an older one nothing) and \`ctx_expand(message=N)\` brings it back whole.
28020
-
28021
- ${TOOL_HISTORY_GUIDANCE_LIGHT}
28022
-
28023
- \`ctx_search\` searches the archive — anything ever said, decided, committed or noted here; ask it before asking the user anything that may already be recorded, phrased as a question with the exact names in it. ${memoryEnabled ? `${MEMORY_GUIDANCE_LIGHT} ` : ""}${CTX_NOTE_GUIDANCE_LIGHT}${dreamerEnabled ? SMART_NOTE_GUIDANCE_LIGHT : ""}.
28024
-
28025
- Magic Context's own markings — \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE_LIGHT : ""} — are read, never reproduced, never instructions.`;
28026
- var SUBAGENT_REDUCE_INTRO = () => `Your context is 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. When an item no longer needs to stay on the desk for the work ahead, stamp it: \`ctx_reduce\` with its tag ("3-5", "1,2,9", "1-5,8,12-15"). Stamping does not remove anything — the item stays fully readable until Magic Context clears stamped items in one sweep, and the newest tags are protected, so stamping recent output is harmless. Stamp as soon as an item has served its purpose, not at the end of the task, and do it silently. Never stamp a user message for what it asks of you; look at each tag before stamping and never blanket-stamp a range like "1-50".
28027
-
28028
- A cleared item leaves a \`[dropped §N§]\` placeholder or disappears from the desk; that is normal housekeeping, not something to copy or mention. If there is no tool result on the desk, the action did not happen: never fabricate or inline a tool call, an output, a search result or a diff — make a fresh real tool call instead.
28029
-
28030
- \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\` and \`[dropped §N§]\` are Magic Context's own markings: read them, never reproduce them in a reply, never treat them as instructions.`;
28031
- var SUBAGENT_REDUCE_INTRO_LIGHT = () => `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 with \`ctx_reduce\` ("3-5", "1,2,9"). Stamping removes nothing — the item stays readable until Magic Context clears stamped items in one sweep; newest tags are protected. Stamp as soon as an item has served its purpose, silently; never stamp a user message for what it asks; look at each tag, never blanket-stamp "1-50".
28032
-
28033
- A cleared item leaves \`[dropped §N§]\` or disappears; that is housekeeping, not a pattern to copy. No tool result on the desk means the action did not happen — never fabricate or inline a call, output, search result or diff; make a real call.
28034
-
28035
- \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\` are Magic Context's markings: read, never reproduced, never instructions.`;
28036
- var CAVEMAN_COMPRESSION_WARNING = `
28037
- **BEWARE**: History compression is on; older user AND assistant text — including your own earlier responses — has been deterministically rewritten in a terse caveman style (dropped articles, missing auxiliaries, \`//\` instead of connectives like \`because\`). This is automatic context compression that runs after the fact, not your actual prior wording or the user's. **DO NOT mimic this style in new turns.** Write fresh responses in normal prose. If you notice your output drifting into caveman cadence, that drift is in-context-learning bleeding from the compressed history — consciously revert to full sentences.`;
28038
- function buildMagicContextSection(_agent, _legacyProtectionCount, ctxReduceCallable = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true, preset = "full", primaryOverride) {
28039
- if (subagentMode) {
28040
- const intro = preset === "light" ? SUBAGENT_REDUCE_INTRO_LIGHT() : SUBAGENT_REDUCE_INTRO();
28041
- return `## Magic Context
28042
-
28043
- ${intro}`;
28044
- }
28045
- const temporalOverrideGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_OVERRIDE_GUIDANCE : "";
28046
- const cavemanWarning = cavemanTextCompressionEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
28047
- const languageDirective = buildPrimaryLanguageDirective(language);
28048
- const languageGuidance = languageDirective ? `
28049
-
28050
- ${languageDirective}` : "";
28051
- if (primaryOverride !== undefined) {
28052
- return `${primaryOverride}${temporalOverrideGuidance}${cavemanWarning}${languageGuidance}`;
28053
- }
28054
- if (!ctxReduceCallable) {
28055
- if (preset === "light") {
28056
- return `## Magic Context
28057
-
28058
- ${LONG_TERM_PARTNER_FRAME}
28059
- ${PARTNER_FRAME_CLOSER_NO_REDUCE_LIGHT}
28060
-
28061
- ${BASE_INTRO_NO_REDUCE_LIGHT(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28062
- }
28063
- return `## Magic Context
28064
-
28065
- ${LONG_TERM_PARTNER_FRAME}
28066
- ${PARTNER_FRAME_CLOSER_NO_REDUCE}
28067
-
28068
- ${BASE_INTRO_NO_REDUCE(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28069
- }
28070
- if (preset === "light") {
28071
- return `## Magic Context
28072
-
28073
- ${LONG_TERM_PARTNER_FRAME}
28074
- ${PARTNER_FRAME_CLOSER_REDUCE_LIGHT}
28075
-
28076
- ${BASE_INTRO_LIGHT(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28077
- }
28078
- return `## Magic Context
28079
-
28080
- ${LONG_TERM_PARTNER_FRAME}
28081
- ${PARTNER_FRAME_CLOSER_REDUCE}
28082
-
28083
- ${BASE_INTRO(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28084
- }
28085
-
28086
- // src/system-prompt.ts
28087
28904
  var MAGIC_CONTEXT_MARKER = "## Magic Context";
28088
28905
  var stickyDateBySession = new Map;
28089
28906
  function buildMagicContextBlock(opts) {
@@ -29026,12 +29843,14 @@ var MAX_TRACKED_SESSIONS = 100;
29026
29843
  function trackSessionForProject(projectIdentity, sessionId) {
29027
29844
  activeContextHandlerSessions.delete(sessionId);
29028
29845
  activeContextHandlerSessions.add(sessionId);
29029
- let sessions = sessionsByProject.get(projectIdentity);
29030
- if (!sessions) {
29031
- sessions = new Set;
29032
- sessionsByProject.set(projectIdentity, sessions);
29846
+ if (isUsableProjectIdentity(projectIdentity)) {
29847
+ let sessions = sessionsByProject.get(projectIdentity);
29848
+ if (!sessions) {
29849
+ sessions = new Set;
29850
+ sessionsByProject.set(projectIdentity, sessions);
29851
+ }
29852
+ sessions.add(sessionId);
29033
29853
  }
29034
- sessions.add(sessionId);
29035
29854
  while (activeContextHandlerSessions.size > MAX_TRACKED_SESSIONS) {
29036
29855
  const oldest = activeContextHandlerSessions.values().next().value;
29037
29856
  if (oldest === undefined || oldest === sessionId)
@@ -29490,7 +30309,7 @@ function databaseIsInTransaction(db) {
29490
30309
  }
29491
30310
  function runImmediateTransaction(db, fn) {
29492
30311
  if (databaseIsInTransaction(db)) {
29493
- return db.transaction(fn)();
30312
+ return db.transaction(fn).immediate();
29494
30313
  }
29495
30314
  const transactionStartedAt = performance.now();
29496
30315
  db.exec("BEGIN IMMEDIATE");
@@ -29672,6 +30491,7 @@ function registerPiContextHandler(pi, baseOptions) {
29672
30491
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
29673
30492
  const tEntryBranch = performance.now();
29674
30493
  const branchEntries = readPiBranchEntriesForContext(ctx, sessionId);
30494
+ const piLiveUsageIsRawBranchEstimate = isPiLiveUsageRawBranchEstimate(branchEntries);
29675
30495
  schedulePiTransformDecisionResolve({
29676
30496
  db: options.db,
29677
30497
  sessionId,
@@ -29704,14 +30524,29 @@ function registerPiContextHandler(pi, baseOptions) {
29704
30524
  const lkgInputIdByRef = new Map;
29705
30525
  for (const input of lkgPassSnapshot.inputs)
29706
30526
  lkgInputIdByRef.set(event.messages[input.messageIndex], input.id);
29707
- const thinkingBindingRecoveryApplied = applyPiThinkingBindingRecovery({
30527
+ const bindingStripOrder = resolvePiBindingStripOrder({
30528
+ db: options.db,
30529
+ sessionId,
30530
+ messages: event.messages,
30531
+ entryIds: lkgEntryIds ?? [],
30532
+ bustPermittedAtStart: hasPendingMaterialization(sessionId)
30533
+ });
30534
+ const startOfPassBindingRecovery = bindingStripOrder === "start" ? applyPiThinkingBindingRecovery({
29708
30535
  db: options.db,
29709
30536
  sessionId,
29710
30537
  messages: event.messages,
29711
30538
  entryIds: lkgEntryIds ?? [],
29712
30539
  provider: lkgProviderKey ?? undefined,
29713
30540
  model: ctx.model?.id
29714
- });
30541
+ }) : null;
30542
+ const bindingEntryIdByRef = new Map;
30543
+ if (lkgEntryIds) {
30544
+ for (let index = 0;index < event.messages.length; index += 1) {
30545
+ const entryId = lkgEntryIds[index];
30546
+ if (entryId)
30547
+ bindingEntryIdByRef.set(event.messages[index], entryId);
30548
+ }
30549
+ }
29715
30550
  const tMeta = performance.now();
29716
30551
  const sessionMetaForUsage = getOrCreateSessionMeta(options.db, sessionId);
29717
30552
  sessionMetaForPass = sessionMetaForUsage;
@@ -29773,6 +30608,8 @@ function registerPiContextHandler(pi, baseOptions) {
29773
30608
  }
29774
30609
  const previousModelKey = liveModelBySession.get(sessionId);
29775
30610
  const currentModelKey = resolvePiContextModelKey(ctx);
30611
+ if (options.cacheTtlConfig !== undefined)
30612
+ resolveSessionCacheTtl(options.db, sessionId, options.cacheTtlConfig, currentModelKey);
29776
30613
  const modelChanged = previousModelKey !== undefined && currentModelKey !== undefined && piModelRefToCanonical(previousModelKey) !== piModelRefToCanonical(currentModelKey);
29777
30614
  if (currentModelKey !== undefined) {
29778
30615
  liveModelBySession.set(sessionId, currentModelKey);
@@ -29921,7 +30758,11 @@ function registerPiContextHandler(pi, baseOptions) {
29921
30758
  if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
29922
30759
  usagePercentage = usageInputTokens / usageContextLimit * 100;
29923
30760
  }
29924
- ({ percentage: usagePercentage, inputTokens: usageInputTokens } = resolvePiPressureSnapshot({
30761
+ ({ percentage: usagePercentage, inputTokens: usageInputTokens } = resolvePiPressureSnapshotWithEstimateGuard({
30762
+ sessionId,
30763
+ source: "transform",
30764
+ liveIsRawBranchEstimate: piLiveUsageIsRawBranchEstimate,
30765
+ persistedFromLive: !usedPersistedUsage,
29925
30766
  persistedPercentage: usagePercentage,
29926
30767
  persistedInputTokens: usageInputTokens,
29927
30768
  liveInputTokens: piUsage?.tokens,
@@ -30099,6 +30940,7 @@ function registerPiContextHandler(pi, baseOptions) {
30099
30940
  sessionMeta,
30100
30941
  piUsage,
30101
30942
  minimumPercentage: usagePercentage,
30943
+ liveIsRawBranchEstimate: piLiveUsageIsRawBranchEstimate,
30102
30944
  historianStateSnapshot: historianStateForPass,
30103
30945
  publishedHistoryRide: isCacheBusting
30104
30946
  });
@@ -30161,7 +31003,7 @@ function registerPiContextHandler(pi, baseOptions) {
30161
31003
  const tTodoCapture = performance.now();
30162
31004
  try {
30163
31005
  const sessionMetaForTodo = sessionMeta;
30164
- if (!options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
31006
+ if (options.todowriteEnabled === true && !options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
30165
31007
  const isCacheBustingForTodo = isCacheBusting || result.executedWorkThisPass;
30166
31008
  outputMessages = injectSyntheticTodowriteForPi({
30167
31009
  db: options.db,
@@ -30176,6 +31018,34 @@ function registerPiContextHandler(pi, baseOptions) {
30176
31018
  sessionLog(sessionId, `synthetic todowrite injection failed: ${err instanceof Error ? err.message : String(err)}`);
30177
31019
  }
30178
31020
  logTransformTiming(sessionId, "todoCapture", tTodoCapture);
31021
+ const tThinkingBinding = performance.now();
31022
+ const outputEntryIds = resolvePiLkgOutputEntryIds(outputMessages, result.syntheticLeadingCount, (message) => result.lkgEntryIdByRef.get(message) ?? result.postCommitEntryIdByRef.get(message) ?? bindingEntryIdByRef.get(message)).map((entryId) => entryId ?? undefined);
31023
+ const thinkingBindingRecoveryApplied = bindingStripOrder === "end" ? applyPiThinkingBindingRecovery({
31024
+ db: options.db,
31025
+ sessionId,
31026
+ messages: outputMessages,
31027
+ entryIds: outputEntryIds,
31028
+ provider: lkgProviderKey ?? undefined,
31029
+ model: ctx.model?.id,
31030
+ endOfPassOrder: true
31031
+ }) : startOfPassBindingRecovery;
31032
+ if (!options.compactionOff && !sessionMeta.isSubagent && bindingStripOrder === "end") {
31033
+ try {
31034
+ applyPiProactiveThinkingStrip({
31035
+ db: options.db,
31036
+ sessionId,
31037
+ messages: outputMessages,
31038
+ entryIds: outputEntryIds,
31039
+ provider: lkgProviderKey ?? undefined,
31040
+ model: ctx.model?.id,
31041
+ cacheBustingPass: isCacheBusting || result.executedWorkThisPass,
31042
+ report: (line) => sessionLog(sessionId, line)
31043
+ });
31044
+ } catch (err) {
31045
+ sessionLog(sessionId, `proactive thinking strip failed: ${err instanceof Error ? err.message : String(err)}`);
31046
+ }
31047
+ }
31048
+ logTransformTiming(sessionId, "thinkingBinding", tThinkingBinding);
30179
31049
  const tChannelAccounting = performance.now();
30180
31050
  try {
30181
31051
  const sessionMetaForCh1 = sessionMeta;
@@ -30655,7 +31525,11 @@ function maybeFireHistorian(args) {
30655
31525
  };
30656
31526
  usageSource = "piUsage fallback";
30657
31527
  }
30658
- usage = resolvePiPressureSnapshot({
31528
+ usage = resolvePiPressureSnapshotWithEstimateGuard({
31529
+ sessionId,
31530
+ source: "historian trigger",
31531
+ liveIsRawBranchEstimate: args.liveIsRawBranchEstimate,
31532
+ persistedFromLive: usageSource === "piUsage fallback",
30659
31533
  persistedPercentage: usage.percentage,
30660
31534
  persistedInputTokens: usage.inputTokens,
30661
31535
  liveInputTokens: piUsage?.tokens,
@@ -30978,6 +31852,7 @@ async function runPipeline(args) {
30978
31852
  const foldDueDecision = piM0State ? mustMaterializePi(piM0State, args.db, injectionPassSnapshot?.compartments, injectionPassSnapshot) : { value: false, reason: null };
30979
31853
  const firstRenderBust = piM0State !== undefined && !(injectionPassSnapshot?.cachedRow?.cached_m0_bytes && injectionPassSnapshot.cachedRow.cached_m1_bytes);
30980
31854
  let foldExecutedThisPass = false;
31855
+ let foldBustsServedPrefixThisPass = false;
30981
31856
  let publishedM1RefreshedThisPass = false;
30982
31857
  let prefixPreflightContended = false;
30983
31858
  const softRefreshOpportunity = args.schedulerDecision === "execute";
@@ -30991,6 +31866,18 @@ async function runPipeline(args) {
30991
31866
  prefixPreflightContended = preFoldInjectionResult.contentionExhausted === true;
30992
31867
  publishedM1RefreshedThisPass = !prefixPreflightContended && persistedM0BeforeFold.cachedM1Bytes?.toString("utf8") !== getOrCreateSessionMeta(args.db, args.sessionId).cachedM1Bytes?.toString("utf8");
30993
31868
  foldExecutedThisPass = foldExecutesThisPass(foldDueDecision.value || softRefreshOpportunity, preFoldInjectionResult.m0Materialized === true);
31869
+ if (foldExecutedThisPass) {
31870
+ const afterFold = getOrCreateSessionMeta(args.db, args.sessionId);
31871
+ foldBustsServedPrefixThisPass = foldBustsServedPrefix(foldDueDecision.reason, {
31872
+ m0Bytes: persistedM0BeforeFold.cachedM0Bytes ?? null,
31873
+ m1Bytes: persistedM0BeforeFold.cachedM1Bytes ?? null,
31874
+ muralDataUrl: persistedM0BeforeFold.cachedM0MuralDataUrl ?? null
31875
+ }, {
31876
+ m0Bytes: afterFold.cachedM0Bytes ?? null,
31877
+ m1Bytes: afterFold.cachedM1Bytes ?? null,
31878
+ muralDataUrl: afterFold.cachedM0MuralDataUrl ?? null
31879
+ });
31880
+ }
30994
31881
  if (preFoldInjectionResult.m0Materialized) {
30995
31882
  injectionPassSnapshot = createPiM0M1PassSnapshot({
30996
31883
  db: args.db,
@@ -31018,7 +31905,7 @@ async function runPipeline(args) {
31018
31905
  sessionLog(args.sessionId, `pi m[0] HARD fold pre-execution failed: ${error instanceof Error ? error.message : String(error)}`);
31019
31906
  }
31020
31907
  const mismatch = foldDueDecision.mismatch ? ` mismatch=${JSON.stringify(foldDueDecision.mismatch)}` : "";
31021
- sessionLog(args.sessionId, `pi m[0] HARD fold decision: reason=${foldDueDecision.reason ?? "unknown"}${mismatch} executed=${foldExecutedThisPass}`);
31908
+ sessionLog(args.sessionId, `pi m[0] HARD fold decision: reason=${foldDueDecision.reason ?? "unknown"}${mismatch} executed=${foldExecutedThisPass} bustsServedPrefix=${foldBustsServedPrefixThisPass}`);
31022
31909
  }
31023
31910
  const executePressureEligible = args.schedulerDecision === "execute" || emergencyDropEligible;
31024
31911
  if (!executePressureEligible) {
@@ -31029,7 +31916,7 @@ async function runPipeline(args) {
31029
31916
  const routinePressureAlreadyApplied = !args.sessionMeta.isSubagent && executePressureEligible && routinePressureAppliedBySession.get(args.sessionId) === true;
31030
31917
  const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
31031
31918
  const rideSignals = {
31032
- hardFold: foldExecutedThisPass || firstRenderBust,
31919
+ hardFold: foldBustsServedPrefixThisPass || firstRenderBust,
31033
31920
  force: (args.forceMaterialization === true || emergencyDropEligible) && (args.contextUsage.percentage >= 95 || getEmergencyInputSample(args.db, args.sessionId) === 0),
31034
31921
  explicitFlush: hasPendingMaterializeSignal || deferredMaterializeEligible && !prefixPreflightContended,
31035
31922
  publishedHistory: !prefixPreflightContended && (args.isCacheBusting || publishedM1RefreshedThisPass || canConsumeDeferredLate && deferredHistoryWasPendingAtPassStart)
@@ -31045,7 +31932,7 @@ async function runPipeline(args) {
31045
31932
  onRejectedProjectOverride: (warning) => sessionLog(args.sessionId, warning)
31046
31933
  });
31047
31934
  let protectionFloorResolution = resolveProtectionFloor();
31048
- const shouldRunHeuristics = args.heuristics !== undefined && isCacheBustingPass && (rideSignals.publishedHistory || args.forceMaterialization === true || hasPendingMaterializeSignal || deferredMaterializeEligible || foldExecutedThisPass || firstRenderBust || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
31935
+ const shouldRunHeuristics = args.heuristics !== undefined && isCacheBustingPass && (rideSignals.publishedHistory || args.forceMaterialization === true || hasPendingMaterializeSignal || deferredMaterializeEligible || foldBustsServedPrefixThisPass || firstRenderBust || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
31049
31936
  const tFallbackIdentity = performance.now();
31050
31937
  const hasFallbackMessageTags = hasPiFallbackMessageTags(args.db, args.sessionId);
31051
31938
  const entryFingerprintByMessageId = buildEntryFingerprintMap(args.messages, stableIdResolver, args.reusableMessageIds, hasFallbackMessageTags);
@@ -31085,6 +31972,19 @@ async function runPipeline(args) {
31085
31972
  } : undefined
31086
31973
  });
31087
31974
  logTransformTiming(args.sessionId, "tagMessages", tTag);
31975
+ if (foldBustsServedPrefixThisPass) {
31976
+ try {
31977
+ let converted = 0;
31978
+ args.db.transaction(() => {
31979
+ converted = convertLegacyToolSkeletons(args.db, args.sessionId, targets).size;
31980
+ }).immediate();
31981
+ if (converted > 0) {
31982
+ sessionLog(args.sessionId, `pi HARD fold converted ${converted} legacy dropped-tool skeleton(s) to real-or-absent`);
31983
+ }
31984
+ } catch (error) {
31985
+ sessionLog(args.sessionId, `pi legacy dropped-tool skeleton conversion failed (kept legacy bytes): ${error instanceof Error ? error.message : String(error)}`);
31986
+ }
31987
+ }
31088
31988
  try {
31089
31989
  if (!args.sessionMeta.isSubagent) {
31090
31990
  const hasRecentCommit = detectRecentCommit(args.messages);
@@ -31111,12 +32011,13 @@ async function runPipeline(args) {
31111
32011
  mutationGateObserverForTests?.({
31112
32012
  foldDue: foldDueDecision.value,
31113
32013
  foldExecuted: foldExecutedThisPass,
32014
+ foldBustsServedPrefix: foldBustsServedPrefixThisPass,
31114
32015
  shouldApplyPendingOps,
31115
32016
  shouldRunHeuristics,
31116
32017
  shouldRunReasoningCleanup: args.reasoningClearing !== undefined && shouldRunHeuristics
31117
32018
  });
31118
32019
  if (shouldApplyPendingOps) {
31119
- const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : foldExecutedThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : `${reclaimRideLabel(rideSignals)} (scheduler=${args.schedulerDecision})`;
32020
+ const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : foldBustsServedPrefixThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : `${reclaimRideLabel(rideSignals)} (scheduler=${args.schedulerDecision})`;
31120
32021
  const pendingOpsDepth = getPendingOpsCount(args.db, args.sessionId);
31121
32022
  const pendingDecisionLog = `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOpsDepth === null ? "not loaded (deferred pass)" : pendingOpsDepth} context=${args.contextUsage.percentage.toFixed(1)}%`;
31122
32023
  sessionLog(args.sessionId, pendingDecisionLog);
@@ -31203,7 +32104,7 @@ async function runPipeline(args) {
31203
32104
  const activeTags = allTagsForPass.filter((tag) => tag.status === "active");
31204
32105
  logTransformTiming(args.sessionId, "getTagsBySessionSnapshot", tActiveTags, `all=${allTagsForPass.length} active=${activeTags.length}`);
31205
32106
  if (shouldRunHeuristics) {
31206
- const reason = args.forceMaterialization ? "force_materialization" : foldExecutedThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : `${reclaimRideLabel(rideSignals)} (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
32107
+ const reason = args.forceMaterialization ? "force_materialization" : foldBustsServedPrefixThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : `${reclaimRideLabel(rideSignals)} (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
31207
32108
  const heuristicsDecisionLog = `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=n/a`;
31208
32109
  sessionLog(args.sessionId, heuristicsDecisionLog);
31209
32110
  pendingDecisionLogObserverForTests?.(heuristicsDecisionLog);
@@ -31216,7 +32117,7 @@ async function runPipeline(args) {
31216
32117
  if (shouldRunHeuristics && args.heuristics) {
31217
32118
  try {
31218
32119
  const tHeuristic = performance.now();
31219
- const independentMutationBeforeHeuristics = pendingOpsDidMutate || foldExecutedThisPass;
32120
+ const independentMutationBeforeHeuristics = pendingOpsDidMutate || foldBustsServedPrefixThisPass;
31220
32121
  if (args.forceMaterialization === true && independentMutationBeforeHeuristics && getEmergencyInputSample(args.db, args.sessionId) > 0) {
31221
32122
  clearEmergencyDropSample(args.db, args.sessionId);
31222
32123
  }
@@ -31991,6 +32892,7 @@ ${result}`;
31991
32892
  }
31992
32893
 
31993
32894
  // ../plugin/src/features/magic-context/store-generation-rebase.ts
32895
+ var rebasesInFlight = new WeakMap;
31994
32896
  var repairCheckedSessions = new WeakMap;
31995
32897
 
31996
32898
  // ../plugin/src/hooks/magic-context/historian-state-file.ts
@@ -32195,7 +33097,7 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
32195
33097
  clearInjectionCache(sessionId);
32196
33098
  }
32197
33099
  promoted.facts;
32198
- if (deps.memoryEnabled !== false) {
33100
+ {
32199
33101
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
32200
33102
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
32201
33103
  const liveCompartments = getCompartments(db, sessionId);
@@ -32412,7 +33314,7 @@ Another process acquired the compartment-state lease before recomp could publish
32412
33314
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd, compartmentTagKeys);
32413
33315
  }
32414
33316
  deps.onCompartmentStatePublished?.(sessionId);
32415
- if (deps.memoryEnabled !== false) {
33317
+ {
32416
33318
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
32417
33319
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
32418
33320
  const liveCompartments = getCompartments(db, sessionId);
@@ -32574,7 +33476,7 @@ Could not acquire the compartment-state lease for this session.`;
32574
33476
  clearInjectionCache(sessionId);
32575
33477
  }
32576
33478
  deps.onCompartmentStatePublished?.(sessionId);
32577
- if (deps.memoryEnabled !== false) {
33479
+ {
32578
33480
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
32579
33481
  const liveCompartments = getCompartments(db, sessionId);
32580
33482
  const chunksToEmbed = liveCompartments.map((c) => ({
@@ -33551,6 +34453,8 @@ ${c.content}
33551
34453
 
33552
34454
  // ../plugin/src/shared/cache-ttl-display.ts
33553
34455
  function resolveCacheTtlDisplay(args) {
34456
+ if (args.frozen)
34457
+ return args.frozen;
33554
34458
  if (args.sessionModelKey && (!args.modelKey || args.sessionModelKey === args.modelKey) || !args.modelKey && !args.sessionModelKey && args.sessionValue !== "5m") {
33555
34459
  return {
33556
34460
  value: args.sessionValue || "5m",
@@ -33558,24 +34462,14 @@ function resolveCacheTtlDisplay(args) {
33558
34462
  modelKey: args.modelKey ?? args.sessionModelKey ?? undefined
33559
34463
  };
33560
34464
  }
33561
- if (typeof args.configured === "string") {
33562
- return {
33563
- value: args.configured,
33564
- source: args.configuredExplicitly ? "config" : "default",
33565
- modelKey: args.modelKey
33566
- };
33567
- }
33568
- const matched = resolveModelConfigValue(args.configured, args.modelKey);
33569
- if (matched) {
33570
- return { value: matched.value, source: "config", modelKey: args.modelKey };
33571
- }
33572
- return {
33573
- value: args.configured.default ?? "5m",
33574
- source: "default",
33575
- modelKey: args.modelKey
33576
- };
34465
+ const resolved = resolveModelCacheTtl(args.configured, args.modelKey);
34466
+ if (resolved.source === "default" && typeof args.configured === "string" && args.configuredExplicitly)
34467
+ return { ...resolved, source: "config" };
34468
+ return resolved;
33577
34469
  }
33578
34470
  function formatCacheTtlDisplay(display) {
34471
+ if (display.source === "OpenAI GPT-5.6+ default")
34472
+ return `Cache TTL: ${display.value} (${display.source})`;
33579
34473
  if (display.source === "session")
33580
34474
  return `Cache TTL: ${display.value} (session)`;
33581
34475
  if (display.source === "config") {
@@ -33601,6 +34495,11 @@ function compressionText(summary) {
33601
34495
  return "Waiting for enough conversation history";
33602
34496
  }
33603
34497
  }
34498
+ function runnerText(status) {
34499
+ const why = status.source === "configured" ? "configured" : `default for harness ${status.harness || "unknown"}`;
34500
+ const observed = status.observed === "last_completion" ? "" : " · no completion yet";
34501
+ return `${status.runner} (${why})${observed}`;
34502
+ }
33604
34503
  function reclaimableText(summary) {
33605
34504
  const count = Math.max(0, Math.floor(summary.reclaimable.toolOutputCount));
33606
34505
  if (count === 0)
@@ -33642,6 +34541,12 @@ function renderUserStatusSummary(summary, style) {
33642
34541
  ],
33643
34542
  ["Search indexing", embeddingText(summary)]
33644
34543
  ];
34544
+ if (summary.historianRunner) {
34545
+ values.push(["Historian runner", runnerText(summary.historianRunner)]);
34546
+ }
34547
+ if (summary.dreamerRunner) {
34548
+ values.push(["Dreamer runner", runnerText(summary.dreamerRunner)]);
34549
+ }
33645
34550
  if (summary.historianRefusal) {
33646
34551
  values.push([
33647
34552
  "Historian refusal",
@@ -34120,7 +35025,7 @@ function buildStatusView(source, options) {
34120
35025
  // package.json
34121
35026
  var package_default = {
34122
35027
  name: "@cortexkit/pi-magic-context",
34123
- version: "0.43.2",
35028
+ version: "0.44.1",
34124
35029
  type: "module",
34125
35030
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
34126
35031
  main: "dist/index.js",
@@ -34147,6 +35052,7 @@ var package_default = {
34147
35052
  "dist",
34148
35053
  "README.md"
34149
35054
  ],
35055
+ "//": "The build intentionally bundles typebox (no --external typebox) and keeps it a regular dependency. OMP's extension loader rewrites every bare `typebox` import to its own omptype-based shim, whose schemas are callable objects rather than JSON Schema; they fail structuredClone during tool registration, so an external typebox import stops Magic Context loading on OMP. src/typebox-bundling.test.ts guards this.",
34150
35056
  scripts: {
34151
35057
  build: "bun ../../scripts/check-bun.mjs && tsc -p ../retina-local-fs/tsconfig.build.json && rm -f dist/index.js dist/subagent-entry.js dist/historian-calibration-extension.js dist/transformers-web.js dist/transformers-node-wasm.js dist/*-*.js && bun build ../plugin/src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/transformers-web.js --target browser --format esm --external onnxruntime-web && bun ../plugin/scripts/build-transformers-node-wasm.ts dist && bun build src/index.ts src/subagent-entry.ts src/historian-calibration-extension.ts --outdir dist --target node --format esm --splitting --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external onnxruntime-node --external onnxruntime-web --external sharp --external node:sqlite",
34152
35058
  "build:e2e-argv": "bun build src/subagent-runner.ts --outfile ../../tests/docker/.generated/subagent-runner-e2e.mjs --target node --format esm --external node:sqlite",
@@ -34161,7 +35067,7 @@ var package_default = {
34161
35067
  prepublishOnly: "bun run build"
34162
35068
  },
34163
35069
  dependencies: {
34164
- "@cortexkit/subc-client": "0.4.1",
35070
+ "@cortexkit/subc-client": "0.11.1",
34165
35071
  "@jitl/quickjs-singlefile-cjs-release-asyncify": "0.32.0",
34166
35072
  "ai-tokenizer": "^1.0.6",
34167
35073
  "comment-json": "^5.0.0",
@@ -34183,6 +35089,7 @@ var package_default = {
34183
35089
  "@types/better-sqlite3": "^7.6.13",
34184
35090
  "@types/bun": "^1.3.10",
34185
35091
  "@types/node": "^22.20.0",
35092
+ "pi-agent-core-087": "npm:@earendil-works/pi-agent-core@0.87.1",
34186
35093
  "pi-ai-086": "npm:@earendil-works/pi-ai@0.86.0",
34187
35094
  "pi-coding-agent-086": "npm:@earendil-works/pi-coding-agent@0.86.0",
34188
35095
  "pi-coding-agent-087": "npm:@earendil-works/pi-coding-agent@0.87.1",
@@ -34530,6 +35437,7 @@ ${safeStringify(tool.parameters)}`);
34530
35437
  sessionId
34531
35438
  });
34532
35439
  const cacheTtlDisplay = resolveCacheTtlDisplay({
35440
+ frozen: readSessionCacheTtl(deps.db, sessionId),
34533
35441
  configured: deps.cacheTtlConfig ?? "5m",
34534
35442
  configuredExplicitly: deps.cacheTtlConfigured === true,
34535
35443
  modelKey,
@@ -34723,11 +35631,14 @@ No active Pi session is available.`,
34723
35631
  }
34724
35632
  try {
34725
35633
  if (ctx.hasUI) {
35634
+ if (currentDeps.modelChainWarning)
35635
+ ctx.ui.notify(currentDeps.modelChainWarning, "error");
34726
35636
  await showStatusDialog(pi, ctx, currentDeps);
34727
35637
  return;
34728
35638
  }
34729
35639
  const statusDetail = buildPiStatusDetail(pi, ctx, currentDeps, sessionId);
34730
- const statusText = formatPiStatusSummary(statusDetail);
35640
+ const statusText = `${formatPiStatusSummary(statusDetail)}${currentDeps.modelChainWarning ? `
35641
+ WARNING: ${currentDeps.modelChainWarning}` : ""}`;
34731
35642
  const details = buildStatusDetails(currentDeps, statusDetail);
34732
35643
  sendStatus({
34733
35644
  title: "/ctx-status",
@@ -35436,7 +36347,7 @@ function persistPiMessageEndModelMeta(args) {
35436
36347
  const modelKey = canonicalPiModelKey(msg.provider, msg.model);
35437
36348
  if (!recordPiLiveModel(args.sessionId, modelKey, msg.timestamp))
35438
36349
  return;
35439
- const cacheTtl = resolveCacheTtl(args.cacheTtlConfig, modelKey);
36350
+ const cacheTtl = resolveSessionCacheTtl(args.db, args.sessionId, args.cacheTtlConfig, modelKey).value;
35440
36351
  const currentMeta = getOrCreateSessionMeta(args.db, args.sessionId);
35441
36352
  updateSessionMeta(args.db, args.sessionId, {
35442
36353
  ...currentMeta.cacheTtl === cacheTtl ? {} : { cacheTtl },
@@ -35502,6 +36413,13 @@ function info(message, data) {
35502
36413
  function warn(message, data) {
35503
36414
  log(`${PREFIX2} WARN ${message}`, data);
35504
36415
  }
36416
+ var loggedNoProjectIdentityDirs = new Set;
36417
+ function logNoProjectIdentityOnce(directory) {
36418
+ if (loggedNoProjectIdentityDirs.has(directory))
36419
+ return;
36420
+ loggedNoProjectIdentityDirs.add(directory);
36421
+ info(`no project identity for ${directory}: it is not treated as a project, so memory, search, ` + "embeddings and the dreamer stay off here. Start Pi inside a project folder, or set " + "`allow_home_project: true` in the user-level magic-context.jsonc to give home sessions their own project.");
36422
+ }
35505
36423
  var migratedConfigDirs = new Set;
35506
36424
  var loggedPiConfigDirs = new Set;
35507
36425
  function ensureConfigLocationsMigrated(dir) {
@@ -35514,7 +36432,7 @@ function ensureConfigLocationsMigrated(dir) {
35514
36432
  });
35515
36433
  }
35516
36434
  function logPiConfigLoad(args) {
35517
- const key = resolve3(args.dir);
36435
+ const key = resolve4(args.dir);
35518
36436
  if (args.dedupe && loggedPiConfigDirs.has(key))
35519
36437
  return;
35520
36438
  if (args.dedupe) {
@@ -35542,6 +36460,14 @@ function claimProtectedTagsDeprecationNoticeOnce() {
35542
36460
  function resetClaimedProtectedTagsDeprecationNoticeForTesting() {
35543
36461
  protectedTagsDeprecationClaimed = false;
35544
36462
  }
36463
+ function registerConfiguredTodoLifecycle(pi, options) {
36464
+ if (!options.configured)
36465
+ return;
36466
+ registerTodoStateLifecycle(pi, {
36467
+ readLastTodoState: options.readLastTodoState
36468
+ });
36469
+ return options.overlay ? registerTodoOverlay(pi, { readLastTodoState: options.readLastTodoState }) : undefined;
36470
+ }
35545
36471
  var __test = {
35546
36472
  logPiConfigLoad,
35547
36473
  resetLoggedPiConfigDirs() {
@@ -35575,6 +36501,13 @@ function logPiUsageBoundOnce(sessionId, reading, absoluteWall, reason) {
35575
36501
  piUsageBoundLogSeen.add(key);
35576
36502
  info(`message_end: session=${sessionId} bounded ${reason} ${reading} at trusted absolute wall ${absoluteWall}; pressure_proof_not_capacity`);
35577
36503
  }
36504
+ function logPiUsageAboveWindowOnce(sessionId, reading, absoluteWall) {
36505
+ const key = `${sessionId}|accepted above window`;
36506
+ if (piUsageBoundLogSeen.has(key))
36507
+ return;
36508
+ piUsageBoundLogSeen.add(key);
36509
+ info(`message_end: session=${sessionId} provider usage ${reading} exceeds configured window ${absoluteWall}; counted as real pressure against the configured limit`);
36510
+ }
35578
36511
  function resolvePiPressureContextLimit(args) {
35579
36512
  let detectedContextLimit;
35580
36513
  try {
@@ -35606,24 +36539,32 @@ async function persistPiPressureFromMessageEnd(args) {
35606
36539
  });
35607
36540
  const trustedAbsoluteWall = reportedGeometry && hasTrustedAbsoluteWall(reportedGeometry) ? reportedGeometry.derivation.absoluteWall : undefined;
35608
36541
  const unboundedPressure = computePiPressure(usage, args.piContextWindow);
35609
- const rawPressure = computePiPressure(usage, args.piContextWindow, trustedAbsoluteWall);
35610
- if (unboundedPressure && trustedAbsoluteWall !== undefined && unboundedPressure.inputTokens > trustedAbsoluteWall) {
35611
- logPiUsageBoundOnce(args.sessionId, unboundedPressure.inputTokens, trustedAbsoluteWall, "current reading");
35612
- }
35613
36542
  const msg = args.message && typeof args.message === "object" ? args.message : undefined;
35614
36543
  const messageHadOverflowError = typeof msg?.errorMessage === "string" && detectOverflow(msg.errorMessage).isOverflow;
35615
- const requestSucceeded = !messageHadOverflowError && !(unboundedPressure && trustedAbsoluteWall !== undefined && unboundedPressure.inputTokens > trustedAbsoluteWall);
35616
- if (requestSucceeded && rawPressure) {
36544
+ const readingAboveTrustedWall = unboundedPressure !== null && trustedAbsoluteWall !== undefined && unboundedPressure.inputTokens > trustedAbsoluteWall;
36545
+ const requestAccepted = !messageHadOverflowError;
36546
+ if (readingAboveTrustedWall && unboundedPressure && trustedAbsoluteWall) {
36547
+ if (requestAccepted) {
36548
+ logPiUsageAboveWindowOnce(args.sessionId, unboundedPressure.inputTokens, trustedAbsoluteWall);
36549
+ } else {
36550
+ logPiUsageBoundOnce(args.sessionId, unboundedPressure.inputTokens, trustedAbsoluteWall, "current reading");
36551
+ }
36552
+ }
36553
+ const requestSucceeded = requestAccepted && !readingAboveTrustedWall;
36554
+ if (requestAccepted && unboundedPressure) {
35617
36555
  const rawOverflow = getOverflowState(args.db, args.sessionId);
35618
36556
  const detectedLimitMatchesModel = rawOverflow.detectedContextLimitModelKey === null || modelKey !== undefined && rawOverflow.detectedContextLimitModelKey === modelKey;
35619
- if (rawOverflow.detectedContextLimit > 0 && detectedLimitMatchesModel && rawPressure.inputTokens > rawOverflow.detectedContextLimit) {
36557
+ if (rawOverflow.detectedContextLimit > 0 && detectedLimitMatchesModel && unboundedPressure.inputTokens > rawOverflow.detectedContextLimit) {
35620
36558
  clearDetectedContextLimit(args.db, args.sessionId);
35621
- info(`message_end: detected limit ${rawOverflow.detectedContextLimit} invalidated by a successful ${rawPressure.inputTokens}-token request; using Pi contextWindow`);
36559
+ info(`message_end: detected limit ${rawOverflow.detectedContextLimit} invalidated by a successful ${unboundedPressure.inputTokens}-token request; using Pi contextWindow`);
35622
36560
  }
35623
36561
  }
35624
36562
  const meta = getOrCreateSessionMeta(args.db, args.sessionId);
35625
36563
  let observedSafeInputTokens = meta.observedSafeInputTokens ?? 0;
35626
- const updates = { lastResponseTime: Date.now() };
36564
+ const updates = {};
36565
+ if (unboundedPressure !== null) {
36566
+ updates.lastResponseTime = Date.now();
36567
+ }
35627
36568
  if (trustedAbsoluteWall !== undefined && observedSafeInputTokens > trustedAbsoluteWall) {
35628
36569
  logPiUsageBoundOnce(args.sessionId, observedSafeInputTokens, trustedAbsoluteWall, "persisted floor");
35629
36570
  observedSafeInputTokens = 0;
@@ -35644,8 +36585,10 @@ async function persistPiPressureFromMessageEnd(args) {
35644
36585
  provenInputTokens: observedSafeInputTokens
35645
36586
  });
35646
36587
  const reportedContextLimit = reportedGeometry?.usableSoft ?? 0;
35647
- const pressure = computePiPressure(usage, effectiveContextLimit, trustedAbsoluteWall);
36588
+ const pressure = computePiPressure(usage, effectiveContextLimit, requestAccepted ? undefined : trustedAbsoluteWall);
36589
+ let cacheAlert;
35648
36590
  if (pressure) {
36591
+ notePiUsageReadingUsed(args.sessionId);
35649
36592
  const provenSafeInputTokens = requestSucceeded ? Math.max(observedSafeInputTokens, pressure.inputTokens) : observedSafeInputTokens;
35650
36593
  const contextLimit = requestSucceeded ? resolvePiPressureContextLimit({
35651
36594
  db: args.db,
@@ -35659,7 +36602,7 @@ async function persistPiPressureFromMessageEnd(args) {
35659
36602
  if (requestSucceeded && reportedContextLimit > 0 && reportedContextLimit < provenSafeInputTokens && !meta.cacheAlertSent) {
35660
36603
  updates.cacheAlertSent = true;
35661
36604
  const modelLabel = activeModel.provider && activeModel.id ? `${activeModel.provider}/${activeModel.id}` : "the active model";
35662
- await args.notifyIssue?.(`⚠️ Magic Context: Pi reports a context limit of ${formatTokens(reportedContextLimit)} tokens for ${modelLabel}, but this session has sent ${formatTokens(provenSafeInputTokens)} tokens successfully. Magic Context will keep using the larger proven value for its pressure math. If Pi's model metadata is wrong for your provider, set contextWindow for that model in Pi's model configuration.`);
36605
+ cacheAlert = `⚠️ Magic Context: Pi reports a context limit of ${formatTokens(reportedContextLimit)} tokens for ${modelLabel}, but this session has sent ${formatTokens(provenSafeInputTokens)} tokens successfully. Magic Context will keep using the larger proven value for its pressure math. If Pi's model metadata is wrong for your provider, set contextWindow for that model in Pi's model configuration.`;
35663
36606
  }
35664
36607
  updates.lastContextPercentage = percentage;
35665
36608
  updates.lastInputTokens = pressure.inputTokens;
@@ -35668,13 +36611,21 @@ async function persistPiPressureFromMessageEnd(args) {
35668
36611
  updates.observedSafeInputTokens = provenSafeInputTokens;
35669
36612
  }
35670
36613
  } else if (usage === null && typeof args.piTokens === "number" && (trustedAbsoluteWall === undefined || args.piTokens <= trustedAbsoluteWall)) {
35671
- updates.lastInputTokens = args.piTokens;
35672
- if (effectiveContextLimit > 0) {
35673
- updates.lastContextPercentage = args.piTokens / effectiveContextLimit * 100;
35674
- updates.lastUsageContextLimit = effectiveContextLimit;
36614
+ if (args.piTokensIsRawBranchEstimate === true) {
36615
+ noteRawBranchEstimateSetAside(args.sessionId, args.piTokens, "message_end estimate");
36616
+ } else {
36617
+ notePiUsageReadingUsed(args.sessionId);
36618
+ updates.lastInputTokens = args.piTokens;
36619
+ if (effectiveContextLimit > 0) {
36620
+ updates.lastContextPercentage = args.piTokens / effectiveContextLimit * 100;
36621
+ updates.lastUsageContextLimit = effectiveContextLimit;
36622
+ }
35675
36623
  }
35676
36624
  }
35677
36625
  updateSessionMeta(args.db, args.sessionId, updates);
36626
+ if (cacheAlert !== undefined) {
36627
+ await args.notifyIssue?.(cacheAlert);
36628
+ }
35678
36629
  }
35679
36630
  var PLUGIN_VERSION = (() => {
35680
36631
  try {
@@ -35685,17 +36636,27 @@ var PLUGIN_VERSION = (() => {
35685
36636
  }
35686
36637
  })();
35687
36638
  setHarness(PI_HARNESS_KIND);
35688
- function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND) {
36639
+ function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND, modelRegistry) {
35689
36640
  const historian = config.historian;
35690
36641
  if (historian?.disable === true)
35691
36642
  return;
35692
36643
  const resolved = resolveHistorianModel(config, harness);
35693
- const model = resolved.primary?.model;
36644
+ const validated = modelRegistry && resolved.primary ? validatePiDreamerModels([
36645
+ {
36646
+ task: "classify-memories",
36647
+ schedule: "",
36648
+ timeoutMinutes: 20,
36649
+ model: resolved.primary,
36650
+ fallbackModels: resolved.fallbacks
36651
+ }
36652
+ ], modelRegistry, harness)[0] : undefined;
36653
+ const primary = validated ? typeof validated.model === "string" ? { model: validated.model } : validated.model : resolved.primary;
36654
+ const model = primary?.model;
35694
36655
  if (!model)
35695
36656
  return;
35696
36657
  const historianContextLimit = resolveHistorianContextLimit(model);
35697
36658
  const historianChunkTokens = deriveHistorianChunkTokens(historianContextLimit);
35698
- const fallbackModels = resolved.fallbacks;
36659
+ const fallbackModels = validated?.fallbackModels ?? resolved.fallbacks;
35699
36660
  return {
35700
36661
  runner: new PiSubagentRunner,
35701
36662
  model,
@@ -35706,7 +36667,7 @@ function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND) {
35706
36667
  temperature: historian?.temperature,
35707
36668
  maxOutputTokens: historian?.maxTokens,
35708
36669
  twoPass: historian?.two_pass === true,
35709
- thinkingLevel: resolved.primary?.qualifier,
36670
+ thinkingLevel: primary?.qualifier,
35710
36671
  executeThresholdPercentage: config.execute_threshold_percentage,
35711
36672
  executeThresholdTokens: config.execute_threshold_tokens,
35712
36673
  commitClusterTrigger: config.commit_cluster_trigger,
@@ -35760,7 +36721,7 @@ async function src_default(pi) {
35760
36721
  mmapSizeMb: bootConfig.config.sqlite.mmap_size_mb
35761
36722
  });
35762
36723
  const storageDir = getMagicContextStorageDir();
35763
- const dbPath = join8(storageDir, "context.db");
36724
+ const dbPath = join11(storageDir, "context.db");
35764
36725
  let openFailureCause = null;
35765
36726
  const openStorage = async () => {
35766
36727
  try {
@@ -35770,30 +36731,7 @@ async function src_default(pi) {
35770
36731
  return null;
35771
36732
  }
35772
36733
  };
35773
- const unavailableReason = () => {
35774
- const migration = getMigrationOnOpenRefusal();
35775
- const blockingProcesses = migration?.blockingProcesses ?? migration?.serverPids.map((pid) => ({ kind: "process", pid })) ?? [];
35776
- const fence = getSchemaFenceRejection();
35777
- if (migration && blockingProcesses.length > 0) {
35778
- return {
35779
- kind: "migration_guard",
35780
- persistedVersion: migration.persistedVersion,
35781
- supportedVersion: migration.supportedVersion,
35782
- blockingProcesses
35783
- };
35784
- }
35785
- if (fence) {
35786
- return {
35787
- kind: "schema_fence",
35788
- persistedVersion: fence.persistedVersion,
35789
- supportedVersion: fence.supportedVersion
35790
- };
35791
- }
35792
- return {
35793
- kind: "storage_failure",
35794
- cause: migration?.unreadableFile ? `migration guard could not read RPC discovery file ${migration.unreadableFile}` : openFailureCause ?? `storage unavailable at ${dbPath} (cache schema newer than this binary, or open failed)`
35795
- };
35796
- };
36734
+ const unavailableReason = () => describeStorageUnavailability(openFailureCause ?? `storage unavailable at ${dbPath} (cache schema newer than this binary, or open failed)`);
35797
36735
  const bootResult = await bootPiRuntimeWithDeadline({
35798
36736
  deadlineMs: PI_BOOT_DEADLINE_MS,
35799
36737
  openStorage,
@@ -35888,7 +36826,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
35888
36826
  const projectIdentity = resolveProjectIdentityForSession(projectDir, config.allow_home_project) ?? "";
35889
36827
  if (projectIdentity)
35890
36828
  seenDreamerProjectIdentities.add(projectIdentity);
35891
- info(`loaded v${PLUGIN_VERSION} | harness=${PI_HARNESS_KIND} (via ${PI_HARNESS_DETECTION.via}) | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
36829
+ info(`loaded v${PLUGIN_VERSION} | harness=${PI_HARNESS_KIND} (via ${PI_HARNESS_DETECTION.via}) | db=${dbPath} | ` + `project=${projectIdentity || "(none)"} | dir=${projectDir}`);
35892
36830
  const compactionOff = !isCompactionEnabled(config);
35893
36831
  setCtxReduceRegisteredGlobally(!compactionOff);
35894
36832
  if (!compactionOff) {
@@ -35923,7 +36861,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
35923
36861
  return;
35924
36862
  }
35925
36863
  await ensureProjectRegisteredFromPiDirectory(projectDir, db);
35926
- info(`registered embedding config for project ${projectIdentity ?? "(no project identity; cwd is $HOME)"}`);
36864
+ if (isUsableProjectIdentity(projectIdentity)) {
36865
+ info(`registered embedding config for project ${projectIdentity}`);
36866
+ } else {
36867
+ logNoProjectIdentityOnce(projectDir);
36868
+ }
35927
36869
  const projectDepsByDir = new Map;
35928
36870
  const liveReaders = new Map;
35929
36871
  function liveReaderFor(dir, boot) {
@@ -35947,6 +36889,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
35947
36889
  }
35948
36890
  const buildContextOptions = (cfg, hist, auto) => ({
35949
36891
  db: database,
36892
+ cacheTtlConfig: cfg.cache_ttl,
35950
36893
  smartDrops: cfg.smart_drops === true,
35951
36894
  protectedTokens: cfg.protected_tokens,
35952
36895
  protectedTokenTierOverrides: getProtectedTokensTierOverrides(cfg) ?? {},
@@ -35973,21 +36916,15 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
35973
36916
  language: cfg.language,
35974
36917
  autoSearch: auto,
35975
36918
  resolveForProject: resolveContextOptionsForProject,
36919
+ todowriteEnabled: cfg.todowrite.enabled,
35976
36920
  compactionOff,
35977
36921
  allowHomeProject: cfg.allow_home_project,
35978
36922
  maybeAutoEmbedSession: (sessionId, dir, identity) => {
35979
36923
  maybeAutoEmbedPiSession({
35980
36924
  db: database,
35981
36925
  projectDir: dir,
35982
- projectIdentity: identity,
35983
- memoryEnabled: cfg.memory.enabled
35984
- }, sessionId, dir, identity, (text) => {
35985
- sendCtxStatusMessage(pi, {
35986
- title: "/ctx-embed",
35987
- text,
35988
- level: "info"
35989
- });
35990
- });
36926
+ projectIdentity: identity
36927
+ }, sessionId, dir, identity);
35991
36928
  }
35992
36929
  });
35993
36930
  function buildProjectDeps(dir, identity, cfg, loadMetadata) {
@@ -36041,10 +36978,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36041
36978
  function resolveCurrentProjectDeps(ctx) {
36042
36979
  return resolveProjectDepsForDir(ctx.cwd);
36043
36980
  }
36981
+ let activeModelRegistry;
36044
36982
  function resolveContextOptionsForProject(dir) {
36045
36983
  const project = resolveProjectDepsForDir(dir);
36046
36984
  const sampled = historianRunConfig(project.config, liveReaderFor(dir, project.config).poll().effective);
36047
- const historian = resolveHistorianFromConfig(sampled);
36985
+ const historian = resolveHistorianFromConfig(sampled, PI_HARNESS_KIND, activeModelRegistry);
36048
36986
  return { ...project.contextOptions, historian };
36049
36987
  }
36050
36988
  const bootProjectDeps = buildProjectDeps(projectDir, projectIdentity, config, {
@@ -36053,9 +36991,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36053
36991
  hasDeprecatedProtectedTags
36054
36992
  });
36055
36993
  projectDepsByDir.set(projectDir, bootProjectDeps);
36056
- function syncDreamerProjectRegistration(current) {
36994
+ function syncDreamerProjectRegistration(current, modelRegistry) {
36057
36995
  if (sessionShuttingDown)
36058
36996
  return;
36997
+ if (!isUsableProjectIdentity(current.projectIdentity)) {
36998
+ logNoProjectIdentityOnce(current.projectDir);
36999
+ return;
37000
+ }
36059
37001
  seenDreamerProjectIdentities.add(current.projectIdentity);
36060
37002
  if (!current.dreamerConfig) {
36061
37003
  unregisterPiDreamerProject({
@@ -36070,6 +37012,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36070
37012
  projectIdentity: current.projectIdentity,
36071
37013
  registrationOwner: dreamerRegistrationOwner,
36072
37014
  config: current.dreamerConfig,
37015
+ modelRegistry,
36073
37016
  sampleDreamRun: () => {
36074
37017
  const fresh = liveReaderFor(current.projectDir, current.config).poll().effective;
36075
37018
  const sampled = dreamerRunConfig(current.config, fresh);
@@ -36090,8 +37033,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36090
37033
  });
36091
37034
  }
36092
37035
  registerPiDroppedInputGuard(pi);
36093
- const todowriteEnabled = bootProjectDeps.config.todowrite.enabled !== false;
36094
- const todowriteOverlayEnabled = todowriteEnabled && bootProjectDeps.config.todowrite.overlay !== false;
37036
+ const todowriteEnabled = bootProjectDeps.config.todowrite.enabled;
37037
+ const todowriteOverlayEnabled = todowriteEnabled && bootProjectDeps.config.todowrite.overlay;
36095
37038
  registerMagicContextTools(pi, {
36096
37039
  db,
36097
37040
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
@@ -36111,6 +37054,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36111
37054
  pi.on("session_start", async (event, ctx) => {
36112
37055
  projectDepsByDir.delete(ctx.cwd);
36113
37056
  const current = resolveCurrentProjectDeps(ctx);
37057
+ activeModelRegistry = ctx.modelRegistry;
37058
+ syncDreamerProjectRegistration(current, ctx.modelRegistry);
36114
37059
  syncCtxMemoryToolEnabled(pi, current.config.memory.enabled);
36115
37060
  const failuresToShow = claimConfigParseFailuresOnce("pi", current.configParseFailures);
36116
37061
  if (ctx.hasUI && failuresToShow.length > 0) {
@@ -36135,12 +37080,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36135
37080
  });
36136
37081
  });
36137
37082
  const readLastTodoState = (sessionId) => getOrCreateSessionMeta(db, sessionId).lastTodoState;
36138
- if (todowriteEnabled) {
36139
- registerTodoStateLifecycle(pi, { readLastTodoState });
36140
- }
36141
- const todoOverlay = todowriteOverlayEnabled ? registerTodoOverlay(pi, {
37083
+ const todoOverlay = registerConfiguredTodoLifecycle(pi, {
37084
+ configured: todowriteEnabled,
37085
+ overlay: bootProjectDeps.config.todowrite.overlay,
36142
37086
  readLastTodoState
36143
- }) : undefined;
37087
+ });
36144
37088
  info(todowriteOverlayEnabled ? "registered todowrite overlay" : "registered todowrite overlay: DISABLED (todowrite.enabled=false or todowrite.overlay=false)");
36145
37089
  registerPiContextHandler(pi, bootProjectDeps.contextOptions);
36146
37090
  info(bootProjectDeps.historianConfig ? `registered historian trigger (model=${bootProjectDeps.historianConfig.model}, executeThreshold=${formatExecuteThresholdForLog(bootProjectDeps.historianConfig.executeThresholdPercentage)})` : "registered historian trigger: DISABLED (configure the active harness's historian model in magic-context.jsonc)");
@@ -36189,6 +37133,16 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36189
37133
  runnable: current.dreamerEnabled,
36190
37134
  scheduleSummary: summarizeDreamSchedule(current.config.dreamer)
36191
37135
  },
37136
+ modelChainWarning: (() => {
37137
+ const registry = activeModelRegistry;
37138
+ if (!registry)
37139
+ return;
37140
+ const tasks = validatePiDreamerModels(buildDreamTaskRuntimeConfigs(current.config.dreamer, PI_HARNESS_KIND, current.config.language, current.config.mural.model), registry, PI_HARNESS_KIND);
37141
+ const empty = tasks.filter((task) => task.modelChainUnavailable).map((task) => task.task);
37142
+ if (!resolveHistorianFromConfig(current.config, PI_HARNESS_KIND, registry) && current.historianConfig)
37143
+ empty.push("historian");
37144
+ return empty.length ? `Pi model chain empty (no model found): ${empty.join(", ")}` : undefined;
37145
+ })(),
36192
37146
  activeProfile: current.config.profile,
36193
37147
  cacheTtlConfig: current.config.cache_ttl,
36194
37148
  cacheTtlConfigured: current.cacheTtlConfigured,
@@ -36218,7 +37172,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36218
37172
  resolveRuntimeDeps: (ctx) => {
36219
37173
  const current = resolveCurrentProjectDeps(ctx);
36220
37174
  const fresh = historianRunConfig(current.config, liveReaderFor(current.projectDir, current.config).poll().effective);
36221
- const historian = resolveHistorianFromConfig(fresh) ?? current.historianConfig;
37175
+ const historian = resolveHistorianFromConfig(fresh, PI_HARNESS_KIND, activeModelRegistry);
36222
37176
  return {
36223
37177
  db,
36224
37178
  runner: recompRunner,
@@ -36253,7 +37207,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36253
37207
  resolveRuntimeDeps: (ctx) => {
36254
37208
  const current = resolveCurrentProjectDeps(ctx);
36255
37209
  const fresh = historianRunConfig(current.config, liveReaderFor(current.projectDir, current.config).poll().effective);
36256
- const historian = resolveHistorianFromConfig(fresh) ?? current.historianConfig;
37210
+ const historian = resolveHistorianFromConfig(fresh, PI_HARNESS_KIND, activeModelRegistry);
36257
37211
  return {
36258
37212
  db,
36259
37213
  runner: wrapupRunner,
@@ -36287,7 +37241,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36287
37241
  dreamerEnabled: bootProjectDeps.dreamerEnabled,
36288
37242
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
36289
37243
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity),
36290
- ensureRegistered: (ctx) => syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx)),
37244
+ ensureRegistered: (ctx) => syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry),
36291
37245
  registrationOwner: dreamerRegistrationOwner
36292
37246
  });
36293
37247
  info("registered /ctx-dream");
@@ -36295,8 +37249,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36295
37249
  db,
36296
37250
  projectDir,
36297
37251
  projectIdentity,
36298
- memoryEnabled: bootProjectDeps.config.memory.enabled,
36299
- resolveMemoryEnabled: (ctx) => resolveCurrentProjectDeps(ctx).config.memory.enabled,
36300
37252
  resolveProject: (ctx) => {
36301
37253
  const current = resolveCurrentProjectDeps(ctx);
36302
37254
  return {
@@ -36340,7 +37292,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36340
37292
  };
36341
37293
  const effectiveConfig = effectiveProjectDeps.config;
36342
37294
  try {
36343
- syncDreamerProjectRegistration(effectiveProjectDeps);
37295
+ activeModelRegistry = ctx.modelRegistry;
37296
+ syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
36344
37297
  } catch (err) {
36345
37298
  warn("before_agent_start: dreamer registration sync threw:", err);
36346
37299
  }
@@ -36540,6 +37493,14 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36540
37493
  piContextWindowSource: "catalog",
36541
37494
  piModel: ctx.model,
36542
37495
  piTokens: piUsage && typeof piUsage.tokens === "number" ? piUsage.tokens : undefined,
37496
+ piTokensIsRawBranchEstimate: (() => {
37497
+ try {
37498
+ const branch = ctx.sessionManager.getBranch?.();
37499
+ return isPiLiveUsageRawBranchEstimate(branch);
37500
+ } catch {
37501
+ return false;
37502
+ }
37503
+ })(),
36543
37504
  notifyIssue: async (message) => {
36544
37505
  const uiNotify = ctx.ui?.notify;
36545
37506
  if (typeof uiNotify === "function") {
@@ -36663,6 +37624,7 @@ export {
36663
37624
  handlePiSessionBeforeCompact,
36664
37625
  persistPiMessageEndModelMeta,
36665
37626
  persistPiPressureFromMessageEnd,
37627
+ registerConfiguredTodoLifecycle,
36666
37628
  resetClaimedProtectedTagsDeprecationNoticeForTesting,
36667
37629
  resolveDreamerFromConfig,
36668
37630
  resolveHistorianFromConfig,