@cortexkit/pi-magic-context 0.44.0 → 0.44.2

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-tfzybajk.js";
13
+ } from "./index-59dmj4zs.js";
13
14
  import {
14
15
  COMPACTION_ENABLED_PATH,
15
16
  isDreamerRunnable,
@@ -43,13 +44,12 @@ 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,
52
+ DREAM_TASK_PROMOTION_DEFAULTS,
53
53
  LIVE_RELOAD_CONFIG_PATHS,
54
54
  getProtectedTokensTierOverrides,
55
55
  FAIL_CLOSED_DOCTOR_COMMAND,
@@ -60,12 +60,25 @@ import {
60
60
  createFailClosedController,
61
61
  ProjectIdentityError,
62
62
  resolveProjectIdentityStrict,
63
+ isUserHomeDirectory,
63
64
  resolveProjectIdentity,
65
+ isUsableProjectIdentity,
64
66
  resolveProjectIdentityForSession,
65
67
  beginBootQuietPeriod,
66
68
  scheduleAfterBootQuiet,
67
69
  clearCompressionDepth,
68
70
  clearCompressionDepthRange,
71
+ formatSynapseLaneDescriptor,
72
+ buildCanonicalChunkTextFromFts,
73
+ buildCompartmentSummaryFallbackText,
74
+ canonicalizeInMemoryChunkTextForEmbedding,
75
+ chunkCanonicalText,
76
+ chunkEmbeddingWindowsAreCurrent,
77
+ replaceCompartmentChunkEmbeddings,
78
+ deleteChunkEmbedBackoffForSession,
79
+ readSessionActivity,
80
+ observeSessionActivity,
81
+ backfillSessionActivity,
69
82
  getMessageIndexSourceIdentity,
70
83
  isMessageIndexSourceCurrent,
71
84
  getLastIndexedOrdinal,
@@ -74,42 +87,6 @@ import {
74
87
  indexSingleMessage,
75
88
  indexMessagesAfterOrdinal,
76
89
  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
- getAuthorityManagedMarker,
104
- drainMirrorPages,
105
- V2_MEMORY_CATEGORIES,
106
- parseCompartmentOutput,
107
- resolveWorkspaceShareCategories,
108
- resolveWorkspaceIdentitySet,
109
- expandWorkspaceIdentitySetWithAliases,
110
- sourceNameForMemory,
111
- computeWorkspaceEpochFingerprint,
112
- readProjectDocsCanonical,
113
90
  MAX_EXECUTE_THRESHOLD,
114
91
  escalationBands,
115
92
  HYGIENE_PROVIDER_UNITS_VERSION,
@@ -118,6 +95,10 @@ import {
118
95
  computeProtectionWindow,
119
96
  readEpochFloorSnapshot,
120
97
  getProtectionWindowForSession,
98
+ persistCachedM0,
99
+ clearCachedM0M1,
100
+ readReplayDocument,
101
+ updateReplayDocument,
121
102
  isProviderOverflowFailClosedProven,
122
103
  describeProtectedTailDrainBudgetSkip,
123
104
  loadProtectedTailMeta,
@@ -194,6 +175,51 @@ import {
194
175
  setSessionWorkMetrics,
195
176
  getSessionWorkMetrics,
196
177
  resolveEpochFloorForPass,
178
+ getOrCreateSessionMeta,
179
+ updateSessionMeta,
180
+ advanceToolReclaimWatermark,
181
+ retryPendingSessionCleanups,
182
+ retryPendingRustSessionCleanupsForProject,
183
+ recordSessionProjectIdentity,
184
+ embedPauseBySession,
185
+ embedRunStateBySession,
186
+ autoEmbedAttemptedBySession,
187
+ autoEmbedIdentityBySession,
188
+ invalidateAutoEmbedSession,
189
+ getEmbedDrainUiStatus,
190
+ piHarnessKindFromExecutable,
191
+ COMPARTMENT_LEASE_RENEWAL_MS,
192
+ acquireCompartmentLease,
193
+ renewCompartmentLease,
194
+ releaseCompartmentLease,
195
+ releaseCompartmentLeaseBestEffort,
196
+ isCompartmentLeaseHeld,
197
+ isNoContentCompartment,
198
+ HAS_COMPARTMENT_CONTENT_SQL,
199
+ getCompartments,
200
+ getLastCompartmentEndMessage,
201
+ getLastCompartmentEndMessageId,
202
+ getCompartmentsByEndMessageId,
203
+ appendCompartments,
204
+ saveRecompStagingPass,
205
+ getRecompStaging,
206
+ clearRecompStaging,
207
+ getRecompPartialRange,
208
+ setRecompPartialRange,
209
+ escapeXmlAttr,
210
+ escapeXmlContent,
211
+ getModuleNoteEvaluationBridge,
212
+ getContextStoreUuid,
213
+ getAuthorityManagedMarker,
214
+ drainMirrorPages,
215
+ V2_MEMORY_CATEGORIES,
216
+ parseCompartmentOutput,
217
+ resolveWorkspaceShareCategories,
218
+ resolveWorkspaceIdentitySet,
219
+ expandWorkspaceIdentitySetWithAliases,
220
+ sourceNameForMemory,
221
+ computeWorkspaceEpochFingerprint,
222
+ readProjectDocsCanonical,
197
223
  BoundedSessionMap,
198
224
  registerLkgPersistence,
199
225
  signatureForFields,
@@ -224,6 +250,7 @@ import {
224
250
  runSqliteOptimize,
225
251
  openDatabase,
226
252
  openDatabaseAsync,
253
+ closeDatabase,
227
254
  queueM0Mutation,
228
255
  getMaxM0MutationId,
229
256
  queueMemoryMutation,
@@ -231,11 +258,6 @@ import {
231
258
  getMemoryMutationsForRenderByProjects,
232
259
  getMaxMemoryMutationId,
233
260
  getMaxMemoryMutationIdForProjects,
234
- getOrCreateSessionMeta,
235
- updateSessionMeta,
236
- advanceToolReclaimWatermark,
237
- retryPendingSessionCleanups,
238
- retryPendingRustSessionCleanupsForProject,
239
261
  getNotes,
240
262
  getSessionNotes,
241
263
  getPendingSmartNotes,
@@ -323,13 +345,6 @@ import {
323
345
  peekCurateCategoryScope,
324
346
  beginCurateCategoryRun,
325
347
  curateTaskStateAfterSuccess,
326
- formatSynapseLaneDescriptor,
327
- buildCanonicalChunkTextFromFts,
328
- buildCompartmentSummaryFallbackText,
329
- canonicalizeInMemoryChunkTextForEmbedding,
330
- chunkCanonicalText,
331
- chunkEmbeddingWindowsAreCurrent,
332
- replaceCompartmentChunkEmbeddings,
333
348
  cosineSimilarity,
334
349
  GIT_SWEEP_LEASE_RENEWAL_MS,
335
350
  acquireGitSweepLease,
@@ -351,6 +366,7 @@ import {
351
366
  drainCommitBacklogForProject,
352
367
  embedSessionCompartmentChunks,
353
368
  getEmbeddingCoverageStatus,
369
+ getAutoEmbeddingSessionCoverage,
354
370
  promoteSessionFactsDurable,
355
371
  embedPromotedFacts,
356
372
  recordMemoryMapping,
@@ -371,9 +387,12 @@ import {
371
387
  embedUnembeddedCommits,
372
388
  loadPiConfig,
373
389
  loadPiConfigDetailed,
390
+ unregisterPiProjectEmbeddings,
374
391
  ensureProjectRegisteredFromPiDirectory,
375
392
  resolvePiHarnessDetection,
376
393
  resolvePiHarnessKind,
394
+ MEMORY_MURAL_BLOCK,
395
+ buildMagicContextSection,
377
396
  resolveMuralWire,
378
397
  updateCompactionMarkerAfterPublication,
379
398
  COMPARTMENT_RENDER_EPOCH,
@@ -404,7 +423,7 @@ import {
404
423
  registerTodoStateLifecycle,
405
424
  syncCtxMemoryToolEnabled,
406
425
  registerMagicContextTools
407
- } from "./index-8ytss1hp.js";
426
+ } from "./index-esnhck8v.js";
408
427
  import {
409
428
  CTX_REDUCE_KEEP,
410
429
  newestCtxReduceTagNumbers,
@@ -483,6 +502,7 @@ import {
483
502
  claimOpenCodeDbDiagnosticOnce,
484
503
  Database,
485
504
  closeQuietly,
505
+ RAW_SUMMARY_TEXT_MAX_CHARS,
486
506
  completedToolArcCrossesBoundary,
487
507
  estimateTrueRawMessageTokens,
488
508
  buildToolArcs,
@@ -491,6 +511,12 @@ import {
491
511
  buildTrueRawTokenIndex,
492
512
  computeRawRangeFingerprint,
493
513
  invalidateTrueRawTokenCache,
514
+ DreamTokenBudgetExceeded,
515
+ TOKEN_BUDGET_FINALIZE_MESSAGE,
516
+ TOKEN_BUDGET_TOOL_REFUSAL,
517
+ registerBudgetFinalizeChild,
518
+ releaseBudgetFinalizeChild,
519
+ createDreamTokenBudget,
494
520
  droppedInputMarker,
495
521
  createDroppedInputGuard,
496
522
  isEditTool,
@@ -507,6 +533,7 @@ import {
507
533
  withRawSessionMessageCache2,
508
534
  readRawSessionMessages2,
509
535
  getRawSessionMessageOrdinalCount2,
536
+ visitRawSessionMessages2,
510
537
  readRawSessionMessageRange2,
511
538
  primeTailRawMessageCache2,
512
539
  getCachedAbsoluteMessageCount2,
@@ -516,14 +543,18 @@ import {
516
543
  getRawSessionTagKeysThrough2,
517
544
  getLegacyProtectedTailStartOrdinal2,
518
545
  readSessionChunk2
519
- } from "./index-2kj55q4e.js";
546
+ } from "./index-gsff513e.js";
520
547
  import {
521
548
  pushNotification2
522
549
  } from "./index-b3eqj1g6.js";
523
- import"./index-jscq7067.js";
550
+ import {
551
+ subagentStepCap,
552
+ SubagentTelemetry
553
+ } from "./index-n8kg008j.js";
554
+ import"./index-5fw91enm.js";
524
555
  import {
525
556
  logSlowWriteTransaction
526
- } from "./index-mmkdxa5j.js";
557
+ } from "./index-97e5fzy3.js";
527
558
  import {
528
559
  __toESM
529
560
  } from "./index-22jkk6wg.js";
@@ -531,7 +562,7 @@ import {
531
562
  // src/index.ts
532
563
  import { AsyncLocalStorage } from "node:async_hooks";
533
564
  import { createRequire as createRequire2 } from "node:module";
534
- import { join as join8, resolve as resolve4 } from "node:path";
565
+ import { join as join11, resolve as resolve4 } from "node:path";
535
566
 
536
567
  // ../plugin/src/config/live-snapshot.ts
537
568
  import { createHash } from "node:crypto";
@@ -816,6 +847,15 @@ var CANONICAL_DREAM_TASKS = [
816
847
  "promote-primers",
817
848
  "refresh-primers"
818
849
  ];
850
+ var DREAM_TOOL_LOOP_TOKEN_BUDGETS = {
851
+ "map-memories": 1500000,
852
+ verify: 1700000,
853
+ "verify-broad": 1700000,
854
+ curate: 1500000,
855
+ retrospective: 300000,
856
+ "maintain-docs": 1600000,
857
+ "refresh-primers": 350000
858
+ };
819
859
  var DREAM_TASK_CAPABILITIES = {
820
860
  "map-memories": {
821
861
  requiresTools: true,
@@ -928,6 +968,7 @@ function buildDreamTaskRuntimeConfigs(dreamer, harness, language, muralModel) {
928
968
  thinkingLevel: harness === "pi" ? resolved.primary?.qualifier : undefined,
929
969
  language,
930
970
  timeoutMinutes: resolved.timeoutMinutes ?? 20,
971
+ tokenBudget: task in DREAM_TOOL_LOOP_TOKEN_BUDGETS ? dreamer?.tasks?.[task]?.token_budget ?? DREAM_TOOL_LOOP_TOKEN_BUDGETS[task] : undefined,
931
972
  promotionThreshold: resolved.promotionThreshold,
932
973
  docsMaxTokens: task === "maintain-docs" ? dreamer?.tasks?.["maintain-docs"]?.max_tokens ?? 12000 : undefined,
933
974
  retrospectiveRecencyDays: task === "retrospective" ? dreamer?.tasks?.retrospective?.recency_days ?? 30 : undefined
@@ -1319,6 +1360,67 @@ function parseReportedLimit(message) {
1319
1360
  return;
1320
1361
  }
1321
1362
 
1363
+ // ../plugin/src/shared/model-cache-ttl.ts
1364
+ var MODEL_CACHE_LIFETIMES = [
1365
+ {
1366
+ source: "OpenAI GPT-5.6+ default",
1367
+ value: "30m",
1368
+ matches(model) {
1369
+ const version = /^gpt-(\d+)(?:\.(\d+))?(?:$|[^\d.])/.exec(model);
1370
+ if (!version)
1371
+ return false;
1372
+ const major = Number(version[1]);
1373
+ const minor = Number(version[2] ?? 0);
1374
+ return major > 5 || major === 5 && minor >= 6;
1375
+ }
1376
+ }
1377
+ ];
1378
+ function resolveModelCacheTtl(config, modelKey) {
1379
+ if (config && typeof config !== "string") {
1380
+ const match = modelKey && !modelKey.includes("/") && Object.hasOwn(config, modelKey) && modelKey !== "default" ? config[modelKey] : resolveModelConfigValue(config, modelKey)?.value;
1381
+ if (match !== undefined)
1382
+ return { value: match, source: "config", modelKey };
1383
+ }
1384
+ if (typeof config === "string" && config !== "5m")
1385
+ return { value: config, source: "config", modelKey };
1386
+ const model = canonicalModelIdentity(modelKey ?? "").toLowerCase().split("/").at(-1) ?? "";
1387
+ const known = MODEL_CACHE_LIFETIMES.find((entry) => entry.matches(model));
1388
+ if (known)
1389
+ return { value: known.value, source: known.source, modelKey };
1390
+ return {
1391
+ value: (typeof config === "object" ? config.default : config) ?? "5m",
1392
+ source: "default",
1393
+ modelKey
1394
+ };
1395
+ }
1396
+
1397
+ // ../plugin/src/features/magic-context/session-cache-ttl.ts
1398
+ function readSessionCacheTtl(db, sessionId) {
1399
+ const saved = readReplayDocument(db, sessionId).cacheTtlPolicy;
1400
+ return saved && typeof saved.value === "string" && saved.config !== undefined ? saved : undefined;
1401
+ }
1402
+ function resolveSessionCacheTtl(db, sessionId, config, modelKey) {
1403
+ const meta = getOrCreateSessionMeta(db, sessionId);
1404
+ let saved = readSessionCacheTtl(db, sessionId);
1405
+ if (!modelKey)
1406
+ return saved ?? resolveModelCacheTtl(config ?? meta.cacheTtl, undefined);
1407
+ if (!saved || saved.modelKey !== modelKey) {
1408
+ const frozenConfig = saved?.config ?? config ?? meta.cacheTtl;
1409
+ const resolved = resolveModelCacheTtl(frozenConfig, modelKey);
1410
+ const next = { ...resolved, config: frozenConfig };
1411
+ if (!updateReplayDocument(db, sessionId, (doc) => {
1412
+ doc.version = 2;
1413
+ doc.cacheTtlPolicy = next;
1414
+ return true;
1415
+ }))
1416
+ throw new Error("cannot persist session cache TTL policy");
1417
+ saved = next;
1418
+ }
1419
+ if (meta.cacheTtl !== saved.value)
1420
+ updateSessionMeta(db, sessionId, { cacheTtl: saved.value });
1421
+ return saved;
1422
+ }
1423
+
1322
1424
  // ../plugin/src/features/magic-context/session-project-backfill.ts
1323
1425
  import { randomUUID } from "node:crypto";
1324
1426
  import { existsSync } from "node:fs";
@@ -1460,7 +1562,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1460
1562
  const startedAt = performance.now();
1461
1563
  const harness = getHarness();
1462
1564
  const now = options.now ?? Date.now;
1463
- const resolveIdentity = options.resolveIdentity ?? resolveProjectIdentity;
1565
+ const resolveIdentity = options.resolveIdentity ?? ((directory) => resolveProjectIdentityForSession(directory, options.allowHomeProject));
1464
1566
  const yieldFn = options.yieldFn ?? defaultYieldFn;
1465
1567
  const holderId = options.holderId ?? randomUUID();
1466
1568
  const readPage = createPageReader(source);
@@ -1474,7 +1576,8 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1474
1576
  skippedEmptyDirectories: 0,
1475
1577
  durationMs: 0
1476
1578
  };
1477
- const leaseStatus = acquireBackfillLease(db, harness, holderId, now());
1579
+ const leaseKey = options.leaseKey ?? harness;
1580
+ const leaseStatus = acquireBackfillLease(db, leaseKey, holderId, now());
1478
1581
  if (leaseStatus !== "acquired") {
1479
1582
  result.status = leaseStatus;
1480
1583
  result.durationMs = performance.now() - startedAt;
@@ -1486,10 +1589,10 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1486
1589
  return result;
1487
1590
  };
1488
1591
  const yieldAndRenew = async () => {
1489
- if (!renewBackfillLease(db, harness, holderId, now()))
1592
+ if (!renewBackfillLease(db, leaseKey, holderId, now()))
1490
1593
  return false;
1491
1594
  await yieldFn();
1492
- return renewBackfillLease(db, harness, holderId, now());
1595
+ return renewBackfillLease(db, leaseKey, holderId, now());
1493
1596
  };
1494
1597
  const existenceCache = new Map;
1495
1598
  const identityCache = new Map;
@@ -1503,7 +1606,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1503
1606
  sourcePage = await readPage(afterSessionId, SESSION_PAGE_SIZE);
1504
1607
  } catch (error) {
1505
1608
  try {
1506
- if (!markBackfillRetryPending(db, harness, holderId, now())) {
1609
+ if (!markBackfillRetryPending(db, leaseKey, holderId, now())) {
1507
1610
  log("[session-projects] backfill lease changed before failure cleanup");
1508
1611
  }
1509
1612
  } catch (releaseError) {
@@ -1528,7 +1631,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1528
1631
  result.alreadyMappedSessions += 1;
1529
1632
  } else {
1530
1633
  result.unmappedSessions += 1;
1531
- if (!session.directory) {
1634
+ if (!session.directory || !options.allowHomeProject && isUserHomeDirectory(session.directory)) {
1532
1635
  result.skippedEmptyDirectories += 1;
1533
1636
  } else {
1534
1637
  const stillExists = existenceCache.get(session.directory) ?? existsSync(session.directory);
@@ -1546,13 +1649,18 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1546
1649
  }
1547
1650
  identity = await resolveIdentity(session.directory);
1548
1651
  identityResolutionsSinceYield += 1;
1549
- if (!renewBackfillLease(db, harness, holderId, now())) {
1652
+ if (!renewBackfillLease(db, leaseKey, holderId, now())) {
1550
1653
  return finishWithLostLease();
1551
1654
  }
1552
- identityCache.set(session.directory, identity);
1655
+ if (identity)
1656
+ identityCache.set(session.directory, identity);
1657
+ }
1658
+ if (identity) {
1659
+ recordSessionProjectIdentity(db, session.sessionId, identity);
1660
+ result.backfilledSessions += 1;
1661
+ } else {
1662
+ result.skippedEmptyDirectories += 1;
1553
1663
  }
1554
- recordSessionProjectIdentity(db, session.sessionId, identity);
1555
- result.backfilledSessions += 1;
1556
1664
  }
1557
1665
  }
1558
1666
  }
@@ -1568,11 +1676,11 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1568
1676
  }
1569
1677
  const hasSkippedSessions = result.skippedDeadDirectories > 0 || result.skippedEmptyDirectories > 0;
1570
1678
  if (hasSkippedSessions) {
1571
- if (!markBackfillRetryPending(db, harness, holderId, now())) {
1679
+ if (!markBackfillRetryPending(db, leaseKey, holderId, now())) {
1572
1680
  return finishWithLostLease();
1573
1681
  }
1574
1682
  result.status = "retry_pending";
1575
- } else if (!markBackfillCompleted(db, harness, holderId, now())) {
1683
+ } else if (!markBackfillCompleted(db, leaseKey, holderId, now())) {
1576
1684
  return finishWithLostLease();
1577
1685
  }
1578
1686
  result.durationMs = performance.now() - startedAt;
@@ -1642,7 +1750,7 @@ function countFailures(db) {
1642
1750
  function classifyBackfillError(error) {
1643
1751
  if (error instanceof ProjectIdentityError) {
1644
1752
  return {
1645
- errorClass: error.errorClass === "dubious_ownership" ? "unknown" : error.errorClass,
1753
+ errorClass: error.errorClass === "dubious_ownership" || error.errorClass === "home_project_disabled" || error.errorClass === "git_identity_unavailable" ? "unknown" : error.errorClass,
1646
1754
  errorMessage: error.message
1647
1755
  };
1648
1756
  }
@@ -1896,141 +2004,6 @@ function producerSourceLocalBudget(providerTokens, modelKey) {
1896
2004
  return ratio === 1 ? providerTokens : localBudget(providerTokens, ratio);
1897
2005
  }
1898
2006
 
1899
- // ../plugin/src/hooks/magic-context/event-resolvers.ts
1900
- var DEFAULT_CONTEXT_LIMIT = 200000;
1901
- function resolveCacheTtl(cacheTtl, modelKey) {
1902
- if (typeof cacheTtl === "string") {
1903
- return cacheTtl;
1904
- }
1905
- return resolveModelConfigOrDefault(cacheTtl, modelKey, cacheTtl.default ?? "5m");
1906
- }
1907
- var clampWarnSeen = new Set;
1908
- function isFinitePositive(v) {
1909
- return typeof v === "number" && Number.isFinite(v) && v > 0;
1910
- }
1911
- function* modelKeyLookupOrder(modelKey) {
1912
- const slash = modelKey.indexOf("/");
1913
- const providerRefs = slash >= 0 ? modelRefLookupOrder(modelKey) : [];
1914
- let modelId = slash >= 0 ? modelKey.slice(slash + 1) : modelKey;
1915
- while (modelId.length > 0) {
1916
- for (const providerRef of providerRefs) {
1917
- const providerSlash = providerRef.indexOf("/");
1918
- yield `${providerRef.slice(0, providerSlash)}/${modelId}`;
1919
- }
1920
- yield modelId;
1921
- const lastDash = modelId.lastIndexOf("-");
1922
- if (lastDash <= 0)
1923
- break;
1924
- modelId = modelId.slice(0, lastDash);
1925
- }
1926
- }
1927
- function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
1928
- if (options?.tokensConfig && isFinitePositive(options.contextLimit)) {
1929
- const contextLimit = options.contextLimit;
1930
- const tokenMatch = resolveTokensMatchWithKey(options.tokensConfig, modelKey);
1931
- if (tokenMatch && isFinitePositive(tokenMatch.value)) {
1932
- const cap = contextLimit * (MAX_EXECUTE_THRESHOLD / 100);
1933
- const effectiveTokens = Math.min(tokenMatch.value, cap);
1934
- if (effectiveTokens < tokenMatch.value) {
1935
- const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
1936
- if (!clampWarnSeen.has(dedupeKey)) {
1937
- clampWarnSeen.add(dedupeKey);
1938
- const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (${MAX_EXECUTE_THRESHOLD}% of ${contextLimit}) for ${modelKey ?? "default"}`;
1939
- if (options.sessionId) {
1940
- sessionLog(options.sessionId, `WARN: ${msg}`);
1941
- } else {
1942
- log(`[magic-context] WARN: ${msg}`);
1943
- }
1944
- }
1945
- }
1946
- const percentage = effectiveTokens / contextLimit * 100;
1947
- const detail = {
1948
- percentage: Math.min(percentage, MAX_EXECUTE_THRESHOLD),
1949
- mode: "tokens",
1950
- absoluteTokens: Math.floor(effectiveTokens),
1951
- matchedKey: tokenMatch.matchedKey
1952
- };
1953
- if (effectiveTokens < tokenMatch.value) {
1954
- detail.clamped = true;
1955
- detail.configuredValue = tokenMatch.value;
1956
- }
1957
- return detail;
1958
- }
1959
- }
1960
- let resolved;
1961
- let matchedKey;
1962
- if (typeof config === "number") {
1963
- resolved = config;
1964
- } else if (modelKey) {
1965
- let matched;
1966
- for (const candidate of modelKeyLookupOrder(modelKey)) {
1967
- if (typeof config[candidate] === "number") {
1968
- matched = config[candidate];
1969
- matchedKey = candidate;
1970
- break;
1971
- }
1972
- }
1973
- if (matched === undefined && typeof config.default === "number") {
1974
- resolved = config.default;
1975
- matchedKey = "default";
1976
- } else {
1977
- resolved = matched ?? fallback;
1978
- }
1979
- } else if (typeof config.default === "number") {
1980
- resolved = config.default;
1981
- matchedKey = "default";
1982
- } else {
1983
- resolved = fallback;
1984
- }
1985
- if (!Number.isFinite(resolved) || resolved < 0) {
1986
- resolved = fallback;
1987
- }
1988
- const cappedPercentage = Math.min(resolved, MAX_EXECUTE_THRESHOLD);
1989
- const percentageClamped = cappedPercentage < resolved;
1990
- if (percentageClamped) {
1991
- const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
1992
- if (!clampWarnSeen.has(dedupeKey)) {
1993
- clampWarnSeen.add(dedupeKey);
1994
- 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)`;
1995
- if (options?.sessionId) {
1996
- sessionLog(options.sessionId, `WARN: ${msg}`);
1997
- } else {
1998
- log(`[magic-context] WARN: ${msg}`);
1999
- }
2000
- }
2001
- }
2002
- const detail = {
2003
- percentage: cappedPercentage,
2004
- mode: "percentage",
2005
- matchedKey
2006
- };
2007
- if (percentageClamped) {
2008
- detail.clamped = true;
2009
- detail.configuredValue = resolved;
2010
- }
2011
- return detail;
2012
- }
2013
- function resolveExecuteThreshold(config, modelKey, fallback, options) {
2014
- return resolveExecuteThresholdDetail(config, modelKey, fallback, options).percentage;
2015
- }
2016
- function resolveTokensMatchWithKey(tokensConfig, modelKey) {
2017
- if (!tokensConfig) {
2018
- return;
2019
- }
2020
- if (modelKey) {
2021
- for (const candidate of modelKeyLookupOrder(modelKey)) {
2022
- const value = tokensConfig[candidate];
2023
- if (typeof value === "number") {
2024
- return { value, matchedKey: candidate };
2025
- }
2026
- }
2027
- }
2028
- if (typeof tokensConfig.default === "number") {
2029
- return { value: tokensConfig.default, matchedKey: "default" };
2030
- }
2031
- return;
2032
- }
2033
-
2034
2007
  // ../plugin/src/hooks/magic-context/note-nudger.ts
2035
2008
  var NOTE_NUDGE_COOLDOWN_MS = 15 * 60 * 1000;
2036
2009
  var lastDeliveredAt = new Map;
@@ -2381,10 +2354,12 @@ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
2381
2354
  class SmartNoteNetworkError extends Error {
2382
2355
  isSmartNoteNetworkError = true;
2383
2356
  terminal;
2357
+ persistent;
2384
2358
  constructor(message, options = {}) {
2385
2359
  super(message);
2386
2360
  this.name = "SmartNoteNetworkError";
2387
2361
  this.terminal = options.terminal ?? false;
2362
+ this.persistent = options.persistent ?? false;
2388
2363
  }
2389
2364
  }
2390
2365
 
@@ -2506,6 +2481,7 @@ function storeCompiledSmartNoteCheck(db, args) {
2506
2481
  check_failure_count = 0,
2507
2482
  check_network_failure_count = 0,
2508
2483
  check_quarantined_until = NULL,
2484
+ ready_reason = NULL,
2509
2485
  check_next_due_at = ?,
2510
2486
  check_compiled_at = ?,
2511
2487
  check_false_since_at = COALESCE(check_false_since_at, ?),
@@ -2554,15 +2530,17 @@ function markSmartNoteLivenessChecked(db, noteId, now) {
2554
2530
  function markSmartNoteCheckStatus(db, noteId, status, now) {
2555
2531
  db.prepare(`UPDATE notes SET check_status = ?, updated_at = ? WHERE id = ? AND type = 'smart'`).run(status, now, noteId);
2556
2532
  }
2557
- function markSmartNoteCompilationFailure(db, noteId, now, maxFailures) {
2533
+ function markSmartNoteCompilationFailure(db, noteId, now, maxFailures, error, persistent) {
2558
2534
  const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
2559
- const status = failureCount >= maxFailures ? "fallback" : "uncompiled";
2535
+ const status = persistent ? "uncompiled" : failureCount >= maxFailures ? "fallback" : "uncompiled";
2536
+ const nextDueAt = now + (persistent ? 7 * 24 * 60 * 60 * 1000 : backoffMs(failureCount));
2560
2537
  db.prepare(`UPDATE notes
2561
2538
  SET check_failure_count = ?,
2562
2539
  check_status = ?,
2563
2540
  check_next_due_at = ?,
2541
+ ready_reason = ?,
2564
2542
  updated_at = ?
2565
- WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
2543
+ WHERE id = ? AND type = 'smart'`).run(failureCount, status, nextDueAt, persistent ? `Condition can't be checked: ${error}; rewrite it` : null, now, noteId);
2566
2544
  }
2567
2545
  function readFailureCount(db, noteId, column) {
2568
2546
  if (column !== "check_failure_count" && column !== "check_network_failure_count")
@@ -2610,7 +2588,10 @@ function countCompartmentsSince(db, projectPath, since) {
2610
2588
  return row?.cnt ?? 0;
2611
2589
  }
2612
2590
  function countProjectSessionsSince(db, projectPath, since) {
2613
- const row = since === null ? db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ?").get(projectPath) : db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ? AND updated_at > ?").get(projectPath, since);
2591
+ const row = since === null ? db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ?").get(projectPath) : db.prepare(`SELECT COUNT(*) AS cnt FROM session_projects sp
2592
+ JOIN schema_migrations_meta activity
2593
+ ON activity.key = 'retrospective_activity:' || sp.session_id
2594
+ WHERE sp.project_path = ? AND CAST(activity.value AS INTEGER) > ?`).get(projectPath, since);
2614
2595
  return row?.cnt ?? 0;
2615
2596
  }
2616
2597
  function countMappedMemories(db, projectPath) {
@@ -2719,12 +2700,14 @@ function getDreamTaskBacklog(db, projectPath, task, options = {}) {
2719
2700
  };
2720
2701
  }
2721
2702
  case "retrospective": {
2722
- const pending = countProjectSessionsSince(db, projectPath, options.retrospectiveWatermarkMs ?? null);
2703
+ const watermark = options.retrospectiveWatermarkMs !== undefined ? options.retrospectiveWatermarkMs : getTaskScheduleState(db, projectPath, task)?.retrospectiveWatermarkMs;
2704
+ const pending = countProjectSessionsSince(db, projectPath, watermark ?? null);
2723
2705
  return { pending, total: pending };
2724
2706
  }
2725
2707
  case "maintain-docs": {
2726
2708
  const total = countCompartmentsSince(db, projectPath, 0);
2727
- const pending = countCompartmentsSince(db, projectPath, options.lastRunAt ?? 0);
2709
+ const lastRunAt = options.lastRunAt !== undefined ? options.lastRunAt : getTaskScheduleState(db, projectPath, task)?.lastRunAt;
2710
+ const pending = countCompartmentsSince(db, projectPath, lastRunAt ?? 0);
2728
2711
  return { pending, total };
2729
2712
  }
2730
2713
  case "evaluate-smart-notes": {
@@ -2779,7 +2762,7 @@ function evaluateTaskGate(task, ctx) {
2779
2762
  case "review-user-memories":
2780
2763
  return getUserMemoryCandidates(db).length >= ctx.promotionThreshold;
2781
2764
  case "promote-primers":
2782
- return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? 2);
2765
+ return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"]);
2783
2766
  case "refresh-primers":
2784
2767
  return getActivePrimers(db, project).some((primer) => !primer.answer.trim() || primer.answerRefreshedAt == null || (primer.lastObservedAt ?? 0) > primer.answerRefreshedAt);
2785
2768
  default: {
@@ -2826,6 +2809,11 @@ var USER_FACING_FAILURES = {
2826
2809
  sentence: "Memory maintenance stopped at its hidden agent step limit.",
2827
2810
  action: "This task needs less work per run; changing the model connection will not help."
2828
2811
  },
2812
+ dream_token_budget: {
2813
+ code: "MC-D11",
2814
+ sentence: "Memory maintenance reached its prompt-token budget.",
2815
+ action: "The unfinished items will be retried on the next run."
2816
+ },
2829
2817
  dream_empty_completion: {
2830
2818
  code: "MC-D03",
2831
2819
  sentence: "Memory maintenance received no usable response.",
@@ -3043,6 +3031,7 @@ var DREAM_FAILURE_KEYS = {
3043
3031
  provider_timeout: "dream_provider_timeout",
3044
3032
  provider_error: "dream_provider_error",
3045
3033
  step_limit: "dream_step_limit",
3034
+ token_budget: "dream_token_budget",
3046
3035
  empty_completion: "dream_empty_completion",
3047
3036
  no_models: "dream_no_models",
3048
3037
  child_aborted: "dream_child_aborted",
@@ -3078,6 +3067,8 @@ import { parse, resolve as resolve3 } from "node:path";
3078
3067
 
3079
3068
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
3080
3069
  function openOpenCodeDb() {
3070
+ if (!harnessOwnsOpenCodeStore())
3071
+ return null;
3081
3072
  const resolution = resolveOpenCodeDbPath();
3082
3073
  const dbPath = resolution.path;
3083
3074
  if (!openCodeDbPathExists(resolution)) {
@@ -3108,7 +3099,7 @@ function openOpenCodeDb() {
3108
3099
 
3109
3100
  // ../plugin/src/features/magic-context/dreamer/task-executor.ts
3110
3101
  import { createHash as createHash10 } from "node:crypto";
3111
- import { existsSync as existsSync6 } from "node:fs";
3102
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
3112
3103
 
3113
3104
  // ../plugin/src/agents/dreamer.ts
3114
3105
  var DREAMER_AGENT = "dreamer";
@@ -3241,7 +3232,7 @@ var READ_TOOLS = ["read", "grep", "glob"];
3241
3232
  var AGENT_TOOLS = {
3242
3233
  [HIDDEN_HISTORIAN_AGENT]: [],
3243
3234
  [HIDDEN_DREAMER_AGENT]: [],
3244
- [HIDDEN_CURATE_AGENT]: ["ctx_memory", "ctx_memory_list"],
3235
+ [HIDDEN_CURATE_AGENT]: ["ctx_memory"],
3245
3236
  [DREAMER_MEMORY_MAPPER_AGENT]: READ_TOOLS,
3246
3237
  [DREAMER_PRIMER_INVESTIGATOR_AGENT]: [...READ_TOOLS, "ctx_search"],
3247
3238
  [DREAMER_RETROSPECTIVE_AGENT]: ["ctx_search"]
@@ -3490,7 +3481,7 @@ async function abortChildRun(client, sessionId) {
3490
3481
  }
3491
3482
  }
3492
3483
  function isNonRetryable(error, externalSignal) {
3493
- if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit)
3484
+ if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit || error instanceof DreamTokenBudgetExceeded)
3494
3485
  return true;
3495
3486
  if (error instanceof Error) {
3496
3487
  if (error.name === "AbortError")
@@ -3516,6 +3507,8 @@ function classifyPromptFailure(error, phase, externalSignal) {
3516
3507
  const message = extractMessage(error);
3517
3508
  if (error instanceof HiddenAgentStepLimit)
3518
3509
  return "step_limit";
3510
+ if (error instanceof DreamTokenBudgetExceeded)
3511
+ return "token_budget";
3519
3512
  if (externalSignal?.aborted || message === "prompt aborted by external signal") {
3520
3513
  return "child_aborted";
3521
3514
  }
@@ -3766,10 +3759,115 @@ function normalizeSDKResponse(response, fallback, options) {
3766
3759
  }
3767
3760
  return fallback;
3768
3761
  }
3762
+ // ../plugin/src/features/magic-context/subagent-token-capture.ts
3763
+ function asNumber(value) {
3764
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
3765
+ }
3766
+ function tokenObjectFromMessage(message) {
3767
+ const info = message.info;
3768
+ if (info && typeof info === "object") {
3769
+ const tokens = info.tokens;
3770
+ if (tokens && typeof tokens === "object")
3771
+ return tokens;
3772
+ }
3773
+ const tokens = message.tokens;
3774
+ if (tokens && typeof tokens === "object")
3775
+ return tokens;
3776
+ const usage = message.usage;
3777
+ if (usage && typeof usage === "object")
3778
+ return usage;
3779
+ return null;
3780
+ }
3781
+ function isAssistantMessage(message) {
3782
+ if (!message || typeof message !== "object")
3783
+ return false;
3784
+ const record = message;
3785
+ const info = record.info;
3786
+ if (info && typeof info === "object") {
3787
+ return info.role === "assistant";
3788
+ }
3789
+ return record.role === "assistant";
3790
+ }
3791
+ function modelFromMessage(message) {
3792
+ const info = message.info;
3793
+ const source = info && typeof info === "object" ? info : message;
3794
+ return {
3795
+ providerId: typeof source.providerID === "string" ? source.providerID : typeof source.providerId === "string" ? source.providerId : null,
3796
+ modelId: typeof source.modelID === "string" ? source.modelID : typeof source.modelId === "string" ? source.modelId : null
3797
+ };
3798
+ }
3799
+ function failedInvocationStatus(error) {
3800
+ const message = describeError(error).brief;
3801
+ if (/timed out after \d+ms|prompt timed out/i.test(message))
3802
+ return "timed_out";
3803
+ if (error !== null && typeof error === "object" && error.name === "TimeoutError") {
3804
+ return "timed_out";
3805
+ }
3806
+ if (/length-capped|no (?:assistant )?output|empty (?:assistant )?(?:output|text)/i.test(message)) {
3807
+ return "empty";
3808
+ }
3809
+ return "failed";
3810
+ }
3811
+ function emptyTokenTotals() {
3812
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
3813
+ }
3814
+ function sumTokensFromChildMessages(messages) {
3815
+ const totals = emptyTokenTotals();
3816
+ for (const message of messages) {
3817
+ if (!isAssistantMessage(message))
3818
+ continue;
3819
+ const tokens = tokenObjectFromMessage(message);
3820
+ if (!tokens)
3821
+ continue;
3822
+ const cache = tokens.cache && typeof tokens.cache === "object" ? tokens.cache : {};
3823
+ totals.input += asNumber(tokens.input);
3824
+ totals.output += asNumber(tokens.output);
3825
+ totals.cacheRead += asNumber(cache.read ?? tokens.cacheRead ?? tokens.cache_read);
3826
+ totals.cacheWrite += asNumber(cache.write ?? tokens.cacheWrite ?? tokens.cache_write);
3827
+ }
3828
+ return totals;
3829
+ }
3830
+ function findLastAssistantModel(messages) {
3831
+ for (let index = messages.length - 1;index >= 0; index -= 1) {
3832
+ const message = messages[index];
3833
+ if (isAssistantMessage(message))
3834
+ return modelFromMessage(message);
3835
+ }
3836
+ return { providerId: null, modelId: null };
3837
+ }
3838
+ function recordChildInvocation(input) {
3839
+ if (!input.db)
3840
+ return null;
3841
+ const tokens = input.tokens ?? sumTokensFromChildMessages(input.messages ?? []);
3842
+ const model = input.providerId !== undefined || input.modelId !== undefined ? { providerId: input.providerId ?? null, modelId: input.modelId ?? null } : findLastAssistantModel(input.messages ?? []);
3843
+ try {
3844
+ return recordSubagentInvocation(input.db, {
3845
+ sessionId: input.parentSessionId,
3846
+ harness: input.harness,
3847
+ subagent: input.subagent,
3848
+ task: input.task ?? null,
3849
+ providerId: model.providerId,
3850
+ modelId: model.modelId,
3851
+ startedAt: input.startedAt,
3852
+ endedAt: input.endedAt ?? Date.now(),
3853
+ status: input.status,
3854
+ inputTokens: tokens.input,
3855
+ outputTokens: tokens.output,
3856
+ cacheReadTokens: tokens.cacheRead,
3857
+ cacheWriteTokens: tokens.cacheWrite,
3858
+ error: input.error ? describeError(input.error).brief : null,
3859
+ parentInvocationId: input.parentInvocationId ?? null
3860
+ });
3861
+ } catch (error) {
3862
+ sessionLog(input.parentSessionId, "subagent token accounting failed:", describeError(error).brief);
3863
+ return null;
3864
+ }
3865
+ }
3866
+
3769
3867
  // ../plugin/src/shared/prompt-async-transport.ts
3770
3868
  var DEFAULT_POLL_INTERVAL_MS = 1000;
3771
3869
  var DEFAULT_START_GRACE_MS = 30000;
3772
- var MESSAGE_WINDOW = 20;
3870
+ var MESSAGE_WINDOW = 100;
3773
3871
  function sessionApi(client) {
3774
3872
  return client?.session;
3775
3873
  }
@@ -3780,7 +3878,8 @@ function supportsPromptAsync(client) {
3780
3878
  function createPromptAsyncTransport(client, childSessionId, options = {}) {
3781
3879
  if (!supportsPromptAsync(client))
3782
3880
  return;
3783
- return Object.assign((request) => promptAsyncAndWaitForIdle(client, request, options), { childSessionId });
3881
+ const budgetGuard = options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined;
3882
+ return Object.assign((request) => promptAsyncAndWaitForIdle(client, request, { ...options, budgetGuard }), { childSessionId });
3784
3883
  }
3785
3884
  function infoOf(message) {
3786
3885
  const info = message?.info;
@@ -3796,6 +3895,10 @@ function isSettledAssistant(message) {
3796
3895
  return false;
3797
3896
  return info.time?.completed != null || info.error != null || info.finish != null;
3798
3897
  }
3898
+ function isTerminalAssistant(message) {
3899
+ const info = infoOf(message);
3900
+ return isSettledAssistant(message) && info.finish !== "tool-calls" && info.finish !== "tool_use" && !(message && typeof message === "object" && Array.isArray(message.parts) && message.parts.some((part) => part.type === "tool"));
3901
+ }
3799
3902
  function unwrapData(response) {
3800
3903
  if (response && typeof response === "object" && "data" in response) {
3801
3904
  return response.data;
@@ -3873,40 +3976,106 @@ async function promptAsyncAndWaitForIdle(client, request, options = {}) {
3873
3976
  const signal = request.signal;
3874
3977
  const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
3875
3978
  const startGraceMs = options.startGraceMs ?? DEFAULT_START_GRACE_MS;
3876
- const baseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((id) => id !== null));
3979
+ const budget = options.budgetGuard ?? (options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined);
3980
+ if (budget?.snapshot().finalizeFired)
3981
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
3982
+ const seenUsage = new Set;
3983
+ let finalizing = false;
3984
+ let completionBaseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((id) => id !== null));
3985
+ const initialBaseline = new Set(completionBaseline);
3877
3986
  const sent = await session.promptAsync?.(request);
3878
3987
  if (sent && typeof sent === "object" && "error" in sent) {
3879
3988
  const rejection = sent.error;
3880
3989
  if (rejection)
3881
3990
  throw new Error(`prompt_async was rejected: ${describeRejection(rejection)}`);
3882
3991
  }
3883
- const sentAt = Date.now();
3992
+ let sentAt = Date.now();
3884
3993
  let sawBusy = false;
3885
- for (;; ) {
3886
- await sleep(pollIntervalMs, signal);
3887
- const status = await readStatus(session, sessionId, dir, signal);
3888
- if (status === "busy" || status === "retry") {
3889
- sawBusy = true;
3890
- continue;
3891
- }
3892
- if (status === null)
3893
- continue;
3894
- const messages = await readMessages(session, sessionId, dir, signal);
3895
- const last = messages.at(-1);
3896
- const lastId = messageId(last);
3897
- if (last && lastId !== null && !baseline.has(lastId) && isSettledAssistant(last))
3898
- return;
3899
- const fresh = messages.some((message) => {
3900
- const id = messageId(message);
3901
- return id !== null && !baseline.has(id);
3902
- });
3903
- if (sawBusy && fresh)
3904
- return;
3905
- if (Date.now() - sentAt >= startGraceMs) {
3906
- if (fresh)
3994
+ try {
3995
+ for (;; ) {
3996
+ await sleep(pollIntervalMs, signal);
3997
+ const status = await readStatus(session, sessionId, dir, signal);
3998
+ const messages = await readMessages(session, sessionId, dir, signal);
3999
+ if (budget) {
4000
+ for (const message of messages) {
4001
+ const id = messageId(message);
4002
+ if (!id || seenUsage.has(id) || infoOf(message).role !== "assistant")
4003
+ continue;
4004
+ if (initialBaseline.has(id))
4005
+ continue;
4006
+ const tokens = sumTokensFromChildMessages([message]);
4007
+ if (tokens.input + tokens.cacheRead + tokens.cacheWrite === 0)
4008
+ continue;
4009
+ seenUsage.add(id);
4010
+ const decision = budget.charge(tokens.input, tokens.cacheRead, tokens.cacheWrite, isTerminalAssistant(message));
4011
+ options.onBudgetUpdate?.({ ...budget.snapshot(), sessionId });
4012
+ if (decision === "stop") {
4013
+ await session.abort?.({ path: { id: sessionId } });
4014
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4015
+ }
4016
+ if (decision === "finalize") {
4017
+ finalizing = true;
4018
+ registerBudgetFinalizeChild(sessionId, budget);
4019
+ if (!session.abort)
4020
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4021
+ await session.abort({ path: { id: sessionId } });
4022
+ const abortDeadline = Date.now() + startGraceMs;
4023
+ while (Date.now() < abortDeadline) {
4024
+ const state = await readStatus(session, sessionId, dir, signal);
4025
+ if (state === "idle")
4026
+ break;
4027
+ await sleep(pollIntervalMs, signal);
4028
+ }
4029
+ if (await readStatus(session, sessionId, dir, signal) !== "idle") {
4030
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4031
+ }
4032
+ completionBaseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((value) => value !== null));
4033
+ const finalizeResponse = await session.promptAsync?.({
4034
+ ...request,
4035
+ body: {
4036
+ ...request.body,
4037
+ parts: [{ type: "text", text: TOKEN_BUDGET_FINALIZE_MESSAGE }]
4038
+ }
4039
+ });
4040
+ if (finalizeResponse && typeof finalizeResponse === "object" && "error" in finalizeResponse && finalizeResponse.error) {
4041
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4042
+ }
4043
+ sentAt = Date.now();
4044
+ sawBusy = false;
4045
+ break;
4046
+ }
4047
+ }
4048
+ if (budget.snapshot().hardStopped) {
4049
+ await session.abort?.({ path: { id: sessionId } });
4050
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4051
+ }
4052
+ }
4053
+ if (status === "busy" || status === "retry") {
4054
+ sawBusy = true;
4055
+ continue;
4056
+ }
4057
+ if (status === null)
4058
+ continue;
4059
+ const current = finalizing ? await readMessages(session, sessionId, dir, signal) : messages;
4060
+ const last = current.at(-1);
4061
+ const lastId = messageId(last);
4062
+ if (last && lastId !== null && !completionBaseline.has(lastId) && isSettledAssistant(last))
4063
+ return;
4064
+ const fresh = current.some((message) => {
4065
+ const id = messageId(message);
4066
+ return id !== null && !completionBaseline.has(id);
4067
+ });
4068
+ if (sawBusy && fresh)
3907
4069
  return;
3908
- throw new Error(`prompt_async did not start a run in child session ${sessionId} within ${startGraceMs}ms`);
4070
+ if (Date.now() - sentAt >= startGraceMs) {
4071
+ if (fresh)
4072
+ return;
4073
+ throw new Error(`prompt_async did not start a run in child session ${sessionId} within ${startGraceMs}ms`);
4074
+ }
3909
4075
  }
4076
+ } finally {
4077
+ if (budget)
4078
+ releaseBudgetFinalizeChild(sessionId);
3910
4079
  }
3911
4080
  }
3912
4081
  // ../plugin/src/shared/summarize-child-stderr.ts
@@ -4007,109 +4176,13 @@ var HISTORIAN_AGENT = "historian";
4007
4176
  var HISTORIAN_RECOMP_AGENT = "historian-recomp";
4008
4177
  var HISTORIAN_EDITOR_AGENT = "historian-editor";
4009
4178
 
4010
- // ../plugin/src/features/magic-context/subagent-token-capture.ts
4011
- function asNumber(value) {
4012
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
4013
- }
4014
- function tokenObjectFromMessage(message) {
4015
- const info = message.info;
4016
- if (info && typeof info === "object") {
4017
- const tokens = info.tokens;
4018
- if (tokens && typeof tokens === "object")
4019
- return tokens;
4020
- }
4021
- const tokens = message.tokens;
4022
- if (tokens && typeof tokens === "object")
4023
- return tokens;
4024
- const usage = message.usage;
4025
- if (usage && typeof usage === "object")
4026
- return usage;
4027
- return null;
4028
- }
4029
- function isAssistantMessage(message) {
4030
- if (!message || typeof message !== "object")
4031
- return false;
4032
- const record = message;
4033
- const info = record.info;
4034
- if (info && typeof info === "object") {
4035
- return info.role === "assistant";
4036
- }
4037
- return record.role === "assistant";
4179
+ // ../plugin/src/config/live-child-output-cap.ts
4180
+ var historianCaps = new BoundedSessionMap(4096);
4181
+ function rememberHistorianOutputCap(sessionId, cap) {
4182
+ historianCaps.set(sessionId, { value: cap });
4038
4183
  }
4039
- function modelFromMessage(message) {
4040
- const info = message.info;
4041
- const source = info && typeof info === "object" ? info : message;
4042
- return {
4043
- providerId: typeof source.providerID === "string" ? source.providerID : typeof source.providerId === "string" ? source.providerId : null,
4044
- modelId: typeof source.modelID === "string" ? source.modelID : typeof source.modelId === "string" ? source.modelId : null
4045
- };
4046
- }
4047
- function failedInvocationStatus(error) {
4048
- const message = describeError(error).brief;
4049
- if (/timed out after \d+ms|prompt timed out/i.test(message))
4050
- return "timed_out";
4051
- if (error !== null && typeof error === "object" && error.name === "TimeoutError") {
4052
- return "timed_out";
4053
- }
4054
- if (/length-capped|no (?:assistant )?output|empty (?:assistant )?(?:output|text)/i.test(message)) {
4055
- return "empty";
4056
- }
4057
- return "failed";
4058
- }
4059
- function emptyTokenTotals() {
4060
- return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
4061
- }
4062
- function sumTokensFromChildMessages(messages) {
4063
- const totals = emptyTokenTotals();
4064
- for (const message of messages) {
4065
- if (!isAssistantMessage(message))
4066
- continue;
4067
- const tokens = tokenObjectFromMessage(message);
4068
- if (!tokens)
4069
- continue;
4070
- const cache = tokens.cache && typeof tokens.cache === "object" ? tokens.cache : {};
4071
- totals.input += asNumber(tokens.input);
4072
- totals.output += asNumber(tokens.output);
4073
- totals.cacheRead += asNumber(cache.read ?? tokens.cacheRead ?? tokens.cache_read);
4074
- totals.cacheWrite += asNumber(cache.write ?? tokens.cacheWrite ?? tokens.cache_write);
4075
- }
4076
- return totals;
4077
- }
4078
- function findLastAssistantModel(messages) {
4079
- for (let index = messages.length - 1;index >= 0; index -= 1) {
4080
- const message = messages[index];
4081
- if (isAssistantMessage(message))
4082
- return modelFromMessage(message);
4083
- }
4084
- return { providerId: null, modelId: null };
4085
- }
4086
- function recordChildInvocation(input) {
4087
- if (!input.db)
4088
- return null;
4089
- const tokens = input.tokens ?? sumTokensFromChildMessages(input.messages ?? []);
4090
- const model = input.providerId !== undefined || input.modelId !== undefined ? { providerId: input.providerId ?? null, modelId: input.modelId ?? null } : findLastAssistantModel(input.messages ?? []);
4091
- try {
4092
- return recordSubagentInvocation(input.db, {
4093
- sessionId: input.parentSessionId,
4094
- harness: input.harness,
4095
- subagent: input.subagent,
4096
- task: input.task ?? null,
4097
- providerId: model.providerId,
4098
- modelId: model.modelId,
4099
- startedAt: input.startedAt,
4100
- endedAt: input.endedAt ?? Date.now(),
4101
- status: input.status,
4102
- inputTokens: tokens.input,
4103
- outputTokens: tokens.output,
4104
- cacheReadTokens: tokens.cacheRead,
4105
- cacheWriteTokens: tokens.cacheWrite,
4106
- error: input.error ? describeError(input.error).brief : null,
4107
- parentInvocationId: input.parentInvocationId ?? null
4108
- });
4109
- } catch (error) {
4110
- sessionLog(input.parentSessionId, "subagent token accounting failed:", describeError(error).brief);
4111
- return null;
4112
- }
4184
+ function forgetHistorianOutputCap(sessionId) {
4185
+ historianCaps.delete(sessionId);
4113
4186
  }
4114
4187
 
4115
4188
  // ../plugin/src/hooks/magic-context/historian-prompt.generated.ts
@@ -5423,6 +5496,8 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5423
5496
  const id = typeof created?.id === "string" ? created.id : "";
5424
5497
  if (id && run.kind === "dreamer-task")
5425
5498
  asyncChildren.add(id);
5499
+ if (id && run.kind !== "dreamer-task")
5500
+ rememberHistorianOutputCap(id, run.maxOutputTokens);
5426
5501
  return { id, childSessionId: id || undefined };
5427
5502
  },
5428
5503
  async attempt(handle, request) {
@@ -5459,8 +5534,10 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5459
5534
  };
5460
5535
  },
5461
5536
  async close(handle, settlement) {
5462
- if (handle?.id)
5537
+ if (handle?.id) {
5463
5538
  asyncChildren.delete(handle.id);
5539
+ forgetHistorianOutputCap(handle.id);
5540
+ }
5464
5541
  if (!client)
5465
5542
  return;
5466
5543
  await teardownChildSession({
@@ -6836,7 +6913,7 @@ Project memory uses exactly 5 categories. Every memory belongs to one:
6836
6913
  var CURATE_SYSTEM_PROMPT = `You are a memory-pool curator for the magic-context system. You run during a scheduled dream window to keep a project's cross-session memory store lean and well-formed.
6837
6914
 
6838
6915
  ## Memory operations
6839
- - \`ctx_memory_list(category="...")\` — browse active memories, optionally filter by category
6916
+ - The scoped category snapshot arrives in the first message. Do not list or get it again; track mutation deltas.
6840
6917
  - \`action="merge", ids=[N,M,...], content="...", category="..."\` — consolidate duplicates into one canonical memory
6841
6918
  - \`action="update", ids=[N], content="...", superseded_by=M\` — rewrite content; name where removed detail survives when cutting more than half
6842
6919
  - \`action="write", category="...", content="..."\` — create a memory (SPLITS ONLY — never mint new facts)
@@ -6866,7 +6943,7 @@ var PRIMER_INVESTIGATOR_SYSTEM_PROMPT = `You are a read-only code investigator f
6866
6943
  function renderMemoryList(memories) {
6867
6944
  return memories.map((memory) => {
6868
6945
  const files = memory.mappedFiles.length ? memory.mappedFiles.join(", ") : "(none mapped yet)";
6869
- return `[${memory.id}] ${memory.category}
6946
+ return `[${memory.id}] ${memory.category} importance=${memory.importance} retrieval_count=${memory.retrievalCount} seen_count=${memory.seenCount}
6870
6947
  Content: ${memory.content}
6871
6948
  Mapped files: ${files}${memory.hasNoFileSentinel ? " (file-independent)" : ""}`;
6872
6949
  }).join(`
@@ -6894,9 +6971,41 @@ Rewrite narrative/historical → operational present tense ("X uses Y because Z"
6894
6971
  Archive a redundant memory only when a better ACTIVE memory in the same project and category preserves its information; name that survivor with \`superseded_by\`. A bare "redundant" verdict is deletion and will be refused. Leave standalone low-value or stale entries unchanged for a human to review. The global user profile describes the operator and is never a substitute for project knowledge, so it cannot justify an archive.
6895
6972
  KEEP (overrides archive): constraint/rule language (must/never/always) · explains WHY (because/so that/to prevent) · EXTERNAL-system limit (CONSTRAINTS: archive only if word-for-word duplicated) · path/config WITH context · retrieval_count>0 · priority/philosophy.
6896
6973
 
6897
- ### Memory pool
6974
+ ### Cross-chunk duplicate candidates (normalized content matches)
6975
+ ${args.crossChunkCandidates?.join(`
6976
+ `) || "(none)"}
6977
+
6978
+ ### Category snapshot (do not re-enumerate)
6898
6979
  ${renderMemoryList(args.memories)}`;
6899
6980
  }
6981
+ function chunkCurateMemories(memories, maxCharacters) {
6982
+ const chunks = [];
6983
+ let current = [];
6984
+ let size = 0;
6985
+ for (const memory of memories) {
6986
+ const length = renderMemoryList([memory]).length + 2;
6987
+ if (current.length && size + length > maxCharacters) {
6988
+ chunks.push(current);
6989
+ current = [];
6990
+ size = 0;
6991
+ }
6992
+ current.push(memory);
6993
+ size += length;
6994
+ }
6995
+ if (current.length)
6996
+ chunks.push(current);
6997
+ const keys = new Map;
6998
+ chunks.forEach((chunk, index) => {
6999
+ chunk.forEach((memory) => {
7000
+ const key = memory.content.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
7001
+ keys.set(key, [...keys.get(key) ?? [], { id: memory.id, chunk: index }]);
7002
+ });
7003
+ });
7004
+ return chunks.map((chunk, index) => ({
7005
+ memories: chunk,
7006
+ crossChunkCandidates: [...keys.values()].filter((matches) => matches.some((match) => match.chunk === index) && matches.some((match) => match.chunk !== index)).map((matches) => `IDs ${matches.map((match) => match.id).join(", ")}`)
7007
+ }));
7008
+ }
6900
7009
  var RETROSPECTIVE_SYSTEM_PROMPT = `You are a retrospective learning agent for Magic Context.
6901
7010
 
6902
7011
  You learn only from recurring user-friction moments where the user had to correct, re-explain, or recover from the assistant's repeated behavior. You receive a pre-rendered friction window from the host and may use ctx_search to look for corroborating prior patterns.
@@ -6955,17 +7064,23 @@ Return only XML in this exact shape:
6955
7064
  <learning route="observation">one recurring user preference</learning>
6956
7065
  </learnings>`;
6957
7066
  }
6958
- function buildMaintainDocsPrompt(projectPath, changeSet, existingDocs, budget = 12000, currentTokens = 0) {
7067
+ function buildMaintainDocsPrompt(projectPath, changeSet, existingDocs, budget = 12000, currentTokens = 0, currentDocs) {
6959
7068
  return `## Task: Propose documentation corrections
6960
7069
 
6961
7070
  Project: ${projectPath}
6962
7071
  Existing docs: ARCHITECTURE.md ${existingDocs.architecture ? "exists" : "missing"}; STRUCTURE.md ${existingDocs.structure ? "exists" : "missing"}.
6963
7072
  Current combined token count: ${currentTokens}. Combined budget: ${budget} tokens.
6964
7073
 
6965
- Host-collected code changes (commit subjects and changed files, not documentation history):
7074
+ Host-collected code changes (diff stat and hunks, or file/line ranges when oversized):
6966
7075
  ${changeSet}
6967
7076
 
6968
- Read the docs and relevant source with read-only tools. These files are short maps read by every agent in every session. Describe how the system works now, never what changed or when: no change log, dates, or commit lists. One short paragraph per subsystem at most. Mechanism detail belongs in docs/architecture/; you may name a docs page for it but never move that detail into these two files. Propose a change only when code contradicts the docs or a major piece is missing. Prefer rewriting a stale sentence to adding one. Propose nothing when nothing is wrong. Keep both files within the combined budget. Never touch protected regions (<!-- mc:protected START ... --> through <!-- mc:protected END -->); preserve their bytes.
7077
+ Current ARCHITECTURE.md:
7078
+ ${currentDocs?.architecture ?? "(read from project)"}
7079
+
7080
+ Current STRUCTURE.md:
7081
+ ${currentDocs?.structure ?? "(read from project)"}
7082
+
7083
+ Find claims in these sections that the diff makes wrong or incomplete. Propose one focused change per claim or section. Do not investigate unrelated areas. Read the docs and relevant source with read-only tools. These files are short maps read by every agent in every session. Describe how the system works now, never what changed or when: no change log, dates, or commit lists. One short paragraph per subsystem at most. Mechanism detail belongs in docs/architecture/; you may name a docs page for it but never move that detail into these two files. Propose a change only when code contradicts the docs or a major piece is missing. Prefer rewriting a stale sentence to adding one. Propose nothing when nothing is wrong. Keep both files within the combined budget. Never touch protected regions (<!-- mc:protected START ... --> through <!-- mc:protected END -->); preserve their bytes.
6969
7084
 
6970
7085
  Return ONLY a JSON array (or []), with each entry {"file":"ARCHITECTURE.md"|"STRUCTURE.md","action":"replace"|"add"|"remove","heading":"## Exact section heading","text":"## Full replacement section including heading and body (empty for remove)","reason":"one-line reason"}. A replacement includes the entire section, including its heading. Additions are appended at the end of the file. Do not include any unmodified sections. No file writes.`;
6971
7086
  }
@@ -6975,10 +7090,11 @@ function buildDreamTaskPrompt(task, args) {
6975
7090
  return buildCuratePrompt({
6976
7091
  projectPath: args.projectPath,
6977
7092
  category: args.curate?.category ?? "PROJECT_RULES",
6978
- memories: args.curate?.memories ?? []
7093
+ memories: args.curate?.memories ?? [],
7094
+ crossChunkCandidates: args.curate?.crossChunkCandidates
6979
7095
  });
6980
7096
  case "maintain-docs":
6981
- return buildMaintainDocsPrompt(args.projectPath, args.docsChangeSet ?? "", args.existingDocs ?? { architecture: false, structure: false }, args.docsBudget, args.docsCurrentTokens);
7097
+ return buildMaintainDocsPrompt(args.projectPath, args.docsChangeSet ?? "", args.existingDocs ?? { architecture: false, structure: false }, args.docsBudget, args.docsCurrentTokens, args.docsContents);
6982
7098
  }
6983
7099
  }
6984
7100
 
@@ -7911,6 +8027,7 @@ function hasCurrentDocsProposal(projectDir) {
7911
8027
  }
7912
8028
  function git(projectDir, args) {
7913
8029
  return execFileSync("git", args, {
8030
+ windowsHide: true,
7914
8031
  cwd: projectDir,
7915
8032
  encoding: "utf8",
7916
8033
  timeout: 1e4,
@@ -7922,11 +8039,80 @@ function docsChangeSet(projectDir, storedAnchor) {
7922
8039
  if (realpathSync2(git(projectDir, ["rev-parse", "--show-toplevel"])) !== realpathSync2(projectDir))
7923
8040
  return null;
7924
8041
  const head = git(projectDir, ["rev-parse", "HEAD"]);
8042
+ const recorded = Boolean(storedAnchor);
7925
8043
  let anchor = storedAnchor;
7926
- if (!anchor || !/^[a-f0-9]{40}$/.test(anchor) || !git(projectDir, ["cat-file", "-t", anchor]).includes("commit"))
8044
+ if (anchor && (!/^[a-f0-9]{40}$/.test(anchor) || git(projectDir, ["cat-file", "-t", anchor]) !== "commit"))
8045
+ throw new Error("maintain-docs checkpoint is not a git commit");
8046
+ if (!anchor)
7927
8047
  anchor = git(projectDir, ["log", "-1", "--format=%H", "--", ...FILES]);
7928
8048
  if (!anchor)
7929
8049
  return null;
8050
+ if (recorded && anchor === head)
8051
+ return { head, text: "", unchanged: true, relevant: false };
8052
+ if (recorded) {
8053
+ const changed = git(projectDir, [
8054
+ "diff",
8055
+ "-M",
8056
+ "--name-status",
8057
+ `${anchor}..HEAD`,
8058
+ "--",
8059
+ "."
8060
+ ]).split(`
8061
+ `).filter(Boolean).map((line) => line.split("\t").slice(1));
8062
+ const files = [
8063
+ ...new Set(changed.flat().filter((file) => file !== "ARCHITECTURE.md" && file !== "STRUCTURE.md" && !/(?:^|[./-])(test|spec)(?:[./-]|$)/.test(file) && !file.endsWith(".lock")))
8064
+ ];
8065
+ const docs = FILES.map((file) => existsSync3(join3(projectDir, file)) ? readFileSync3(join3(projectDir, file), "utf8") : "").join(`
8066
+ `);
8067
+ const relevant = files.some((file) => {
8068
+ const directory = file.split("/").slice(0, -1).join("/");
8069
+ return docs.includes(file) || directory.length > 2 && docs.includes(directory);
8070
+ });
8071
+ if (!relevant)
8072
+ return { head, text: "", unchanged: false, relevant: false };
8073
+ const stat = git(projectDir, [
8074
+ "diff",
8075
+ "-M",
8076
+ "--stat",
8077
+ `${anchor}..HEAD`,
8078
+ "--",
8079
+ ...files
8080
+ ]);
8081
+ const patch = git(projectDir, [
8082
+ "diff",
8083
+ "-M",
8084
+ "--unified=3",
8085
+ `${anchor}..HEAD`,
8086
+ "--",
8087
+ ...files
8088
+ ]);
8089
+ const budget = 32000;
8090
+ const text = `Changed files:
8091
+ ${files.join(`
8092
+ `)}
8093
+
8094
+ Stat:
8095
+ ${stat}
8096
+
8097
+ Hunks:
8098
+ ${patch}`;
8099
+ if (text.length <= budget)
8100
+ return { head, text, unchanged: false, relevant: true };
8101
+ const ranges = [...patch.matchAll(/^\+\+\+ b\/(.+)$|^@@ .* \+(\d+)(?:,(\d+))? @@/gm)];
8102
+ return {
8103
+ head,
8104
+ unchanged: false,
8105
+ relevant: true,
8106
+ text: `Changed files and ranges (diff exceeds prompt budget):
8107
+ ${files.join(`
8108
+ `)}
8109
+ ${ranges.map((match) => match[1] ? `file: ${match[1]}` : `line: ${match[2]} +${match[3] ?? 1}`).join(`
8110
+ `).slice(0, budget)}
8111
+
8112
+ Stat:
8113
+ ${stat}`
8114
+ };
8115
+ }
7930
8116
  const excluded = [
7931
8117
  "ARCHITECTURE.md",
7932
8118
  "STRUCTURE.md",
@@ -7956,11 +8142,15 @@ function docsChangeSet(projectDir, storedAnchor) {
7956
8142
  const text = raw.slice(0, 24000);
7957
8143
  return {
7958
8144
  head,
8145
+ unchanged: false,
8146
+ relevant: true,
7959
8147
  text: `${text}${raw.length > text.length ? `
7960
8148
  [cut ${raw.length - text.length} bytes]` : ""}${commits.length > 201 ? `
7961
8149
  [cut commits beyond newest 200]` : ""}`
7962
8150
  };
7963
- } catch {
8151
+ } catch (error) {
8152
+ if (storedAnchor)
8153
+ throw error;
7964
8154
  return null;
7965
8155
  }
7966
8156
  }
@@ -7994,7 +8184,7 @@ function diff(name, before, after) {
7994
8184
  writeFileSync3(join3(dir, "before"), before);
7995
8185
  writeFileSync3(join3(dir, "after"), after);
7996
8186
  try {
7997
- return execFileSync("git", ["diff", "--no-index", "--", join3(dir, "before"), join3(dir, "after")], { encoding: "utf8" });
8187
+ return execFileSync("git", ["diff", "--no-index", "--", join3(dir, "before"), join3(dir, "after")], { windowsHide: true, encoding: "utf8" });
7998
8188
  } catch (error) {
7999
8189
  return error.stdout?.replaceAll(join3(dir, "before"), `a/${name}`).replaceAll(join3(dir, "after"), `b/${name}`) ?? "";
8000
8190
  }
@@ -8245,7 +8435,7 @@ function requestValidatedAddress(validation, candidate, options) {
8245
8435
  const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8246
8436
  bytes += buf.byteLength;
8247
8437
  if (bytes > options.bodyLimitBytes) {
8248
- reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: response body too large", { terminal: true }));
8438
+ reject(new SmartNoteNetworkError(`SMART_NOTE_NETWORK: response body too large at ${url.href} (received at least ${bytes} bytes; limit ${options.bodyLimitBytes})`, { terminal: true, persistent: true }));
8249
8439
  response.destroy();
8250
8440
  request2.destroy();
8251
8441
  return;
@@ -8582,6 +8772,7 @@ async function runGit(projectRoot, args, signal) {
8582
8772
  throwIfAborted2(signal);
8583
8773
  try {
8584
8774
  const result = await execFileAsync("git", ["-C", projectRoot, ...args], {
8775
+ windowsHide: true,
8585
8776
  timeout: DEFAULT_GIT_TIMEOUT_MS,
8586
8777
  maxBuffer: 128 * 1024,
8587
8778
  signal
@@ -8744,6 +8935,7 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
8744
8935
  const controller = new AbortController;
8745
8936
  let externallyCancelled = false;
8746
8937
  let executionTimedOut = false;
8938
+ let persistentNetworkFailure = false;
8747
8939
  const externalAbort = () => {
8748
8940
  externallyCancelled = true;
8749
8941
  controller.abort(options.signal?.reason);
@@ -8762,7 +8954,19 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
8762
8954
  context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
8763
8955
  context.runtime.setMaxStackSize(options.stackLimitBytes ?? DEFAULT_STACK_LIMIT_BYTES);
8764
8956
  context.runtime.setInterruptHandler(() => controller.signal.aborted || Date.now() > deadline);
8765
- installCapabilityObject(context, capabilities);
8957
+ installCapabilityObject(context, {
8958
+ ...capabilities,
8959
+ httpGet: async (url) => {
8960
+ try {
8961
+ return await capabilities.httpGet(url);
8962
+ } catch (error) {
8963
+ if (error instanceof SmartNoteNetworkError && error.persistent) {
8964
+ persistentNetworkFailure = true;
8965
+ }
8966
+ throw error;
8967
+ }
8968
+ }
8969
+ });
8766
8970
  disableAmbientDynamicCode(context);
8767
8971
  const result = await evalCheck(context, options.compiledCheck);
8768
8972
  const checkResult = result;
@@ -8776,14 +8980,14 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
8776
8980
  } catch (error) {
8777
8981
  if (externallyCancelled && !executionTimedOut)
8778
8982
  return cancelledResult(error);
8779
- return failureResult(formatSandboxError(error), isSmartNoteNetworkError(error));
8983
+ return failureResult(formatSandboxError(error), isSmartNoteNetworkError(error), persistentNetworkFailure);
8780
8984
  } finally {
8781
8985
  clearTimeout(timer);
8782
8986
  options.signal?.removeEventListener("abort", externalAbort);
8783
8987
  }
8784
8988
  }
8785
- function failureResult(error, network) {
8786
- return { ok: false, cancelled: false, error: truncate(error), network };
8989
+ function failureResult(error, network, persistent = false) {
8990
+ return { ok: false, cancelled: false, error: truncate(error), network, persistent };
8787
8991
  }
8788
8992
  function cancelledResult(reason) {
8789
8993
  return {
@@ -8881,7 +9085,12 @@ var MAX_CRON_CHARS = 256;
8881
9085
  var MAX_COMPILER_ERROR_CHARS = 2 * 1024;
8882
9086
  async function compileSmartNoteCheck(args) {
8883
9087
  if (!args.note.surfaceCondition) {
8884
- return { ok: false, cancelled: false, error: "note has no surface condition" };
9088
+ return {
9089
+ ok: false,
9090
+ cancelled: false,
9091
+ error: "note has no surface condition",
9092
+ persistent: false
9093
+ };
8885
9094
  }
8886
9095
  const prompt = `Compile this smart note condition into a sandbox check.
8887
9096
 
@@ -8988,15 +9197,13 @@ Remember: output only the JSON object described by the system prompt.`;
8988
9197
  const compiledCheck = normalizeCompiledCheck(response.compiled_check);
8989
9198
  const manifest = normalizeManifest(response.manifest);
8990
9199
  const checkCron = normalizeCron(response.check_cron);
8991
- for (const warning of manifestAdvisoryWarnings(compiledCheck, manifest)) {
9200
+ const dryRun = await dryRunSmartNoteCheck(compiledCheck, args.capabilityFactory, args.signal);
9201
+ for (const warning of [
9202
+ ...manifestAdvisoryWarnings(compiledCheck, manifest),
9203
+ ...dryRun.advisories
9204
+ ]) {
8992
9205
  log(`[dreamer] smart note #${args.note.id}: manifest advisory — ${warning}`);
8993
9206
  }
8994
- const dryRun = await runCompiledSmartNoteCheck({
8995
- compiledCheck,
8996
- capabilityFactory: args.capabilityFactory,
8997
- signal: args.signal,
8998
- timeoutMs: 2000
8999
- });
9000
9207
  if (!dryRun.ok) {
9001
9208
  const error = boundedError(`dry-run failed: ${dryRun.error}`);
9002
9209
  recordInvocation({
@@ -9004,7 +9211,12 @@ Remember: output only the JSON object described by the system prompt.`;
9004
9211
  messages: outputMessages,
9005
9212
  error
9006
9213
  });
9007
- return { ok: false, cancelled: dryRun.cancelled, error };
9214
+ return {
9215
+ ok: false,
9216
+ cancelled: dryRun.cancelled,
9217
+ error,
9218
+ persistent: !dryRun.cancelled && dryRun.persistent
9219
+ };
9008
9220
  }
9009
9221
  recordInvocation({ status: "completed", messages: outputMessages });
9010
9222
  return {
@@ -9019,7 +9231,7 @@ Remember: output only the JSON object described by the system prompt.`;
9019
9231
  const cancelled = args.signal.aborted;
9020
9232
  const message = boundedError(error instanceof Error ? error.message : String(error));
9021
9233
  recordInvocation({ status: cancelled ? "aborted" : "failed", error: message });
9022
- return { ok: false, cancelled, error: message };
9234
+ return { ok: false, cancelled, error: message, persistent: false };
9023
9235
  } finally {
9024
9236
  if (args.client) {
9025
9237
  await teardownChildSession({
@@ -9034,6 +9246,29 @@ Remember: output only the JSON object described by the system prompt.`;
9034
9246
  }
9035
9247
  }
9036
9248
  }
9249
+ async function dryRunSmartNoteCheck(compiledCheck, capabilityFactory, signal) {
9250
+ const responses = [];
9251
+ const dryRun = await runCompiledSmartNoteCheck({
9252
+ compiledCheck,
9253
+ capabilityFactory: (runSignal) => {
9254
+ const capabilities = capabilityFactory(runSignal);
9255
+ return {
9256
+ ...capabilities,
9257
+ httpGet: async (url) => {
9258
+ const response = await capabilities.httpGet(url);
9259
+ responses.push({ url, status: response.status });
9260
+ return response;
9261
+ }
9262
+ };
9263
+ },
9264
+ signal,
9265
+ timeoutMs: 2000
9266
+ });
9267
+ const advisories = dryRun.ok && responses.length > 0 && responses.every(({ status }) => status === 401 || status === 403 || status === 404) ? [
9268
+ `all HTTP sources currently return 401/403/404: ${responses.map(({ url, status }) => `${url} (HTTP ${status})`).join(", ")}`
9269
+ ] : [];
9270
+ return { ...dryRun, advisories };
9271
+ }
9037
9272
  function parseCompilerOutput(output) {
9038
9273
  if (!output)
9039
9274
  throw new Error("smart-note compiler returned no output");
@@ -9488,7 +9723,7 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld,
9488
9723
  expected: sourceRevisionExpectation(note),
9489
9724
  leaseHeld,
9490
9725
  write: () => {
9491
- markSmartNoteCompilationFailure(args.db, note.id, now, MAX_COMPILATION_FAILURES);
9726
+ markSmartNoteCompilationFailure(args.db, note.id, now, MAX_COMPILATION_FAILURES, result.error, result.persistent);
9492
9727
  }
9493
9728
  });
9494
9729
  return false;
@@ -9859,7 +10094,7 @@ var MAP_MEMORIES_SYSTEM_PROMPT = `You are a memory mapper for the magic-context
9859
10094
 
9860
10095
  A memory's BACKING FILES are the file(s) whose code the memory makes a claim about — the files you would open to check whether the memory is accurate. You do NOT judge accuracy, rewrite, or remove anything. You only LOCATE backing files.
9861
10096
 
9862
- Tools (read-only): read, grep, glob, aft_search, aft_outline, aft_zoom. Each memory may come with "Likely files" already named in it and confirmed to exist — confirm those FIRST (cheap) instead of searching. Use search/grep to FIND code only when no likely files are given. Do not guess — confirm a file exists and genuinely backs the memory before listing it. Keep reads minimal: you do not need to read a whole file to confirm it backs a one-line claim.
10097
+ Tools (read-only): read, grep, glob. Request line ranges rather than full files; use offset/limit for longer files. Each memory may come with "Likely files" already named in it and confirmed to exist — confirm those FIRST (cheap) instead of searching. Use search/grep to FIND code only when no likely files are given. Do not guess — confirm a file exists and genuinely backs the memory before listing it. Keep reads minimal: you do not need to read a whole file to confirm it backs a one-line claim.
9863
10098
 
9864
10099
  For each memory decide ONE of:
9865
10100
  - Backing files found → the COMPLETE set of repo-relative paths whose code the memory is about.
@@ -10116,6 +10351,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
10116
10351
  prompt,
10117
10352
  title: "magic-context-dream-map-memories",
10118
10353
  callContext: "dreamer:map-memories",
10354
+ metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
10119
10355
  model: args.model,
10120
10356
  fallbackModels: args.fallbackModels,
10121
10357
  timeoutMs: sliceMs,
@@ -10156,7 +10392,10 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
10156
10392
  parts: [{ type: "text", text: prompt, synthetic: true }]
10157
10393
  }
10158
10394
  }, {
10159
- transport: createPromptAsyncTransport(client, agentSessionId),
10395
+ transport: createPromptAsyncTransport(client, agentSessionId, {
10396
+ tokenBudget: args.tokenBudget,
10397
+ onBudgetUpdate: args.onBudgetUpdate
10398
+ }),
10160
10399
  timeoutMs: sliceMs,
10161
10400
  signal,
10162
10401
  fallbackModels: args.fallbackModels,
@@ -10196,7 +10435,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
10196
10435
  error,
10197
10436
  messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
10198
10437
  });
10199
- if (error instanceof DreamerModuleFailureError)
10438
+ if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded)
10200
10439
  throw error;
10201
10440
  if (signal.aborted)
10202
10441
  throw error;
@@ -10603,12 +10842,12 @@ async function promotePrimers(args) {
10603
10842
 
10604
10843
  // ../plugin/src/features/magic-context/dreamer/primer-seed.ts
10605
10844
  var PRIMER_SEED_CAP_TOKENS = 4000;
10606
- function renderUserAndToolOrientation(messages, startOrdinal, endOrdinal, capTokens) {
10845
+ function renderUserAndToolOrientation(sessionId, startOrdinal, endOrdinal, capTokens) {
10607
10846
  const lines = [];
10608
10847
  let tokens = 0;
10609
- for (const msg of messages) {
10610
- if (msg.ordinal < startOrdinal || msg.ordinal > endOrdinal)
10611
- continue;
10848
+ let sawMessage = false;
10849
+ visitRawSessionMessages2(sessionId, startOrdinal, endOrdinal, (msg) => {
10850
+ sawMessage = true;
10612
10851
  const out = [];
10613
10852
  if (msg.role === "user" && hasMeaningfulUserText2(msg.parts)) {
10614
10853
  const text = extractTexts2(msg.parts).map((t) => cleanUserText2(t)).map(normalizeText).filter((t) => t.length > 0).join(" / ");
@@ -10621,22 +10860,27 @@ function renderUserAndToolOrientation(messages, startOrdinal, endOrdinal, capTok
10621
10860
  const lineTokens = estimateTokens(line);
10622
10861
  if (tokens + lineTokens > capTokens && lines.length > 0) {
10623
10862
  lines.push("… (orientation truncated; investigate the current source directly)");
10624
- return lines.join(`
10625
- `);
10863
+ return false;
10626
10864
  }
10627
10865
  lines.push(line);
10628
10866
  tokens += lineTokens;
10629
10867
  }
10630
- }
10631
- return lines.join(`
10632
- `);
10868
+ return true;
10869
+ }, { summary: true });
10870
+ return sawMessage ? lines.join(`
10871
+ `) : null;
10872
+ }
10873
+ function selectPrimerOriginCandidate(db, primer) {
10874
+ return getPrimerCandidatesByIds(db, primer.sourceCandidateIds).filter((candidate) => candidate.projectPath === primer.projectPath).sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
10633
10875
  }
10634
10876
  function loadPrePostP1(db, sessionId, originStartMessage) {
10635
10877
  const origin = db.prepare("SELECT sequence FROM compartments WHERE session_id = ? AND start_message = ? ORDER BY sequence ASC LIMIT 1").get(sessionId, originStartMessage);
10636
10878
  if (typeof origin?.sequence !== "number")
10637
10879
  return "";
10638
10880
  const originSeq = origin.sequence;
10639
- const rows = db.prepare(`SELECT sequence, start_message, end_message, title, p1, content
10881
+ const rows = db.prepare(`SELECT sequence, start_message, end_message,
10882
+ substr(title, 1, 512) AS title,
10883
+ substr(p1, 1, 1200) AS p1, substr(content, 1, 1200) AS content
10640
10884
  FROM compartments
10641
10885
  WHERE session_id = ? AND sequence IN (?, ?)
10642
10886
  ORDER BY sequence ASC`).all(sessionId, originSeq - 1, originSeq + 1);
@@ -10650,28 +10894,28 @@ function loadPrePostP1(db, sessionId, originStartMessage) {
10650
10894
  `);
10651
10895
  }
10652
10896
  function closedBookOriginP1(db, sessionId, originStartMessage) {
10653
- 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);
10897
+ const row = db.prepare(`SELECT substr(title, 1, 512) AS title,
10898
+ substr(p1, 1, 2000) AS p1, substr(content, 1, 2000) AS content
10899
+ FROM compartments WHERE session_id = ? AND start_message = ? ORDER BY sequence ASC LIMIT 1`).get(sessionId, originStartMessage);
10654
10900
  const body = (row?.p1 ?? row?.content ?? "").slice(0, 2000);
10655
10901
  const orientation = row?.title ? `${row.title}: ${body}` : body;
10656
10902
  return { orientation, sessionId };
10657
10903
  }
10658
10904
  function buildPrimerSeed(db, primer) {
10659
- const candidates = getPrimerCandidatesByIds(db, primer.sourceCandidateIds);
10660
- const mostRecent = candidates.slice().sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
10905
+ const mostRecent = selectPrimerOriginCandidate(db, primer);
10661
10906
  if (!mostRecent || typeof mostRecent.sourceCompartmentStart !== "number" || typeof mostRecent.sourceCompartmentEnd !== "number") {
10662
10907
  return { kind: "closed-book", orientation: "", prePost: "", sessionId: null };
10663
10908
  }
10664
10909
  const sessionId = mostRecent.sessionId;
10665
10910
  const start = mostRecent.sourceCompartmentStart;
10666
10911
  const end = mostRecent.sourceCompartmentEnd;
10667
- let raw = [];
10912
+ let orientation = null;
10668
10913
  try {
10669
- raw = readRawSessionMessages2(sessionId);
10914
+ orientation = renderUserAndToolOrientation(sessionId, start, end, PRIMER_SEED_CAP_TOKENS);
10670
10915
  } catch {
10671
- raw = [];
10916
+ orientation = null;
10672
10917
  }
10673
- const inRange = raw.some((m) => m.ordinal >= start && m.ordinal <= end);
10674
- if (!inRange) {
10918
+ if (orientation === null) {
10675
10919
  const closed = closedBookOriginP1(db, sessionId, start);
10676
10920
  return {
10677
10921
  kind: "closed-book",
@@ -10680,7 +10924,6 @@ function buildPrimerSeed(db, primer) {
10680
10924
  sessionId
10681
10925
  };
10682
10926
  }
10683
- const orientation = renderUserAndToolOrientation(raw, start, end, PRIMER_SEED_CAP_TOKENS);
10684
10927
  return {
10685
10928
  kind: "raw",
10686
10929
  orientation,
@@ -10822,6 +11065,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
10822
11065
  prompt,
10823
11066
  title: "magic-context-dream-refresh-primers",
10824
11067
  callContext: "dreamer:refresh-primers",
11068
+ metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
10825
11069
  model: args.model,
10826
11070
  fallbackModels: args.fallbackModels,
10827
11071
  language: args.language,
@@ -10871,6 +11115,10 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
10871
11115
  signal,
10872
11116
  fallbackModels: args.fallbackModels,
10873
11117
  callContext: "dreamer:refresh-primers",
11118
+ transport: createPromptAsyncTransport(client, agentSessionId, {
11119
+ tokenBudget: args.tokenBudget,
11120
+ onBudgetUpdate: args.onBudgetUpdate
11121
+ }),
10874
11122
  fetchOutput: async () => {
10875
11123
  const messagesResponse = await client.session.messages({
10876
11124
  path: { id: agentSessionId },
@@ -10914,9 +11162,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
10914
11162
  }
10915
11163
  }
10916
11164
  function originSessionIdForPrimer(args, primer) {
10917
- const candidates = getPrimerCandidatesByIds(args.db, primer.sourceCandidateIds);
10918
- const mostRecent = candidates.slice().sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
10919
- return mostRecent?.sessionId ?? null;
11165
+ return selectPrimerOriginCandidate(args.db, primer)?.sessionId ?? null;
10920
11166
  }
10921
11167
  function recordInvocation3(args, startedAt, params) {
10922
11168
  if (!args.parentSessionId)
@@ -11085,12 +11331,14 @@ class OpenCodeRetrospectiveRawProvider {
11085
11331
  this.openDb = deps.openOpenCodeDb ?? openOpenCodeDb;
11086
11332
  }
11087
11333
  listProjectSessions(projectIdentity) {
11088
- const rows = this.deps.contextDb.prepare(`SELECT sp.session_id, sp.updated_at
11334
+ const rows = this.deps.contextDb.prepare(`SELECT sp.session_id, CAST(activity.value AS INTEGER) AS updated_at
11089
11335
  FROM session_projects sp
11336
+ JOIN schema_migrations_meta activity
11337
+ ON activity.key = 'retrospective_activity:' || sp.session_id
11090
11338
  LEFT JOIN session_meta m ON m.session_id = sp.session_id
11091
11339
  WHERE sp.project_path = ? AND sp.harness = 'opencode'
11092
11340
  AND COALESCE(m.is_subagent, 0) = 0
11093
- ORDER BY sp.updated_at ASC, sp.session_id ASC`).all(projectIdentity);
11341
+ ORDER BY updated_at ASC, sp.session_id ASC`).all(projectIdentity);
11094
11342
  return rows.map((row) => ({
11095
11343
  sessionId: row.session_id,
11096
11344
  updatedAt: typeof row.updated_at === "number" ? row.updated_at : undefined
@@ -11109,15 +11357,13 @@ class OpenCodeRetrospectiveRawProvider {
11109
11357
  const db = this.resolveDb();
11110
11358
  if (!db)
11111
11359
  return { messages: [], truncated: false };
11112
- try {
11113
- return readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession);
11114
- } catch {
11115
- return { messages: [], truncated: false };
11116
- }
11360
+ return readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession);
11117
11361
  }
11118
11362
  readOldestMessageTimesSince(sessionIds, sinceMs) {
11119
11363
  const db = this.resolveDb();
11120
- if (!db || sessionIds.length === 0)
11364
+ if (!db)
11365
+ return null;
11366
+ if (sessionIds.length === 0)
11121
11367
  return new Map;
11122
11368
  return readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs);
11123
11369
  }
@@ -11214,6 +11460,11 @@ async function readRetrospectiveScanWindow(provider, projectIdentity, watermarkM
11214
11460
  }
11215
11461
  const droppedSince = [...budgetDropped, ...countDropped];
11216
11462
  let maxScannedTs = scanSinceMs;
11463
+ if (oldestBySession && sinceReads.every((read) => read.messages.length === 0)) {
11464
+ for (const { session } of eligibleSessions) {
11465
+ maxScannedTs = Math.max(maxScannedTs, session.updatedAt ?? scanSinceMs);
11466
+ }
11467
+ }
11217
11468
  for (const row of keptSince) {
11218
11469
  if (row.ts > maxScannedTs)
11219
11470
  maxScannedTs = row.ts;
@@ -11314,11 +11565,48 @@ function normalizeOpenCodeRows(db, sessionId, rows) {
11314
11565
  return [];
11315
11566
  const messageIds = rows.map((row) => row.id);
11316
11567
  const placeholders = messageIds.map(() => "?").join(", ");
11317
- const partRows = db.prepare(`SELECT message_id, data
11568
+ const userIds = rows.filter((row) => parseJsonRecord(row.data)?.role === "user").map((row) => row.id);
11569
+ const userPlaceholders = userIds.map(() => "?").join(", ") || "NULL";
11570
+ const output = `CASE json_type(data, '$.state.output')
11571
+ WHEN 'text' THEN json_extract(data, '$.state.output')
11572
+ WHEN 'null' THEN '' ELSE COALESCE(data -> '$.state.output', '') END`;
11573
+ const error = `CASE json_type(data, '$.state.error')
11574
+ WHEN 'text' THEN json_extract(data, '$.state.error')
11575
+ WHEN 'null' THEN '' ELSE COALESCE(data -> '$.state.error', '') END`;
11576
+ const containsError = (value) => ["error", "failed", "exception", "traceback"].map((word) => `(' ' || lower(${value}) || ' ') GLOB '*[^a-z0-9_]${word}[^a-z0-9_]*'`).join(" OR ");
11577
+ const errorWords = `CASE WHEN json_type(data, '$.state.output') IN ('object', 'array')
11578
+ THEN EXISTS (SELECT 1 FROM json_tree(data, '$.state.output') AS leaf
11579
+ WHERE (leaf.type = 'text' AND (${containsError("json_quote(leaf.atom)")}))
11580
+ OR (typeof(leaf.key) = 'text' AND (${containsError("json_quote(leaf.key)")})))
11581
+ WHEN instr(CAST(${output} AS BLOB), x'00') > 0 THEN EXISTS (
11582
+ WITH RECURSIVE segments(rest, piece) AS (
11583
+ SELECT CAST(${output} AS BLOB), x''
11584
+ UNION ALL
11585
+ SELECT CASE WHEN instr(rest, x'00') > 0
11586
+ THEN substr(rest, instr(rest, x'00') + 1) ELSE NULL END,
11587
+ CASE WHEN instr(rest, x'00') > 0
11588
+ THEN substr(rest, 1, instr(rest, x'00') - 1) ELSE rest END
11589
+ FROM segments WHERE rest IS NOT NULL
11590
+ ) SELECT 1 FROM segments WHERE (${containsError("CAST(piece AS TEXT)")}))
11591
+ ELSE (${containsError(output)}) END`;
11592
+ const whitespace = `
11593
+ \v\f\r              \u2028\u2029   \uFEFF`;
11594
+ const partRows = db.prepare(`SELECT message_id, CASE json_extract(data, '$.type')
11595
+ WHEN 'text' THEN CASE WHEN message_id IN (${userPlaceholders}) THEN json_object(
11596
+ 'type', 'text', 'text', data -> '$.text',
11597
+ 'synthetic', data -> '$.synthetic', 'ignored', data -> '$.ignored') ELSE '{}' END
11598
+ WHEN 'tool' THEN json_object(
11599
+ 'type', 'tool', 'tool', data -> '$.tool',
11600
+ 'state', json_object('isError', json(CASE WHEN
11601
+ json_type(data, '$.state.isError') = 'true'
11602
+ OR lower(json_extract(data, '$.state.status')) = 'error'
11603
+ OR length(CAST(trim(${error}, ?) AS BLOB)) > 0 OR ${errorWords}
11604
+ THEN 'true' ELSE 'false' END)))
11605
+ ELSE '{}' END AS data
11318
11606
  FROM part
11319
- WHERE +session_id = ?
11607
+ WHERE +session_id = ? AND json_valid(data)
11320
11608
  AND likelihood(message_id IN (${placeholders}), 0.000001)
11321
- ORDER BY time_created ASC, id ASC`).all(sessionId, ...messageIds);
11609
+ ORDER BY time_created ASC, id ASC`).all(...userIds, whitespace, sessionId, ...messageIds);
11322
11610
  const partsByMessageId = new Map;
11323
11611
  for (const row of partRows) {
11324
11612
  const parts = partsByMessageId.get(row.message_id) ?? [];
@@ -11468,6 +11756,127 @@ function insertDreamRun(db, run) {
11468
11756
  // ../plugin/src/features/magic-context/dreamer/verify.ts
11469
11757
  import { createHash as createHash9 } from "node:crypto";
11470
11758
 
11759
+ // ../plugin/src/features/magic-context/dreamer/verify-diff.ts
11760
+ import { execFile as execFile2 } from "node:child_process";
11761
+ import { promisify as promisify2 } from "node:util";
11762
+ var exec = promisify2(execFile2);
11763
+ var VERIFY_DIFF_BUDGET_TOKENS = 12000;
11764
+ var MAX_DIFF_BYTES = 4 * 1024 * 1024;
11765
+ async function git2(root, args) {
11766
+ const { stdout } = await exec("git", args, {
11767
+ cwd: root,
11768
+ timeout: 1e4,
11769
+ maxBuffer: MAX_DIFF_BYTES,
11770
+ encoding: "utf8"
11771
+ });
11772
+ return stdout;
11773
+ }
11774
+ async function buildVerifyDiffEvidence(directory, memories) {
11775
+ const root = await resolveGitTopLevel(directory);
11776
+ const head = root && await readGitHead(root);
11777
+ if (!root || !head || memories.some((m) => !m.verifiedAt))
11778
+ return null;
11779
+ try {
11780
+ const bases = new Map;
11781
+ for (const memory of memories) {
11782
+ const at = memory.verifiedAt;
11783
+ if (!at)
11784
+ return null;
11785
+ const base = memory.verifiedCommit ?? (await git2(root, [
11786
+ "rev-list",
11787
+ "-1",
11788
+ `--before=${new Date(at).toISOString()}`,
11789
+ head
11790
+ ])).trim();
11791
+ if (!/^[0-9a-f]{40}$/i.test(base))
11792
+ return null;
11793
+ bases.set(memory.id, base);
11794
+ }
11795
+ const grouped = new Map;
11796
+ for (const memory of memories) {
11797
+ const at = memory.verifiedAt;
11798
+ const base = bases.get(memory.id);
11799
+ if (!at || !base)
11800
+ return null;
11801
+ for (const file of memory.mappedFiles) {
11802
+ if (!file || file.startsWith("/") || file.split("/").includes(".."))
11803
+ return null;
11804
+ const previous = grouped.get(file);
11805
+ if (!previous)
11806
+ grouped.set(file, {
11807
+ base,
11808
+ time: at,
11809
+ ids: [memory.id]
11810
+ });
11811
+ else {
11812
+ previous.ids.push(memory.id);
11813
+ if (at < previous.time) {
11814
+ previous.time = at;
11815
+ previous.base = base;
11816
+ }
11817
+ }
11818
+ }
11819
+ }
11820
+ const files = [];
11821
+ const namesByBase = new Map;
11822
+ for (const [file, group] of grouped) {
11823
+ let names = namesByBase.get(group.base);
11824
+ if (names === undefined) {
11825
+ names = await git2(root, ["diff", "-M", "--name-status", group.base, head]);
11826
+ namesByBase.set(group.base, names);
11827
+ }
11828
+ const rename = names.split(`
11829
+ `).map((line) => line.split("\t")).find((parts) => parts[0]?.startsWith("R") && parts[1] === file);
11830
+ const paths = rename ? [file, rename[2]] : [file];
11831
+ const committed = await git2(root, [
11832
+ "diff",
11833
+ "-M",
11834
+ "--no-ext-diff",
11835
+ group.base,
11836
+ head,
11837
+ "--",
11838
+ ...paths
11839
+ ]);
11840
+ const pending = await git2(root, ["diff", "-M", "--no-ext-diff", head, "--", ...paths]);
11841
+ const status = rename ? `RENAMED ${file} -> ${rename[2]}
11842
+ ` : names.split(`
11843
+ `).some((line) => line === `D ${file}`) ? `DELETED ${file}
11844
+ ` : "";
11845
+ files.push({
11846
+ path: file,
11847
+ memoryIds: group.ids,
11848
+ text: `${status}${committed}${pending ? `
11849
+ Uncommitted changes:
11850
+ ${pending}` : ""}` || "No changed hunks in mapped file."
11851
+ });
11852
+ }
11853
+ const size = files.reduce((sum, file) => sum + file.text.length, 0);
11854
+ if (size <= VERIFY_DIFF_BUDGET_TOKENS * 3)
11855
+ return { head, mode: "diff", files };
11856
+ return {
11857
+ head,
11858
+ mode: "ranges",
11859
+ files: files.map((file) => {
11860
+ const headers = file.text.split(`
11861
+ `).filter((line) => /^(?:@@|diff --git|rename |deleted file mode|new file mode|RENAMED|DELETED|Uncommitted changes:)/.test(line));
11862
+ const lines = file.text.split(`
11863
+ `);
11864
+ const added = lines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
11865
+ const removed = lines.filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
11866
+ return {
11867
+ ...file,
11868
+ text: `Changed lines: +${added}/-${removed}.
11869
+ ${headers.join(`
11870
+ `)}
11871
+ Read only these changed line ranges if they may affect the claim.`
11872
+ };
11873
+ })
11874
+ };
11875
+ } catch {
11876
+ return null;
11877
+ }
11878
+ }
11879
+
11471
11880
  // ../plugin/src/features/magic-context/dreamer/verify-gate.ts
11472
11881
  import path5 from "node:path";
11473
11882
  function minOf(values) {
@@ -11502,12 +11911,24 @@ async function partitionVerifyScope(args) {
11502
11911
  const active = getMemoriesByProject(args.db, args.projectIdentity);
11503
11912
  const verById = getMemoryVerifications(args.db, active.map((m) => m.id));
11504
11913
  const candidates = active.filter((m) => (verById.get(m.id)?.files.length ?? 0) > 0);
11505
- const toPrompt = (m) => ({
11506
- id: m.id,
11507
- category: m.category,
11508
- content: m.content,
11509
- mappedFiles: verById.get(m.id)?.files ?? []
11510
- });
11914
+ const toPrompt = (m) => {
11915
+ const verifiedAt = verById.get(m.id)?.verifiedAt ?? 0;
11916
+ let verifiedCommit = null;
11917
+ try {
11918
+ const metadata = JSON.parse(m.metadataJson ?? "{}");
11919
+ if (metadata.dreamerVerifiedAt === verifiedAt && typeof metadata.dreamerVerifiedCommit === "string" && /^[0-9a-f]{40}$/i.test(metadata.dreamerVerifiedCommit)) {
11920
+ verifiedCommit = metadata.dreamerVerifiedCommit;
11921
+ }
11922
+ } catch {}
11923
+ return {
11924
+ id: m.id,
11925
+ category: m.category,
11926
+ content: m.content,
11927
+ mappedFiles: verById.get(m.id)?.files ?? [],
11928
+ verifiedAt,
11929
+ verifiedCommit
11930
+ };
11931
+ };
11511
11932
  if (args.forceBroad) {
11512
11933
  const broadCycleStartAt = ensureBroadCycleStart({
11513
11934
  db: args.db,
@@ -11587,9 +12008,9 @@ async function partitionVerifyScope(args) {
11587
12008
  // ../plugin/src/features/magic-context/dreamer/verify-prompt.ts
11588
12009
  var VERIFY_SYSTEM_PROMPT = `You are a memory verifier for the magic-context system. You verify project memories against the CURRENT code.
11589
12010
 
11590
- Each memory below comes with its backing file(s) — the code it makes a claim about. For EACH memory: read its backing files (you may read more if needed) and decide whether the memory is still accurate.
12011
+ Each memory below comes with its backing file(s) — the code it makes a claim about. For EACH memory: use the provided git diff as evidence when present. If no diff is supplied, read its backing files (you may read more if needed) and decide whether the memory is still accurate.
11591
12012
 
11592
- Tools (read-only): read, grep, glob, aft_search, aft_outline, aft_zoom. You read code to check claims; you change nothing.
12013
+ Tools (read-only): read, grep, glob. Request line ranges and keep reads short. You read code only when the evidence requires it; you change nothing.
11593
12014
 
11594
12015
  Decide ONE of four outcomes per memory:
11595
12016
  - VERIFIED — still accurate. Keep it as-is.
@@ -11614,7 +12035,7 @@ Rules:
11614
12035
  - Every input memory id MUST appear exactly once, in exactly one of verified/update/archive/skip.
11615
12036
  - files = the COMPLETE current backing set (repo-relative, comma-separated). It may differ from the given mapping if a file moved — record what you actually verified against.
11616
12037
  - Default to VERIFIED. skip when code cannot decide the claim. update and archive are the exceptions, not the norm.`;
11617
- function buildVerifyPrompt(projectPath, memories) {
12038
+ function buildVerifyPrompt(projectPath, memories, evidence) {
11618
12039
  const list = memories.map((m) => `[${m.id}] ${m.category}
11619
12040
  Content: ${m.content}
11620
12041
  Backing files: ${m.mappedFiles.join(", ")}`).join(`
@@ -11624,11 +12045,18 @@ Backing files: ${m.mappedFiles.join(", ")}`).join(`
11624
12045
 
11625
12046
  Project: ${projectPath}
11626
12047
 
11627
- Read each memory's backing files, decide verified / update / archive / skip (default verified; behavioral directives only permit verified or skip), then output ONE <verify> manifest covering every id.
12048
+ ${evidence ? "The git diff below is the evidence. If changed hunks do not touch a claim, mark it verified without opening files. Open files only when a diff suggests the claim may have changed. A rename or deletion requires an update or archive when positively supported; do not archive on uncertainty." : "Read each memory's backing files to check its claims."} Decide verified / update / archive / skip (default verified; behavioral directives only permit verified or skip), then output ONE <verify> manifest covering every id.
11628
12049
 
11629
12050
  <memories>
11630
12051
  ${list}
11631
- </memories>`;
12052
+ </memories>${evidence ? `
12053
+
12054
+ <changed-files mode="${evidence.mode}">
12055
+ ${evidence.files.map((file) => `File: ${file.path} (memories: ${file.memoryIds.join(", ")})
12056
+ ${file.text}`).join(`
12057
+
12058
+ `)}
12059
+ </changed-files>` : ""}`;
11632
12060
  }
11633
12061
  function attrOf(s, name) {
11634
12062
  const m = s.match(new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`));
@@ -11752,8 +12180,11 @@ async function runVerify(args) {
11752
12180
  return result;
11753
12181
  }
11754
12182
  const batches = [];
11755
- for (let i = 0;i < gate.inScope.length; i += VERIFY_BATCH_SIZE) {
11756
- batches.push(gate.inScope.slice(i, i + VERIFY_BATCH_SIZE));
12183
+ const groups = !args.forceBroad && gate.mode === "incremental" ? [gate.inScope.filter((m) => m.verifiedAt), gate.inScope.filter((m) => !m.verifiedAt)] : [gate.inScope];
12184
+ for (const group of groups) {
12185
+ for (let i = 0;i < group.length; i += VERIFY_BATCH_SIZE) {
12186
+ batches.push(group.slice(i, i + VERIFY_BATCH_SIZE));
12187
+ }
11757
12188
  }
11758
12189
  const abortController = new AbortController;
11759
12190
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
@@ -11817,7 +12248,9 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11817
12248
  let promptSettled = false;
11818
12249
  const startedAt = Date.now();
11819
12250
  try {
11820
- const prompt = buildVerifyPrompt(args.projectIdentity, batch);
12251
+ const headAtPrompt = await readGitHead(args.sessionDirectory);
12252
+ const evidence = !args.forceBroad && batch.every((m) => m.verifiedAt) ? await buildVerifyDiffEvidence(args.sessionDirectory, batch) : null;
12253
+ const prompt = buildVerifyPrompt(args.projectIdentity, batch, evidence);
11821
12254
  if (args.hiddenCompletionExecutor) {
11822
12255
  const run = await runHiddenSingleShotPrompt({
11823
12256
  executor: args.hiddenCompletionExecutor,
@@ -11828,6 +12261,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11828
12261
  prompt,
11829
12262
  title: "magic-context-dream-verify",
11830
12263
  callContext: "dreamer:verify",
12264
+ metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
11831
12265
  model: args.model,
11832
12266
  fallbackModels: args.fallbackModels,
11833
12267
  language: args.language,
@@ -11844,7 +12278,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11844
12278
  status: "completed",
11845
12279
  messages: run.completion.messages ?? []
11846
12280
  });
11847
- return applyParsedVerifyManifest(args, batch, run.validated);
12281
+ return applyParsedVerifyManifest(args, batch, run.validated, evidence?.head ?? headAtPrompt);
11848
12282
  }
11849
12283
  const client = args.client;
11850
12284
  if (!client)
@@ -11872,7 +12306,10 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11872
12306
  parts: [{ type: "text", text: prompt, synthetic: true }]
11873
12307
  }
11874
12308
  }, {
11875
- transport: createPromptAsyncTransport(client, agentSessionId),
12309
+ transport: createPromptAsyncTransport(client, agentSessionId, {
12310
+ tokenBudget: args.tokenBudget,
12311
+ onBudgetUpdate: args.onBudgetUpdate
12312
+ }),
11876
12313
  timeoutMs: sliceMs,
11877
12314
  signal,
11878
12315
  fallbackModels: args.fallbackModels,
@@ -11901,7 +12338,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11901
12338
  });
11902
12339
  promptSettled = true;
11903
12340
  recordInvocation4(args, startedAt, { status: "completed", messages: run.output });
11904
- return await applyParsedVerifyManifest(args, batch, run.validated);
12341
+ return await applyParsedVerifyManifest(args, batch, run.validated, evidence?.head ?? headAtPrompt);
11905
12342
  } catch (error) {
11906
12343
  const desc = describeError(error);
11907
12344
  const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
@@ -11912,7 +12349,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11912
12349
  error,
11913
12350
  messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
11914
12351
  });
11915
- if (error instanceof DreamerModuleFailureError || signal.aborted)
12352
+ if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded || signal.aborted)
11916
12353
  throw error;
11917
12354
  if (promptFailure?.failureClass === "provider_timeout" || isPromptTimeoutError(error)) {
11918
12355
  return {
@@ -11947,7 +12384,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11947
12384
  });
11948
12385
  }
11949
12386
  }
11950
- async function applyParsedVerifyManifest(args, batch, parsed) {
12387
+ async function applyParsedVerifyManifest(args, batch, parsed, headAtPrompt) {
11951
12388
  const batchIds = new Set(batch.map((m) => m.id));
11952
12389
  const batchById = new Map(batch.map((memory) => [memory.id, memory]));
11953
12390
  const valid = {
@@ -11974,6 +12411,7 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
11974
12411
  return { verified: 0, updated: 0, archived: 0, skipped: 0, refused: 0 };
11975
12412
  }
11976
12413
  const now = Date.now();
12414
+ const verifiedHead = headAtPrompt === undefined ? await readGitHead(args.sessionDirectory) : headAtPrompt;
11977
12415
  const writes = [];
11978
12416
  let refused = 0;
11979
12417
  const skipped = valid.skipped.length;
@@ -12085,6 +12523,8 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
12085
12523
  continue;
12086
12524
  if (w.kind === "verify") {
12087
12525
  recordMemoryVerifications(args.db, w.id, w.files, now);
12526
+ if (verifiedHead)
12527
+ recordVerifiedCommit(args.db, memory, now, verifiedHead);
12088
12528
  verified += 1;
12089
12529
  } else if (w.kind === "update") {
12090
12530
  rewriteMemoryContent(args.db, memory, w.content, w.hash);
@@ -12109,6 +12549,15 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
12109
12549
  });
12110
12550
  return { verified, updated, archived, skipped, refused };
12111
12551
  }
12552
+ function recordVerifiedCommit(db, memory, at, head) {
12553
+ let metadata = {};
12554
+ try {
12555
+ const parsed = JSON.parse(memory.metadataJson ?? "{}");
12556
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
12557
+ metadata = parsed;
12558
+ } catch {}
12559
+ db.prepare("UPDATE memories SET metadata_json = ? WHERE id = ?").run(JSON.stringify({ ...metadata, dreamerVerifiedAt: at, dreamerVerifiedCommit: head }), memory.id);
12560
+ }
12112
12561
  async function normalizeFiles(args, rawFiles) {
12113
12562
  if (rawFiles.length === 0)
12114
12563
  return [];
@@ -12166,6 +12615,16 @@ function dreamRunFailureDetail(error) {
12166
12615
  }
12167
12616
  const described = describeError(error);
12168
12617
  const message = described.brief;
12618
+ if (error instanceof DreamTokenBudgetExceeded) {
12619
+ return {
12620
+ failure_class: "token_budget",
12621
+ model_attempted: null,
12622
+ models_tried: [],
12623
+ provider_error: null,
12624
+ timeout_ms: null,
12625
+ child_session_id: error.sessionId
12626
+ };
12627
+ }
12169
12628
  if (error instanceof Error && error.name === "HiddenAgentStepLimit") {
12170
12629
  return {
12171
12630
  failure_class: "step_limit",
@@ -12210,7 +12669,10 @@ function toCuratePromptMemory(memory, verificationById) {
12210
12669
  category: memory.category,
12211
12670
  content: memory.content,
12212
12671
  mappedFiles: verification?.files ?? [],
12213
- hasNoFileSentinel: verification?.hasSentinel ?? false
12672
+ hasNoFileSentinel: verification?.hasSentinel ?? false,
12673
+ importance: memory.importance,
12674
+ retrievalCount: memory.retrievalCount,
12675
+ seenCount: memory.seenCount
12214
12676
  };
12215
12677
  }
12216
12678
  function loadActiveMemoryPromptMemories(db, projectIdentity) {
@@ -12250,7 +12712,10 @@ function inspectCurateMemoryOperations(messages) {
12250
12712
  if (!isRecord(part.state) || part.state.status !== "completed")
12251
12713
  continue;
12252
12714
  const input = isRecord(part.state.input) ? part.state.input : null;
12253
- summary.completedActions.push(typeof input?.action === "string" ? input.action : "unknown");
12715
+ const action = input?.action;
12716
+ const output = typeof part.state.output === "string" ? part.state.output : "";
12717
+ if (output === "completed" && (action === "merge" || action === "archive" || action === "update") || action === "merge" && output.startsWith("Merged memories") || action === "archive" && output.startsWith("Archived memor") || action === "update" && output.startsWith("Updated memory"))
12718
+ summary.completedActions.push(action);
12254
12719
  }
12255
12720
  }
12256
12721
  return summary;
@@ -12298,6 +12763,7 @@ function createDreamTaskExecutor(deps) {
12298
12763
  };
12299
12764
  return async (config, ctx) => {
12300
12765
  const { db, projectIdentity, holderId, leaseKey } = ctx;
12766
+ deps.client?.resetDreamTokenBudget?.(config.task);
12301
12767
  const startedAt = Date.now();
12302
12768
  const leaseAcquisition = ctx.leaseAcquisition ?? acquireLeaseWithAcquisition(db, holderId, leaseKey) ?? (() => {
12303
12769
  throw new Error("Dream lease unavailable during executor setup");
@@ -12356,8 +12822,23 @@ function createDreamTaskExecutor(deps) {
12356
12822
  if (!leaseOwnershipMatches(db, holderId, leaseAcquisition.generation, leaseKey)) {
12357
12823
  throw new Error("Dream lease lost during executor setup");
12358
12824
  }
12825
+ let budgetSpent = 0;
12826
+ let budgetFinalized = false;
12827
+ const childSpending = new Map;
12828
+ const onBudgetUpdate = (state) => {
12829
+ if (state.sessionId) {
12830
+ const previous = childSpending.get(state.sessionId) ?? 0;
12831
+ budgetSpent += Math.max(0, state.spent - previous);
12832
+ childSpending.set(state.sessionId, state.spent);
12833
+ } else
12834
+ budgetSpent = Math.max(budgetSpent, state.spent);
12835
+ budgetFinalized ||= state.finalizeFired;
12836
+ };
12359
12837
  const recordRun = (status, error, extra) => {
12360
12838
  try {
12839
+ const hostBudget = deps.readTokenBudget?.(config.task) ?? deps.client?.readTokenBudget?.(config.task);
12840
+ if (hostBudget)
12841
+ onBudgetUpdate(hostBudget);
12361
12842
  insertDreamRun(db, {
12362
12843
  projectPath: projectIdentity,
12363
12844
  startedAt,
@@ -12373,6 +12854,14 @@ function createDreamTaskExecutor(deps) {
12373
12854
  failure: extra?.failure ?? dreamRunFailureDetail(error ?? "unknown dreamer failure")
12374
12855
  } : {},
12375
12856
  ...extra?.progress ? { progress: extra.progress } : {},
12857
+ ...config.tokenBudget ? {
12858
+ tokenBudget: {
12859
+ budget: config.tokenBudget,
12860
+ spent: budgetSpent,
12861
+ finalizeFired: budgetFinalized,
12862
+ banked: extra?.banked ?? processedDreamTaskItems(backlogAtStart.pending, (extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task)).pending)
12863
+ }
12864
+ } : {},
12376
12865
  backlog: (() => {
12377
12866
  const end = extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task);
12378
12867
  const processed = processedDreamTaskItems(backlogAtStart.pending, end.pending);
@@ -12463,7 +12952,7 @@ function createDreamTaskExecutor(deps) {
12463
12952
  leaseKey,
12464
12953
  deadline,
12465
12954
  leaseAcquisition,
12466
- promotionThreshold: config.promotionThreshold ?? 3,
12955
+ promotionThreshold: config.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"],
12467
12956
  model: config.model,
12468
12957
  fallbackModels: config.fallbackModels,
12469
12958
  language: config.language ?? deps.language
@@ -12487,6 +12976,8 @@ function createDreamTaskExecutor(deps) {
12487
12976
  model: config.model,
12488
12977
  fallbackModels: config.fallbackModels,
12489
12978
  moduleRoute,
12979
+ tokenBudget: config.tokenBudget,
12980
+ onBudgetUpdate,
12490
12981
  onProgress: (processed) => reportProgress(processed)
12491
12982
  });
12492
12983
  log(`[dreamer] map-memories: committed=${result.mapped + result.independent} mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}${result.stopReason ? ` stop_reason=${result.stopReason}` : ""}`);
@@ -12527,6 +13018,8 @@ function createDreamTaskExecutor(deps) {
12527
13018
  fallbackModels: config.fallbackModels,
12528
13019
  language: config.language ?? deps.language,
12529
13020
  moduleRoute,
13021
+ tokenBudget: config.tokenBudget,
13022
+ onBudgetUpdate,
12530
13023
  onProgress: (processed, refused) => reportProgress(processed, refused)
12531
13024
  });
12532
13025
  const processed = result.verified + result.updated + result.archived + result.skipped + result.refused;
@@ -12538,7 +13031,8 @@ function createDreamTaskExecutor(deps) {
12538
13031
  recordRun("completed", null, {
12539
13032
  progress: broadProgress,
12540
13033
  memoryChanges: computeMemoryDelta(memoryBefore),
12541
- backlogAfter
13034
+ backlogAfter,
13035
+ banked: result.verified + result.updated + result.archived
12542
13036
  });
12543
13037
  return { status: "completed" };
12544
13038
  }
@@ -12547,14 +13041,16 @@ function createDreamTaskExecutor(deps) {
12547
13041
  recordRun("failed", error, {
12548
13042
  progress: verificationProgress,
12549
13043
  memoryChanges: computeMemoryDelta(memoryBefore),
12550
- backlogAfter
13044
+ backlogAfter,
13045
+ banked: result.verified + result.updated + result.archived
12551
13046
  });
12552
13047
  return { status: "failed", transient: !batchTimedOut, error };
12553
13048
  }
12554
13049
  recordRun("completed", null, {
12555
13050
  progress: verificationProgress,
12556
13051
  memoryChanges: computeMemoryDelta(memoryBefore),
12557
- backlogAfter
13052
+ backlogAfter,
13053
+ banked: result.verified + result.updated + result.archived
12558
13054
  });
12559
13055
  return { status: "completed" };
12560
13056
  }
@@ -12606,7 +13102,7 @@ function createDreamTaskExecutor(deps) {
12606
13102
  leaseKey,
12607
13103
  deadline,
12608
13104
  leaseAcquisition,
12609
- promotionThreshold: config.promotionThreshold ?? 2,
13105
+ promotionThreshold: config.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"],
12610
13106
  ensureProjectRegistered: deps.ensureProjectRegistered
12611
13107
  });
12612
13108
  recordRun("completed", null);
@@ -12629,6 +13125,8 @@ function createDreamTaskExecutor(deps) {
12629
13125
  fallbackModels: config.fallbackModels,
12630
13126
  language: config.language ?? deps.language,
12631
13127
  rawProviderFactory: deps.primerRawProviderFactory,
13128
+ tokenBudget: config.tokenBudget,
13129
+ onBudgetUpdate,
12632
13130
  onProgress: (processed) => reportProgress(processed)
12633
13131
  });
12634
13132
  recordRun("completed", null);
@@ -12659,6 +13157,7 @@ function createDreamTaskExecutor(deps) {
12659
13157
  if (config.task === "retrospective") {
12660
13158
  const memoryBefore = getMemoryCountsByStatus(db, projectIdentity);
12661
13159
  const retro = await runRetrospectiveTask(config, ctx, {
13160
+ onBudgetUpdate,
12662
13161
  deps,
12663
13162
  deadline,
12664
13163
  parent,
@@ -12666,7 +13165,10 @@ function createDreamTaskExecutor(deps) {
12666
13165
  moduleRoute
12667
13166
  });
12668
13167
  recordRun("completed", null, {
12669
- memoryChanges: computeMemoryDelta(memoryBefore)
13168
+ memoryChanges: computeMemoryDelta(memoryBefore),
13169
+ backlogAfter: retro.retrospectiveWatermarkMs == null ? undefined : getDreamTaskBacklog(db, projectIdentity, "retrospective", {
13170
+ retrospectiveWatermarkMs: retro.retrospectiveWatermarkMs
13171
+ })
12670
13172
  });
12671
13173
  return {
12672
13174
  status: "completed",
@@ -12679,6 +13181,7 @@ function createDreamTaskExecutor(deps) {
12679
13181
  };
12680
13182
  }
12681
13183
  return await runAgenticTask(config, ctx, {
13184
+ onBudgetUpdate,
12682
13185
  deps,
12683
13186
  deadline,
12684
13187
  parent,
@@ -12689,6 +13192,9 @@ function createDreamTaskExecutor(deps) {
12689
13192
  moduleRoute
12690
13193
  });
12691
13194
  } catch (error) {
13195
+ if (error instanceof DreamTokenBudgetExceeded) {
13196
+ budgetSpent = Math.max(budgetSpent, error.spent);
13197
+ }
12692
13198
  const classified = classifyFailure(error);
12693
13199
  const retrospectiveOverflow = error instanceof RetrospectivePromptOverflowError ? error : null;
12694
13200
  const transient = retrospectiveOverflow ? true : classified.transient;
@@ -12847,7 +13353,7 @@ function resolveRetrospectiveUsableInputTokens(config, deps) {
12847
13353
  }
12848
13354
  async function runRetrospectiveTask(config, ctx, helpers) {
12849
13355
  const { db, projectIdentity, holderId, leaseKey } = ctx;
12850
- const { deps, deadline, parent } = helpers;
13356
+ const { deps, deadline, parent, onBudgetUpdate } = helpers;
12851
13357
  const provider = resolveRetrospectiveProvider(deps, db, projectIdentity);
12852
13358
  if (!provider) {
12853
13359
  log("[dreamer] retrospective: no raw provider available — clean no-op");
@@ -12901,7 +13407,8 @@ async function runRetrospectiveTask(config, ctx, helpers) {
12901
13407
  ],
12902
13408
  timeoutMs: Math.max(1, deadline - Date.now()),
12903
13409
  title: "magic-context-dream-retrospective",
12904
- directory: deps.sessionDirectory
13410
+ directory: deps.sessionDirectory,
13411
+ metadata: { tokenBudget: config.tokenBudget, onBudgetUpdate }
12905
13412
  });
12906
13413
  childSessionId = hiddenHandle.id;
12907
13414
  } else {
@@ -12937,6 +13444,12 @@ async function runRetrospectiveTask(config, ctx, helpers) {
12937
13444
  signal: abortController.signal,
12938
13445
  fallbackModels: config.fallbackModels,
12939
13446
  callContext: "dreamer:retrospective",
13447
+ ...!hiddenHandle && deps.client ? {
13448
+ transport: createPromptAsyncTransport(deps.client, sessionId, {
13449
+ tokenBudget: config.tokenBudget,
13450
+ onBudgetUpdate
13451
+ })
13452
+ } : {},
12940
13453
  ...hiddenHandle && deps.hiddenCompletionExecutor ? {
12941
13454
  transport: Object.assign((request) => deps.hiddenCompletionExecutor?.attempt(hiddenHandle, request) ?? Promise.reject(new Error("Hidden transport unavailable")), { childSessionId: hiddenHandle.childSessionId })
12942
13455
  } : {},
@@ -13112,7 +13625,7 @@ async function runRetrospectiveTask(config, ctx, helpers) {
13112
13625
  }
13113
13626
  async function runAgenticTask(config, ctx, helpers) {
13114
13627
  const { db, projectIdentity, holderId, leaseKey } = ctx;
13115
- const { deps, deadline, parent } = helpers;
13628
+ const { deps, deadline, parent, onBudgetUpdate } = helpers;
13116
13629
  const task = config.task;
13117
13630
  const docsDir = deps.sessionDirectory;
13118
13631
  const invocationStartedAt = Date.now();
@@ -13166,122 +13679,178 @@ async function runAgenticTask(config, ctx, helpers) {
13166
13679
  return { status: "completed", detail: progress };
13167
13680
  }
13168
13681
  }
13169
- if (task === "maintain-docs" && (hasCurrentDocsProposal(docsDir) || !changes)) {
13170
- const reason = hasCurrentDocsProposal(docsDir) ? "pending proposal matches current docs" : "no relevant commits or no git repository";
13171
- log(`[dreamer] maintain-docs skipped: ${reason}`);
13682
+ if (task === "maintain-docs" && !changes) {
13683
+ const reason = "no relevant commits or no git repository";
13172
13684
  helpers.recordRun("completed", null, { progress: reason });
13173
13685
  return { status: "completed", detail: reason };
13174
13686
  }
13175
- const taskPrompt = buildDreamTaskPrompt(task, {
13176
- projectPath: projectIdentity,
13177
- lastDreamAt: lastRunAt ? String(lastRunAt) : null,
13178
- existingDocs,
13179
- docsChangeSet: changes?.text,
13180
- docsBudget: config.docsMaxTokens ?? 12000,
13181
- docsCurrentTokens: maintainDocsSnapshot ? Math.ceil([...maintainDocsSnapshot.values()].join("").length / 3.5) : 0,
13182
- curate: curateMemories && curateCategory ? { category: curateCategory, memories: curateMemories } : undefined
13183
- });
13184
- let run;
13185
- if (deps.hiddenCompletionExecutor) {
13186
- const hidden = await runHiddenSingleShotPrompt({
13187
- executor: deps.hiddenCompletionExecutor,
13188
- parentSessionId: parent ?? undefined,
13189
- sessionDirectory: docsDir,
13190
- agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
13191
- system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : CURATE_SYSTEM_PROMPT,
13192
- prompt: taskPrompt,
13193
- title: `magic-context-dream-${task}`,
13194
- callContext: `dreamer:${task}`,
13195
- allowEmpty: task === "curate",
13196
- model: config.model,
13197
- fallbackModels: config.fallbackModels,
13198
- language: task === "curate" ? config.language ?? deps.language : undefined,
13199
- timeoutMs: Math.min(Math.max(0, deadline - Date.now()), config.timeoutMinutes * 60 * 1000),
13200
- signal: abortController.signal,
13201
- parse: (text, completion) => {
13202
- if (task !== "curate")
13203
- return text;
13204
- const memoryOperations = inspectCurateMemoryOperations(completion.messages);
13205
- if (text)
13206
- validateCurateAssistantText(text);
13207
- if (memoryOperations.totalCalls > 0 && memoryOperations.completedActions.length === 0)
13208
- throw new Error("Curate returned no completed ctx_memory tool result.");
13209
- if (!text && memoryOperations.completedActions.length === 0)
13210
- throw new Error("Dreamer returned no assistant output.");
13211
- return { text: text || null, memoryOperations };
13212
- }
13213
- });
13214
- childSessionId = hidden.childSessionId;
13215
- run = { output: hidden.completion.messages ?? [], validated: hidden.validated };
13216
- promptSettled = true;
13217
- } else {
13218
- const createResponse = await createChildSessionWithFence({
13219
- client: requireDreamClient(deps.client),
13220
- db,
13221
- parentSessionId: parent ?? undefined,
13222
- title: `magic-context-dream-${task}`,
13223
- directory: docsDir
13224
- });
13225
- const created = normalizeSDKResponse(createResponse, null, {
13226
- preferResponseOnMissingData: true
13687
+ if (task === "maintain-docs" && changes && (changes.unchanged || !changes.relevant)) {
13688
+ const reason = changes.unchanged ? "no commits since last run" : "diff does not describe a documented area";
13689
+ helpers.recordRun("completed", null, { progress: reason });
13690
+ return {
13691
+ status: "completed",
13692
+ detail: reason,
13693
+ schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) }
13694
+ };
13695
+ }
13696
+ if (task === "maintain-docs" && hasCurrentDocsProposal(docsDir)) {
13697
+ const reason = "pending proposal matches current docs";
13698
+ helpers.recordRun("completed", null, { progress: reason });
13699
+ return {
13700
+ status: "completed",
13701
+ detail: reason,
13702
+ ...changes ? { schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) } } : {}
13703
+ };
13704
+ }
13705
+ const curateChunks = curateMemories ? chunkCurateMemories(curateMemories, Math.max(4000, Math.floor(resolveRetrospectiveUsableInputTokens(config, deps) * 2))) : [];
13706
+ const batches = task === "curate" ? curateChunks : [{ memories: [], crossChunkCandidates: [] }];
13707
+ const completedActions = [];
13708
+ let curateRefused = 0;
13709
+ let run = {
13710
+ output: [],
13711
+ validated: ""
13712
+ };
13713
+ for (const batch of batches) {
13714
+ const taskPrompt = buildDreamTaskPrompt(task, {
13715
+ projectPath: projectIdentity,
13716
+ lastDreamAt: lastRunAt ? String(lastRunAt) : null,
13717
+ existingDocs,
13718
+ docsChangeSet: changes?.text,
13719
+ docsContents: task === "maintain-docs" ? {
13720
+ architecture: existingDocs?.architecture ? readFileSync5(`${docsDir}/ARCHITECTURE.md`, "utf8") : "(missing)",
13721
+ structure: existingDocs?.structure ? readFileSync5(`${docsDir}/STRUCTURE.md`, "utf8") : "(missing)"
13722
+ } : undefined,
13723
+ docsBudget: config.docsMaxTokens ?? 12000,
13724
+ docsCurrentTokens: maintainDocsSnapshot ? Math.ceil([...maintainDocsSnapshot.values()].join("").length / 3.5) : 0,
13725
+ curate: curateMemories && curateCategory ? {
13726
+ category: curateCategory,
13727
+ memories: batch.memories,
13728
+ crossChunkCandidates: batch.crossChunkCandidates
13729
+ } : undefined
13227
13730
  });
13228
- childSessionId = typeof created?.id === "string" ? created.id : null;
13229
- if (!childSessionId)
13230
- throw new Error("Dreamer could not create its child session.");
13231
- const sessionId = childSessionId;
13232
- const remainingMs = Math.max(0, deadline - Date.now());
13233
- run = await promptSyncWithValidatedOutputRetry(requireDreamClient(deps.client), {
13234
- path: { id: sessionId },
13235
- query: { directory: docsDir },
13236
- body: {
13731
+ if (deps.hiddenCompletionExecutor) {
13732
+ const hidden = await runHiddenSingleShotPrompt({
13733
+ executor: deps.hiddenCompletionExecutor,
13734
+ parentSessionId: parent ?? undefined,
13735
+ sessionDirectory: docsDir,
13237
13736
  agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
13238
- system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : withContentLanguageDirective(CURATE_SYSTEM_PROMPT, config.language ?? deps.language),
13239
- ...modelBodyField(config.model),
13240
- parts: [{ type: "text", text: taskPrompt, synthetic: true }]
13241
- }
13242
- }, {
13243
- timeoutMs: Math.min(remainingMs, config.timeoutMinutes * 60 * 1000),
13244
- signal: abortController.signal,
13245
- fallbackModels: config.fallbackModels,
13246
- callContext: `dreamer:${task}`,
13247
- fetchOutput: async () => {
13248
- const messagesResponse = await requireDreamClient(deps.client).session.messages({
13249
- path: { id: sessionId },
13250
- query: {
13251
- directory: docsDir,
13252
- ...task === "curate" ? {} : { limit: 50 }
13737
+ system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : CURATE_SYSTEM_PROMPT,
13738
+ prompt: taskPrompt,
13739
+ title: `magic-context-dream-${task}`,
13740
+ callContext: `dreamer:${task}`,
13741
+ metadata: { tokenBudget: config.tokenBudget, onBudgetUpdate },
13742
+ allowEmpty: task === "curate",
13743
+ model: config.model,
13744
+ fallbackModels: config.fallbackModels,
13745
+ language: task === "curate" ? config.language ?? deps.language : undefined,
13746
+ timeoutMs: Math.min(Math.max(0, deadline - Date.now()), config.timeoutMinutes * 60 * 1000),
13747
+ signal: abortController.signal,
13748
+ parse: (text, completion) => {
13749
+ if (task !== "curate")
13750
+ return text;
13751
+ const memoryOperations = inspectCurateMemoryOperations(completion.messages);
13752
+ if (text)
13753
+ validateCurateAssistantText(text);
13754
+ if (memoryOperations.totalCalls > 0 && memoryOperations.completedActions.length === 0)
13755
+ throw new Error("Curate returned no completed ctx_memory tool result.");
13756
+ if (!text && memoryOperations.completedActions.length === 0)
13757
+ throw new Error("Dreamer returned no assistant output.");
13758
+ return { text: text || null, memoryOperations };
13759
+ }
13760
+ });
13761
+ childSessionId = hidden.childSessionId;
13762
+ run = { output: hidden.completion.messages ?? [], validated: hidden.validated };
13763
+ promptSettled = true;
13764
+ } else {
13765
+ const createResponse = await createChildSessionWithFence({
13766
+ client: requireDreamClient(deps.client),
13767
+ db,
13768
+ parentSessionId: parent ?? undefined,
13769
+ title: `magic-context-dream-${task}`,
13770
+ directory: docsDir
13771
+ });
13772
+ const created = normalizeSDKResponse(createResponse, null, {
13773
+ preferResponseOnMissingData: true
13774
+ });
13775
+ childSessionId = typeof created?.id === "string" ? created.id : null;
13776
+ if (!childSessionId)
13777
+ throw new Error("Dreamer could not create its child session.");
13778
+ const sessionId = childSessionId;
13779
+ const remainingMs = Math.max(0, deadline - Date.now());
13780
+ run = await promptSyncWithValidatedOutputRetry(requireDreamClient(deps.client), {
13781
+ path: { id: sessionId },
13782
+ query: { directory: docsDir },
13783
+ body: {
13784
+ agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
13785
+ system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : withContentLanguageDirective(CURATE_SYSTEM_PROMPT, config.language ?? deps.language),
13786
+ ...modelBodyField(config.model),
13787
+ parts: [{ type: "text", text: taskPrompt, synthetic: true }]
13788
+ }
13789
+ }, {
13790
+ timeoutMs: Math.min(remainingMs, config.timeoutMinutes * 60 * 1000),
13791
+ signal: abortController.signal,
13792
+ fallbackModels: config.fallbackModels,
13793
+ callContext: `dreamer:${task}`,
13794
+ transport: createPromptAsyncTransport(requireDreamClient(deps.client), sessionId, { tokenBudget: config.tokenBudget, onBudgetUpdate }),
13795
+ fetchOutput: async () => {
13796
+ const messagesResponse = await requireDreamClient(deps.client).session.messages({
13797
+ path: { id: sessionId },
13798
+ query: {
13799
+ directory: docsDir,
13800
+ ...task === "curate" ? {} : { limit: 50 }
13801
+ }
13802
+ });
13803
+ return normalizeSDKResponse(messagesResponse, [], {
13804
+ preferResponseOnMissingData: true
13805
+ });
13806
+ },
13807
+ validateOutput: (messages) => {
13808
+ const text = extractLatestAssistantText(messages);
13809
+ if (task !== "curate") {
13810
+ if (!text)
13811
+ throw new Error("Dreamer returned no assistant output.");
13812
+ return text;
13813
+ }
13814
+ const memoryOperations = inspectCurateMemoryOperations(messages);
13815
+ if (text)
13816
+ validateCurateAssistantText(text);
13817
+ if (memoryOperations.completedActions.length > 0) {
13818
+ return { text, memoryOperations };
13253
13819
  }
13254
- });
13255
- return normalizeSDKResponse(messagesResponse, [], {
13256
- preferResponseOnMissingData: true
13257
- });
13258
- },
13259
- validateOutput: (messages) => {
13260
- const text = extractLatestAssistantText(messages);
13261
- if (task !== "curate") {
13262
13820
  if (!text)
13263
13821
  throw new Error("Dreamer returned no assistant output.");
13264
- return text;
13265
- }
13266
- const memoryOperations = inspectCurateMemoryOperations(messages);
13267
- if (text)
13268
- validateCurateAssistantText(text);
13269
- if (memoryOperations.completedActions.length > 0) {
13822
+ if (memoryOperations.totalCalls > 0) {
13823
+ throw new Error("Curate returned no completed ctx_memory tool result.");
13824
+ }
13270
13825
  return { text, memoryOperations };
13271
13826
  }
13272
- if (!text)
13273
- throw new Error("Dreamer returned no assistant output.");
13274
- if (memoryOperations.totalCalls > 0) {
13275
- throw new Error("Curate returned no completed ctx_memory tool result.");
13276
- }
13277
- return { text, memoryOperations };
13278
- }
13279
- });
13280
- promptSettled = true;
13827
+ });
13828
+ promptSettled = true;
13829
+ }
13830
+ if (task === "curate") {
13831
+ completedActions.push(...run.validated.memoryOperations.completedActions);
13832
+ curateRefused += takeCurateSafetyRefusalCount(childSessionId ?? "");
13833
+ }
13834
+ if (childSessionId && !deps.hiddenCompletionExecutor) {
13835
+ await teardownChildSession({
13836
+ client: requireDreamClient(deps.client),
13837
+ sessionId: childSessionId,
13838
+ sessionDirectory: docsDir,
13839
+ promptSettled,
13840
+ privacySensitive: true,
13841
+ context: `[dreamer] ${task}`,
13842
+ log
13843
+ });
13844
+ childSessionId = null;
13845
+ }
13281
13846
  }
13847
+ if (task === "curate")
13848
+ run.validated = {
13849
+ text: null,
13850
+ memoryOperations: { totalCalls: completedActions.length, completedActions }
13851
+ };
13282
13852
  if (leaseLost)
13283
13853
  throw new Error("Dream lease lost during task");
13284
- const curateRefused = task === "curate" ? takeCurateSafetyRefusalCount(childSessionId ?? "") : 0;
13285
13854
  if (curateRefused > 0) {
13286
13855
  helpers.reportProgress(curateRefused, curateRefused);
13287
13856
  log(`[dreamer] curate safety summary: refused=${curateRefused}`);
@@ -13302,7 +13871,11 @@ async function runAgenticTask(config, ctx, helpers) {
13302
13871
  try {
13303
13872
  if (run.validated === "[]") {
13304
13873
  helpers.recordRun("completed", null, { progress: "no corrections proposed" });
13305
- return { status: "completed", detail: "no corrections proposed" };
13874
+ return {
13875
+ status: "completed",
13876
+ detail: "no corrections proposed",
13877
+ schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) }
13878
+ };
13306
13879
  }
13307
13880
  const path = writeDocsProposal(docsDir, String(run.validated), config.docsMaxTokens ?? 12000, docsHashes, changes.head);
13308
13881
  helpers.recordRun("completed", null, { progress: `proposal: ${path}` });
@@ -13520,7 +14093,7 @@ async function runDomainGroup(deps, group, cb) {
13520
14093
  projectIdentity,
13521
14094
  lastRunAt: readLastRunAt(db, projectIdentity, due.config.task),
13522
14095
  retrospectiveWatermarkMs: readRetrospectiveWatermark(db, projectIdentity, due.config.task),
13523
- promotionThreshold: due.config.promotionThreshold ?? 3
14096
+ promotionThreshold: due.config.promotionThreshold ?? (due.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
13524
14097
  });
13525
14098
  if (!gatePass) {
13526
14099
  advanceAfterRun(db, projectIdentity, due, Date.now(), "skipped", null);
@@ -13568,6 +14141,8 @@ async function runManualDream(deps) {
13568
14141
  backlogBefore: {},
13569
14142
  backlogAfter: {}
13570
14143
  };
14144
+ if (!isUsableProjectIdentity(deps.projectIdentity))
14145
+ return result;
13571
14146
  let selected;
13572
14147
  let forceGate = false;
13573
14148
  if (deps.task) {
@@ -13598,7 +14173,7 @@ async function runManualDream(deps) {
13598
14173
  projectIdentity: deps.projectIdentity,
13599
14174
  lastRunAt: readLastRunAt(deps.db, deps.projectIdentity, d.config.task),
13600
14175
  retrospectiveWatermarkMs: readRetrospectiveWatermark(deps.db, deps.projectIdentity, d.config.task),
13601
- promotionThreshold: d.config.promotionThreshold ?? 3
14176
+ promotionThreshold: d.config.promotionThreshold ?? (d.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
13602
14177
  });
13603
14178
  if (pass)
13604
14179
  gated.push(d);
@@ -13641,6 +14216,8 @@ async function runManualDream(deps) {
13641
14216
  return result;
13642
14217
  }
13643
14218
  async function runDueTasksForProject(deps) {
14219
+ if (!isUsableProjectIdentity(deps.projectIdentity))
14220
+ return 0;
13644
14221
  const now = deps.now ?? Date.now();
13645
14222
  const due = planDueTasks(deps.db, deps.projectIdentity, deps.tasks, now);
13646
14223
  if (due.length === 0)
@@ -13652,7 +14229,7 @@ async function runDueTasksForProject(deps) {
13652
14229
  projectIdentity: deps.projectIdentity,
13653
14230
  lastRunAt: readLastRunAt(deps.db, deps.projectIdentity, d.config.task),
13654
14231
  retrospectiveWatermarkMs: readRetrospectiveWatermark(deps.db, deps.projectIdentity, d.config.task),
13655
- promotionThreshold: d.config.promotionThreshold ?? 3
14232
+ promotionThreshold: d.config.promotionThreshold ?? (d.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
13656
14233
  });
13657
14234
  if (pass) {
13658
14235
  gated.push(d);
@@ -13886,6 +14463,13 @@ function openTimerDatabaseOrNull(context) {
13886
14463
  }
13887
14464
  return db;
13888
14465
  }
14466
+ var refusedEmptyIdentityDirectories = new Set;
14467
+ function logEmptyIdentityRefusalOnce(directory) {
14468
+ if (refusedEmptyIdentityDirectories.has(directory))
14469
+ return;
14470
+ refusedEmptyIdentityDirectories.add(directory);
14471
+ log(`[dreamer] not registering ${directory}: it has no project identity, so no project-scoped work runs for it`);
14472
+ }
13889
14473
  var registeredProjects = new Map;
13890
14474
  function stopDreamScheduleTimerIfIdle() {
13891
14475
  if (registeredProjects.size !== 0)
@@ -13905,6 +14489,10 @@ function stopDreamScheduleTimerIfIdle() {
13905
14489
  log("[dreamer] stopped dream schedule timer (no projects left)");
13906
14490
  }
13907
14491
  async function startDreamScheduleTimer(args) {
14492
+ if (!isUsableProjectIdentity(args.projectIdentity)) {
14493
+ logEmptyIdentityRefusalOnce(args.directory);
14494
+ return;
14495
+ }
13908
14496
  beginBootQuietPeriod();
13909
14497
  const db = openTimerDatabaseOrNull("schedule timer registration");
13910
14498
  if (!db)
@@ -14268,7 +14856,7 @@ import * as childProcess from "node:child_process";
14268
14856
  import {
14269
14857
  existsSync as existsSync7,
14270
14858
  mkdtempSync as mkdtempSync2,
14271
- readFileSync as readFileSync5,
14859
+ readFileSync as readFileSync6,
14272
14860
  realpathSync as realpathSync3,
14273
14861
  rmSync as rmSync2,
14274
14862
  statSync as statSync4,
@@ -14297,7 +14885,7 @@ var DEFAULT_PI_RESOLUTION_FS = {
14297
14885
  existsSync: existsSync7,
14298
14886
  statSync: statSync4,
14299
14887
  realpathSync: realpathSync3,
14300
- readFileSync: readFileSync5
14888
+ readFileSync: readFileSync6
14301
14889
  };
14302
14890
  function packageHarnessKind(packageName) {
14303
14891
  if (packageName === "@oh-my-pi/pi-coding-agent")
@@ -14594,12 +15182,6 @@ var DREAMER_ACTION_AGENTS = new Set([
14594
15182
  "dreamer",
14595
15183
  "magic-context-dreamer"
14596
15184
  ]);
14597
- var HISTORIAN_AGENTS = new Set([
14598
- "magic-context-historian",
14599
- "historian",
14600
- "historian-recomp",
14601
- "historian-editor"
14602
- ]);
14603
15185
  var SEARCH_ONLY_SUBAGENT_TOOL_AGENTS = new Set([
14604
15186
  "dreamer-retrospective",
14605
15187
  "dreamer-primer-investigator"
@@ -14789,7 +15371,15 @@ class PiSubagentRunner {
14789
15371
  async runOnce(options, runMode, modelRefOverride) {
14790
15372
  const startTime = Date.now();
14791
15373
  let recordedAccounting = false;
15374
+ const cap = subagentStepCap(options.agent);
15375
+ const budget = options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined;
15376
+ const telemetry = new SubagentTelemetry(options.systemPrompt, (line) => sessionLog(options.accountingSessionId ?? "subagent", line));
15377
+ let loggedTelemetry = false;
14792
15378
  const recordAccounting = (result, messages = []) => {
15379
+ if (!loggedTelemetry) {
15380
+ loggedTelemetry = true;
15381
+ telemetry.finish(options.agent, result.ok ? "completed" : result.reason, cap);
15382
+ }
14793
15383
  if (!options.accountingSessionId || recordedAccounting)
14794
15384
  return;
14795
15385
  recordedAccounting = true;
@@ -14845,7 +15435,9 @@ class PiSubagentRunner {
14845
15435
  return failBeforeSpawn("spawn_failed", `Magic Context: plugin build is older than its database (database=v${fence.failure.persistedVersion}, supported_fence=v${fence.failure.supportedVersion}) — restart Pi.`);
14846
15436
  }
14847
15437
  const promptBytes = Buffer.byteLength(options.userMessage, "utf8");
14848
- const deliverViaStdin = promptBytes > PROMPT_ARGV_MAX_BYTES || this.platform === "win32";
15438
+ const budgetToolGuardAvailable = SUBAGENT_ENTRY_PATH !== undefined || this.subagentExtensions?.some((extension) => basename(extension) === "subagent-entry.js") === true;
15439
+ const rpcBudget = options.tokenBudget !== undefined && this.invocation.targetHarness === "pi" && budgetToolGuardAvailable;
15440
+ const deliverViaStdin = !rpcBudget && (promptBytes > PROMPT_ARGV_MAX_BYTES || this.platform === "win32");
14849
15441
  let systemPromptTempDir;
14850
15442
  let systemPromptPath;
14851
15443
  const cleanupSystemPromptFile = () => {
@@ -14868,13 +15460,30 @@ class PiSubagentRunner {
14868
15460
  return failBeforeSpawn("spawn_failed", `failed to prepare pi system prompt file: ${error instanceof Error ? error.message : String(error)}`);
14869
15461
  }
14870
15462
  }
15463
+ if (rpcBudget && !systemPromptTempDir) {
15464
+ try {
15465
+ systemPromptTempDir = mkdtempSync2(join5(tmpdir2(), "mc-pi-prompt-"));
15466
+ } catch (error) {
15467
+ return failBeforeSpawn("spawn_failed", `failed to prepare pi budget guard: ${error instanceof Error ? error.message : String(error)}`);
15468
+ }
15469
+ }
15470
+ const budgetGuardFile = rpcBudget && systemPromptTempDir ? join5(systemPromptTempDir, "budget-guard") : undefined;
15471
+ if (budgetGuardFile) {
15472
+ try {
15473
+ writeFileSync5(budgetGuardFile, "", "utf8");
15474
+ } catch (error) {
15475
+ cleanupSystemPromptFile();
15476
+ return failBeforeSpawn("spawn_failed", `failed to write pi budget guard: ${error instanceof Error ? error.message : String(error)}`);
15477
+ }
15478
+ }
14871
15479
  const args = buildArgs(options, {
14872
15480
  targetHarness: this.invocation.targetHarness,
14873
15481
  disableDiscoveredExtensions: runMode.disableDiscoveredExtensions,
14874
15482
  subagentExtensions: this.subagentExtensions,
14875
- omitPositionalMessage: deliverViaStdin,
15483
+ omitPositionalMessage: deliverViaStdin || rpcBudget,
14876
15484
  systemPromptPath,
14877
- modelRef: modelRefOverride
15485
+ modelRef: modelRefOverride,
15486
+ rpc: rpcBudget
14878
15487
  });
14879
15488
  return new Promise((resolve) => {
14880
15489
  let accountingMessages = [];
@@ -14883,13 +15492,17 @@ class PiSubagentRunner {
14883
15492
  if (settled)
14884
15493
  return;
14885
15494
  settled = true;
15495
+ const outcome = budget ? {
15496
+ ...result,
15497
+ meta: { ...result.meta, tokenBudget: budget.snapshot() }
15498
+ } : result;
14886
15499
  cleanupSystemPromptFile();
14887
15500
  try {
14888
- recordAccounting(result, accountingMessages);
15501
+ recordAccounting(outcome, accountingMessages);
14889
15502
  } catch (err) {
14890
15503
  sessionLog(options.accountingSessionId ?? "subagent", `subagent accounting failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
14891
15504
  }
14892
- resolve(result);
15505
+ resolve(outcome);
14893
15506
  };
14894
15507
  const emitProgress = (event) => {
14895
15508
  if (!options.onProgress)
@@ -14901,10 +15514,14 @@ class PiSubagentRunner {
14901
15514
  let child;
14902
15515
  try {
14903
15516
  child = this.spawnImpl(this.invocation.command, [...this.invocation.prefixArgs, ...this.extraArgs, ...args], {
15517
+ windowsHide: true,
14904
15518
  cwd: options.cwd,
14905
15519
  env: {
14906
15520
  ...process.env,
14907
15521
  [MAGIC_CONTEXT_PI_SUBAGENT_ENV]: "1",
15522
+ MAGIC_CONTEXT_SUBAGENT_PROMPT_FILE: systemPromptPath,
15523
+ MAGIC_CONTEXT_SUBAGENT_BUDGET_GUARD_FILE: budgetGuardFile,
15524
+ MAGIC_CONTEXT_SUBAGENT_PROVENANCE_ONLY: /^(magic-context-)?historian(?:-recomp|-editor)?$/.test(options.agent) ? undefined : "1",
14908
15525
  ...options.temperature !== undefined ? {
14909
15526
  MAGIC_CONTEXT_HISTORIAN_TEMPERATURE: String(options.temperature)
14910
15527
  } : {},
@@ -14912,7 +15529,11 @@ class PiSubagentRunner {
14912
15529
  MAGIC_CONTEXT_HISTORIAN_MAX_OUTPUT_TOKENS: String(options.maxOutputTokens)
14913
15530
  } : {}
14914
15531
  },
14915
- stdio: [deliverViaStdin ? "pipe" : "ignore", "pipe", "pipe"]
15532
+ stdio: [
15533
+ deliverViaStdin || rpcBudget ? "pipe" : "ignore",
15534
+ "pipe",
15535
+ "pipe"
15536
+ ]
14916
15537
  });
14917
15538
  } catch (error) {
14918
15539
  cleanupSystemPromptFile();
@@ -14935,15 +15556,43 @@ class PiSubagentRunner {
14935
15556
  return;
14936
15557
  }
14937
15558
  emitProgress({ type: "spawned", argv: args, pid: child.pid });
14938
- if (deliverViaStdin && child.stdin) {
15559
+ if (rpcBudget && child.stdin) {
15560
+ child.stdin.on("error", (error) => {
15561
+ terminateChild(child);
15562
+ settle({
15563
+ ok: false,
15564
+ reason: budget?.snapshot().finalizeFired ? "token_budget" : "spawn_failed",
15565
+ error: `Pi RPC input failed: ${error.message}`,
15566
+ durationMs: Date.now() - startTime
15567
+ });
15568
+ });
15569
+ child.stdin.write(`${JSON.stringify({
15570
+ type: "prompt",
15571
+ message: options.userMessage,
15572
+ id: "dreamer-initial"
15573
+ })}
15574
+ `);
15575
+ } else if (deliverViaStdin && child.stdin) {
14939
15576
  child.stdin.on("error", () => {});
14940
15577
  try {
14941
15578
  child.stdin.end(options.userMessage, "utf8");
14942
15579
  } catch {}
14943
15580
  }
14944
15581
  let stderr = "";
15582
+ let stderrLine = "";
14945
15583
  child.stderr?.on("data", (chunk) => {
14946
15584
  const text = chunk.toString("utf8");
15585
+ stderrLine += text;
15586
+ const lines = stderrLine.split(`
15587
+ `);
15588
+ stderrLine = (lines.pop() ?? "").slice(-4096);
15589
+ for (const line of lines) {
15590
+ if (settled || !line.startsWith('{"type":"mc_system_prompt"'))
15591
+ continue;
15592
+ const parsed = parsePiEventLine(line);
15593
+ if (parsed.ok)
15594
+ telemetry.observe(parsed.event);
15595
+ }
14947
15596
  stderr += text;
14948
15597
  if (stderr.length > 16000) {
14949
15598
  stderr = `…[truncated]${stderr.slice(-16000)}`;
@@ -14979,7 +15628,7 @@ class PiSubagentRunner {
14979
15628
  const accumulatedMessages = [];
14980
15629
  accountingMessages = accumulatedMessages;
14981
15630
  rl.on("line", (line) => {
14982
- if (line.length === 0)
15631
+ if (settled || line.length === 0)
14983
15632
  return;
14984
15633
  const parsed = parsePiEventLine(line);
14985
15634
  if (!parsed.ok) {
@@ -14992,6 +15641,95 @@ class PiSubagentRunner {
14992
15641
  if (typeof event !== "object" || event === null)
14993
15642
  return;
14994
15643
  const e = event;
15644
+ if (rpcBudget && e.type === "response" && event.success === false) {
15645
+ const reply = event;
15646
+ if (reply.command === "prompt" || reply.command === "steer" && !sawAgentEnd) {
15647
+ terminateChild(child);
15648
+ settle({
15649
+ ok: false,
15650
+ reason: reply.command === "steer" ? "token_budget" : "model_failed",
15651
+ error: reply.error ?? `Pi RPC ${reply.command} failed`,
15652
+ durationMs: Date.now() - startTime
15653
+ });
15654
+ return;
15655
+ }
15656
+ }
15657
+ telemetry.observe(event);
15658
+ if (budget && e.type === "message_end") {
15659
+ const message = e.message;
15660
+ if (message?.role === "assistant") {
15661
+ const usage = message.usage;
15662
+ const finished = message.stopReason === "stop" && !(Array.isArray(message.content) && message.content.some((part) => typeof part === "object" && part !== null && part.type === "toolCall"));
15663
+ const decision = budget.charge(usage?.input ?? 0, usage?.cacheRead ?? 0, usage?.cacheWrite ?? 0, finished, rpcBudget);
15664
+ if (decision === "stop" || decision === "finalize" && !rpcBudget) {
15665
+ terminateChild(child);
15666
+ settle({
15667
+ ok: false,
15668
+ reason: "token_budget",
15669
+ error: "MC-D11: Pi dreamer exceeded its prompt-token budget",
15670
+ durationMs: Date.now() - startTime,
15671
+ meta: { tokenBudget: budget.snapshot() }
15672
+ });
15673
+ return;
15674
+ }
15675
+ if (decision === "finalize" && rpcBudget) {
15676
+ try {
15677
+ if (!budgetGuardFile)
15678
+ throw new Error("missing budget guard file");
15679
+ writeFileSync5(budgetGuardFile, "finalize", "utf8");
15680
+ } catch {
15681
+ terminateChild(child);
15682
+ settle({
15683
+ ok: false,
15684
+ reason: "token_budget",
15685
+ error: "MC-D11: Pi could not activate its tool guard",
15686
+ durationMs: Date.now() - startTime
15687
+ });
15688
+ return;
15689
+ }
15690
+ child.stdin?.write(`${JSON.stringify({
15691
+ type: "steer",
15692
+ message: TOKEN_BUDGET_FINALIZE_MESSAGE,
15693
+ id: "dreamer-finalize"
15694
+ })}
15695
+ `);
15696
+ }
15697
+ }
15698
+ }
15699
+ const resultParts = event.result?.content;
15700
+ if (budget?.snapshot().finalizeFired && e.type === "tool_execution_end" && resultParts?.some((part) => part.text === TOKEN_BUDGET_TOOL_REFUSAL)) {
15701
+ const refusal = budget.refuseTool();
15702
+ if (refusal?.hardStopped) {
15703
+ terminateChild(child);
15704
+ settle({
15705
+ ok: false,
15706
+ reason: "token_budget",
15707
+ error: "MC-D11: Pi dreamer made two calls after finalize",
15708
+ durationMs: Date.now() - startTime,
15709
+ meta: { tokenBudget: budget.snapshot() }
15710
+ });
15711
+ return;
15712
+ }
15713
+ }
15714
+ if (cap !== undefined && telemetry.steps > cap) {
15715
+ if (e.type === "message_end")
15716
+ accumulatedMessages.push(e.message);
15717
+ if (e.type === "agent_end" && Array.isArray(e.messages))
15718
+ accountingMessages = e.messages;
15719
+ if (timeoutHandle)
15720
+ clearTimeout(timeoutHandle);
15721
+ if (drainTimerHandle)
15722
+ clearTimeout(drainTimerHandle);
15723
+ terminateChild(child);
15724
+ settle({
15725
+ ok: false,
15726
+ reason: "step_limit",
15727
+ error: `MC-D10: Pi subagent ${options.agent} exceeded its ${cap}-step limit`,
15728
+ durationMs: Date.now() - startTime,
15729
+ meta: { cap, steps: telemetry.steps }
15730
+ });
15731
+ return;
15732
+ }
14995
15733
  const isFirstEvent = eventCount === 0;
14996
15734
  eventCount += 1;
14997
15735
  lastEventTimestamp = Date.now();
@@ -15305,9 +16043,9 @@ function buildArgs(options, opts) {
15305
16043
  const targetHarness = opts?.targetHarness ?? resolvePiHarnessKind();
15306
16044
  const ompTarget = targetHarness === "omp";
15307
16045
  const args = [
15308
- "--print",
16046
+ ...opts?.rpc ? [] : ["--print"],
15309
16047
  "--mode",
15310
- "json",
16048
+ opts?.rpc ? "rpc" : "json",
15311
16049
  "--no-session",
15312
16050
  "--no-skills",
15313
16051
  ...ompTarget ? ["--no-rules"] : ["--no-prompt-templates", "--no-context-files"]
@@ -15321,7 +16059,7 @@ function buildArgs(options, opts) {
15321
16059
  }
15322
16060
  }
15323
16061
  const subagentEntryPath = opts?.subagentEntryPath ?? SUBAGENT_ENTRY_PATH;
15324
- const shouldLoadSubagentExtension = subagentEntryPath && (SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
16062
+ const shouldLoadSubagentExtension = subagentEntryPath && (opts?.rpc || SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
15325
16063
  if (shouldLoadSubagentExtension) {
15326
16064
  args.push("--extension", subagentEntryPath);
15327
16065
  if (DREAMER_ACTION_AGENTS.has(options.agent)) {
@@ -15329,7 +16067,7 @@ function buildArgs(options, opts) {
15329
16067
  }
15330
16068
  }
15331
16069
  const historianCalibrationEntryPath = opts?.historianCalibrationEntryPath === undefined ? HISTORIAN_CALIBRATION_ENTRY_PATH : opts.historianCalibrationEntryPath;
15332
- if (HISTORIAN_AGENTS.has(options.agent) && historianCalibrationEntryPath) {
16070
+ if (subagentStepCap(options.agent) !== undefined && historianCalibrationEntryPath) {
15333
16071
  args.push("--extension", historianCalibrationEntryPath);
15334
16072
  }
15335
16073
  const strictTools = STRICT_TOOL_ALLOWLIST.get(options.agent);
@@ -15469,13 +16207,101 @@ function terminateChild(child) {
15469
16207
  }
15470
16208
  }
15471
16209
 
16210
+ // src/dreamer/primer-raw-provider-pi.ts
16211
+ import { join as join8 } from "node:path";
16212
+
16213
+ // src/dreamer/bounded-session-reader.ts
16214
+ import { closeSync, opendirSync, openSync, readSync } from "node:fs";
16215
+ import { join as join6 } from "node:path";
16216
+ var MAX_ENTRY_BYTES = 1024 * 1024;
16217
+ function* lines(path) {
16218
+ const fd = openSync(path, "r");
16219
+ const buffer = Buffer.alloc(64 * 1024);
16220
+ let pending = Buffer.alloc(0);
16221
+ try {
16222
+ while (true) {
16223
+ const bytes = readSync(fd, buffer, 0, buffer.length, null);
16224
+ if (bytes === 0)
16225
+ break;
16226
+ let start = 0;
16227
+ for (let i = 0;i < bytes; i++) {
16228
+ if (buffer[i] !== 10)
16229
+ continue;
16230
+ if (pending.length + i - start > MAX_ENTRY_BYTES)
16231
+ throw new Error("Pi session entry exceeds bounded reader capacity");
16232
+ yield Buffer.concat([pending, buffer.subarray(start, i)]).toString("utf8");
16233
+ pending = Buffer.alloc(0);
16234
+ start = i + 1;
16235
+ }
16236
+ if (pending.length + bytes - start > MAX_ENTRY_BYTES)
16237
+ throw new Error("Pi session entry exceeds bounded reader capacity");
16238
+ pending = Buffer.concat([pending, buffer.subarray(start, bytes)]);
16239
+ }
16240
+ if (pending.length)
16241
+ yield pending.toString("utf8");
16242
+ } finally {
16243
+ closeSync(fd);
16244
+ }
16245
+ }
16246
+ function record(value) {
16247
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
16248
+ }
16249
+ function* sessionFiles(directory, nested) {
16250
+ let dir;
16251
+ try {
16252
+ dir = opendirSync(directory);
16253
+ } catch {
16254
+ return;
16255
+ }
16256
+ try {
16257
+ while (true) {
16258
+ const entry = dir.readSync();
16259
+ if (!entry)
16260
+ break;
16261
+ const path = join6(directory, entry.name);
16262
+ if (entry.isFile() && entry.name.endsWith(".jsonl"))
16263
+ yield path;
16264
+ else if (nested && entry.isDirectory())
16265
+ yield* sessionFiles(path, false);
16266
+ }
16267
+ } finally {
16268
+ dir.closeSync();
16269
+ }
16270
+ }
16271
+ function findSession(directory, nested, sessionId) {
16272
+ for (const header of sessionHeaders(directory, nested)) {
16273
+ if (header.id === sessionId)
16274
+ return header.path;
16275
+ }
16276
+ return null;
16277
+ }
16278
+ function* sessionHeaders(directory, nested) {
16279
+ for (const path of sessionFiles(directory, nested)) {
16280
+ try {
16281
+ for (const line of lines(path)) {
16282
+ const header = record(JSON.parse(line));
16283
+ if (header.type === "session")
16284
+ yield { ...header, path };
16285
+ break;
16286
+ }
16287
+ } catch {}
16288
+ }
16289
+ }
16290
+ function* sessionEntries(path) {
16291
+ for (const line of lines(path)) {
16292
+ try {
16293
+ yield JSON.parse(line);
16294
+ } catch {}
16295
+ }
16296
+ }
16297
+
15472
16298
  // src/dreamer/pi-session-api.ts
15473
- import { existsSync as existsSync8, readFileSync as readFileSync6, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
16299
+ import { existsSync as existsSync8, readFileSync as readFileSync7, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
15474
16300
  import {
15475
16301
  basename as basename2,
15476
16302
  dirname as dirname2,
15477
16303
  extname,
15478
- join as join6,
16304
+ join as join7,
15479
16305
  relative,
15480
16306
  resolve,
15481
16307
  sep as sep2
@@ -15499,7 +16325,7 @@ function isScriptEntry(filePath) {
15499
16325
  }
15500
16326
  function readManifest(pkgJsonPath) {
15501
16327
  try {
15502
- const pkg = JSON.parse(readFileSync6(pkgJsonPath, "utf8"));
16328
+ const pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
15503
16329
  return pkg && typeof pkg === "object" ? pkg : null;
15504
16330
  } catch {
15505
16331
  return null;
@@ -15525,11 +16351,11 @@ function resolveRunningEntry() {
15525
16351
  function findPackageRoot(startDir) {
15526
16352
  let dir = startDir;
15527
16353
  while (dir !== dirname2(dir)) {
15528
- const pkg = readManifest(join6(dir, "package.json"));
16354
+ const pkg = readManifest(join7(dir, "package.json"));
15529
16355
  if (isCodingAgentPackageName(pkg?.name)) {
15530
16356
  if (basename2(dir) === "dist") {
15531
16357
  const parentDir = dirname2(dir);
15532
- const parentPkg = readManifest(join6(parentDir, "package.json"));
16358
+ const parentPkg = readManifest(join7(parentDir, "package.json"));
15533
16359
  if (parentPkg?.name === pkg.name) {
15534
16360
  return { dir: parentDir, pkg: parentPkg };
15535
16361
  }
@@ -15588,7 +16414,7 @@ function toSourceEntry(entryPath, pkgDir) {
15588
16414
  if (srcRel === rel) {
15589
16415
  return null;
15590
16416
  }
15591
- return join6(pkgDir, srcRel);
16417
+ return join7(pkgDir, srcRel);
15592
16418
  }
15593
16419
  var defaultLoaders = [
15594
16420
  {
@@ -15663,7 +16489,7 @@ async function loadDefaultPiSessionApi(loaders) {
15663
16489
  throw new Error("Pi session APIs unavailable: expected SessionManager.listAll on pi-coding-agent");
15664
16490
  }
15665
16491
  const loadEntriesFromFile = mod.loadEntriesFromFile ?? ((filePath) => {
15666
- const content = readFileSync6(filePath, "utf8");
16492
+ const content = readFileSync7(filePath, "utf8");
15667
16493
  return mod.parseSessionEntries?.(content) ?? [];
15668
16494
  });
15669
16495
  return {
@@ -15673,35 +16499,108 @@ async function loadDefaultPiSessionApi(loaders) {
15673
16499
  }
15674
16500
 
15675
16501
  // src/dreamer/primer-raw-provider-pi.ts
15676
- function createPiPrimerRawProviderFactory(deps = {}) {
15677
- let resolved = null;
15678
- const resolveDeps = async () => {
15679
- if (deps.listSessions && deps.loadEntriesFromFile) {
15680
- return {
15681
- listSessions: deps.listSessions,
15682
- loadEntriesFromFile: deps.loadEntriesFromFile
15683
- };
16502
+ var MAX_PARTS = 256;
16503
+ var TOOL_KEYS = [
16504
+ "description",
16505
+ "filePath",
16506
+ "path",
16507
+ "pattern",
16508
+ "query",
16509
+ "symbol",
16510
+ "module",
16511
+ "action"
16512
+ ];
16513
+ function record2(value) {
16514
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
16515
+ }
16516
+ function short(value, max = 512) {
16517
+ if (typeof value !== "string")
16518
+ return;
16519
+ return value.length <= max || estimateTokens(value) <= PRIMER_SEED_CAP_TOKENS ? value : value.slice(0, max);
16520
+ }
16521
+ function projectEntry(value) {
16522
+ const entry = record2(value);
16523
+ if (entry.type !== "message")
16524
+ return { type: entry.type };
16525
+ const message = record2(entry.message);
16526
+ let content = [];
16527
+ if (message.role === "user" && typeof message.content === "string") {
16528
+ content = short(message.content, RAW_SUMMARY_TEXT_MAX_CHARS);
16529
+ } else if (message.role !== "toolResult" && Array.isArray(message.content)) {
16530
+ if (message.content.length > MAX_PARTS)
16531
+ throw new Error("Pi primer entry has too many parts");
16532
+ content = message.content.flatMap((part) => {
16533
+ const p = record2(part);
16534
+ if (p.type === "text")
16535
+ return [
16536
+ { type: "text", text: short(p.text, RAW_SUMMARY_TEXT_MAX_CHARS) }
16537
+ ];
16538
+ if (p.type !== "toolCall")
16539
+ return [];
16540
+ const args = record2(p.arguments);
16541
+ return [
16542
+ {
16543
+ type: "toolCall",
16544
+ id: short(p.id),
16545
+ name: short(p.name),
16546
+ arguments: Object.fromEntries(TOOL_KEYS.map((key) => [key, short(args[key])]))
16547
+ }
16548
+ ];
16549
+ });
16550
+ }
16551
+ return {
16552
+ type: entry.type,
16553
+ id: entry.id,
16554
+ timestamp: entry.timestamp,
16555
+ message: {
16556
+ role: message.role,
16557
+ content,
16558
+ toolCallId: short(message.toolCallId),
16559
+ toolName: short(message.toolName)
15684
16560
  }
15685
- resolved ??= loadDefaultPiSessionApi();
15686
- return resolved;
15687
16561
  };
16562
+ }
16563
+ function* summaryEntries(path) {
16564
+ let pendingResults = 0;
16565
+ for (const line of lines(path)) {
16566
+ let parsed;
16567
+ try {
16568
+ parsed = JSON.parse(line);
16569
+ } catch {
16570
+ continue;
16571
+ }
16572
+ const entry = projectEntry(parsed);
16573
+ const e = record2(entry);
16574
+ if (e.type === "message") {
16575
+ const m = record2(e.message);
16576
+ if (m.role === "toolResult" && m.toolCallId) {
16577
+ if (++pendingResults > MAX_PARTS)
16578
+ throw new Error("Pi primer has too many consecutive tool results");
16579
+ } else if (m.role === "user" || m.role === "assistant")
16580
+ pendingResults = 0;
16581
+ }
16582
+ yield entry;
16583
+ }
16584
+ }
16585
+ function createPiPrimerRawProviderFactory(deps = {}) {
15688
16586
  return async (sessionId) => {
15689
16587
  try {
15690
- const { listSessions, loadEntriesFromFile } = await resolveDeps();
15691
- const sessions = await listSessions(deps.sessionDir);
15692
- const match = sessions.find((s) => s && typeof s === "object" && s.id === sessionId && typeof s.path === "string");
15693
- if (!match || typeof match.path !== "string")
15694
- return null;
15695
- const entries = await loadEntriesFromFile(match.path);
15696
- const messages = convertEntriesToRawMessages(entries);
15697
- if (messages.length === 0)
16588
+ let directory = deps.sessionDir;
16589
+ if (!directory) {
16590
+ const mod = await resolvePiCodingAgentModule();
16591
+ if (!mod.getAgentDir)
16592
+ return null;
16593
+ directory = join8(mod.getAgentDir(), "sessions");
16594
+ }
16595
+ const path = findSession(directory, !deps.sessionDir, sessionId);
16596
+ if (!path)
15698
16597
  return null;
15699
16598
  return {
15700
16599
  readMessages() {
15701
- return messages;
16600
+ throw new Error("Pi primer history requires bounded pages");
15702
16601
  },
15703
- getMessageCount() {
15704
- return messages.length;
16602
+ readMessagePage(after, limit, watermark) {
16603
+ return convertEntriesToRawMessagePage(summaryEntries(path), after, Math.min(50, limit), watermark);
15705
16604
  }
15706
16605
  };
15707
16606
  } catch {
@@ -15711,17 +16610,22 @@ function createPiPrimerRawProviderFactory(deps = {}) {
15711
16610
  }
15712
16611
 
15713
16612
  // src/dreamer/retrospective-raw-provider-pi.ts
15714
- import { resolve as resolve2 } from "node:path";
16613
+ import { join as join9, resolve as resolve2 } from "node:path";
15715
16614
  class PiRetrospectiveRawProvider {
15716
16615
  deps;
15717
16616
  sessionPathById = new Map;
15718
- resolvedDefaultDeps = null;
15719
16617
  constructor(deps) {
15720
16618
  this.deps = deps;
15721
16619
  }
15722
16620
  async listProjectSessions(_projectIdentity) {
15723
- const deps = await this.resolveDeps();
15724
- const sessions = await deps.listSessions(this.deps.sessionDir);
16621
+ let directory = this.deps.sessionDir;
16622
+ if (!directory && !this.deps.listSessions) {
16623
+ const mod = await resolvePiCodingAgentModule();
16624
+ if (!mod.getAgentDir)
16625
+ return [];
16626
+ directory = join9(mod.getAgentDir(), "sessions");
16627
+ }
16628
+ const sessions = this.deps.listSessions ? await this.deps.listSessions(directory) : directory ? sessionHeaders(directory, !this.deps.sessionDir) : [];
15725
16629
  const projectCwd = resolve2(this.deps.projectCwd);
15726
16630
  const result = [];
15727
16631
  this.sessionPathById.clear();
@@ -15733,61 +16637,63 @@ class PiRetrospectiveRawProvider {
15733
16637
  continue;
15734
16638
  if (typeof info.cwd !== "string" || resolve2(info.cwd) !== projectCwd)
15735
16639
  continue;
16640
+ const activity = this.deps.contextDb ? readSessionActivity(this.deps.contextDb, info.id) : typeof info.modified === "number" ? info.modified : undefined;
16641
+ if (this.deps.contextDb && activity === undefined)
16642
+ continue;
15736
16643
  this.sessionPathById.set(info.id, info.path);
15737
16644
  result.push({
15738
16645
  sessionId: info.id,
15739
16646
  path: info.path,
15740
- updatedAt: typeof info.modified === "number" ? info.modified : undefined
16647
+ updatedAt: activity
15741
16648
  });
15742
16649
  }
15743
16650
  return result.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0));
15744
16651
  }
15745
16652
  async readUserMessagesSince(sessionId, sinceMs, capPerSession) {
15746
- const all = await this.loadUserEntries(sessionId);
15747
16653
  const limit = Math.max(1, Math.floor(capPerSession));
15748
- const eligible = all.filter((entry) => entry.ts > sinceMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal);
16654
+ const messages = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, limit + 1, false);
15749
16655
  return {
15750
- messages: eligible.slice(0, limit),
15751
- truncated: eligible.length > limit
16656
+ messages: messages.slice(0, limit),
16657
+ truncated: messages.length > limit
15752
16658
  };
15753
16659
  }
15754
16660
  async readOldestMessageTimesSince(sessionIds, sinceMs) {
15755
16661
  const out = new Map;
15756
16662
  for (const sessionId of sessionIds) {
15757
- const oldest = (await this.loadUserEntries(sessionId)).filter((message) => message.ts > sinceMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal)[0];
16663
+ const [oldest] = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, 1, false);
15758
16664
  if (oldest)
15759
16665
  out.set(sessionId, oldest.ts);
15760
16666
  }
15761
16667
  return out;
15762
16668
  }
15763
16669
  async readUserMessagesBefore(sessionId, beforeMs, count) {
15764
- const all = await this.loadUserEntries(sessionId);
15765
- 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)));
16670
+ return this.selectUserEntries(sessionId, (ts) => ts <= beforeMs, Math.max(1, Math.floor(count)), true);
15766
16671
  }
15767
- async loadUserEntries(sessionId) {
16672
+ async selectUserEntries(sessionId, eligible, limit, newest) {
15768
16673
  const filePath = this.sessionPathById.get(sessionId);
15769
16674
  if (!filePath)
15770
16675
  return [];
15771
- const deps = await this.resolveDeps();
15772
- let entries;
16676
+ const kept = [];
15773
16677
  try {
15774
- entries = await deps.loadEntriesFromFile(filePath);
16678
+ const entries = this.deps.loadEntriesFromFile ? await this.deps.loadEntriesFromFile(filePath) : sessionEntries(filePath);
16679
+ let ordinal = 0;
16680
+ for (const entry of entries) {
16681
+ const row = normalizePiUserEntry(entry, sessionId, ++ordinal);
16682
+ if (!row || !eligible(row.ts))
16683
+ continue;
16684
+ kept.push(row);
16685
+ kept.sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal);
16686
+ if (kept.length > limit) {
16687
+ if (newest)
16688
+ kept.shift();
16689
+ else
16690
+ kept.pop();
16691
+ }
16692
+ }
15775
16693
  } catch {
15776
16694
  return [];
15777
16695
  }
15778
- if (!Array.isArray(entries))
15779
- return [];
15780
- return entries.map((entry, index) => normalizePiUserEntry(entry, sessionId, index + 1)).filter((entry) => entry !== null);
15781
- }
15782
- async resolveDeps() {
15783
- if (this.deps.listSessions && this.deps.loadEntriesFromFile) {
15784
- return {
15785
- listSessions: this.deps.listSessions,
15786
- loadEntriesFromFile: this.deps.loadEntriesFromFile
15787
- };
15788
- }
15789
- this.resolvedDefaultDeps ??= loadDefaultPiSessionApi();
15790
- return this.resolvedDefaultDeps;
16696
+ return kept;
15791
16697
  }
15792
16698
  }
15793
16699
  function normalizePiUserEntry(entry, sessionId, ordinal) {
@@ -15827,7 +16733,7 @@ function extractPiTextContent(content) {
15827
16733
  // src/dreamer/index.ts
15828
16734
  var warnedUnknownModels = new Set;
15829
16735
  var modelAvailability = new WeakMap;
15830
- function validatePiDreamerModels(tasks, registry) {
16736
+ function validatePiDreamerModels(tasks, registry, harness = "pi") {
15831
16737
  let resolved = modelAvailability.get(registry);
15832
16738
  if (!resolved) {
15833
16739
  resolved = new Map;
@@ -15837,6 +16743,8 @@ function validatePiDreamerModels(tasks, registry) {
15837
16743
  const entries = [task.model, ...task.fallbackModels ?? []].filter((entry) => entry !== undefined);
15838
16744
  const valid = entries.filter((entry) => {
15839
16745
  const model = typeof entry === "string" ? entry : entry.model;
16746
+ if (harness === "omp" && model.startsWith("@"))
16747
+ return true;
15840
16748
  let available = resolved.get(model);
15841
16749
  if (available === undefined) {
15842
16750
  const separator = model.indexOf("/");
@@ -15883,12 +16791,18 @@ var inFlightDreams = (() => {
15883
16791
  globals[PI_DREAMER_IN_FLIGHT] = dreams;
15884
16792
  return dreams;
15885
16793
  })();
16794
+ var dreamAbortControllers = new Map;
16795
+ function abortInFlightDreamers(registrationOwner) {
16796
+ for (const controller of dreamAbortControllers.get(registrationOwner) ?? []) {
16797
+ controller.abort();
16798
+ }
16799
+ }
15886
16800
  var sessionCounter = 0;
15887
16801
  var piSubagentRunnerFactory = () => new PiSubagentRunner;
15888
16802
  var startDreamScheduleTimerFn = startDreamScheduleTimer;
15889
16803
  function registerPiDreamerProject(opts) {
15890
16804
  const directory = resolve3(opts.projectDir);
15891
- if (opts.config.disable === true || directory === parse(directory).root || directory === homedir2()) {
16805
+ if (opts.config.disable === true || !isUsableProjectIdentity(opts.projectIdentity) || directory === parse(directory).root || directory === homedir2()) {
15892
16806
  return;
15893
16807
  }
15894
16808
  const existing = registeredProjects2.get(opts.projectIdentity);
@@ -15911,7 +16825,7 @@ function registerPiDreamerProject(opts) {
15911
16825
  const client = createPiDreamerClient(opts, notifyOwnersOfAdjunctRefresh, () => {
15912
16826
  const current = registeredProjects2.get(opts.projectIdentity);
15913
16827
  return current?.generation === generation && current.owners.get(opts.registrationOwner)?.projectDir === opts.projectDir;
15914
- });
16828
+ }, () => opts.sampleDreamRun ? opts.sampleDreamRun().dreamerConfig?.maxTokens : opts.config.maxTokens, () => opts.sampleDreamRun?.().dreamerConfig ?? opts.config);
15915
16829
  let cleanup;
15916
16830
  let cancelled = false;
15917
16831
  const modelRegistry = opts.modelRegistry;
@@ -15921,7 +16835,7 @@ function registerPiDreamerProject(opts) {
15921
16835
  harness: opts.harness,
15922
16836
  client,
15923
16837
  dreamerConfig: opts.config,
15924
- validateTaskModels: modelRegistry ? (tasks) => validatePiDreamerModels(tasks, modelRegistry) : undefined,
16838
+ validateTaskModels: modelRegistry ? (tasks) => validatePiDreamerModels(tasks, modelRegistry, opts.harness) : undefined,
15925
16839
  sampleDreamRun: opts.sampleDreamRun,
15926
16840
  language: opts.language,
15927
16841
  gitCommitIndexing: opts.gitCommitIndexing,
@@ -15930,7 +16844,10 @@ function registerPiDreamerProject(opts) {
15930
16844
  retinaHandoff: opts.retinaHandoff,
15931
16845
  mural: opts.mural,
15932
16846
  ensureRegistered: ensureProjectRegisteredFromPiDirectory,
15933
- retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({ projectCwd: opts.projectDir }),
16847
+ retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({
16848
+ projectCwd: opts.projectDir,
16849
+ contextDb: opts.db
16850
+ }),
15934
16851
  primerRawProviderFactory: createPiPrimerRawProviderFactory()
15935
16852
  }).then((timerCleanup) => {
15936
16853
  if (cancelled) {
@@ -15949,17 +16866,18 @@ function registerPiDreamerProject(opts) {
15949
16866
  const sampled = manualOpts.sampleDreamRun?.();
15950
16867
  const dreamerConfig = sampled?.dreamerConfig ?? manualOpts.config;
15951
16868
  const mural = sampled?.mural ?? manualOpts.mural;
15952
- const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir);
16869
+ const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir, () => dreamerConfig.maxTokens, () => dreamerConfig);
15953
16870
  const manualRun = runManualDream({
15954
16871
  db: manualOpts.db,
15955
16872
  projectIdentity: manualOpts.projectIdentity,
15956
- tasks: manualOpts.modelRegistry ? validatePiDreamerModels(buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model), manualOpts.modelRegistry) : buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model),
16873
+ tasks: manualOpts.modelRegistry ? validatePiDreamerModels(buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model), manualOpts.modelRegistry, manualOpts.harness) : buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model),
15957
16874
  executor: createDreamTaskExecutor({
15958
16875
  client: manualClient,
15959
16876
  sessionDirectory: manualOpts.projectDir,
15960
16877
  openOpenCodeDb,
15961
16878
  retrospectiveRawProvider: new PiRetrospectiveRawProvider({
15962
- projectCwd: manualOpts.projectDir
16879
+ projectCwd: manualOpts.projectDir,
16880
+ contextDb: manualOpts.db
15963
16881
  }),
15964
16882
  primerRawProviderFactory: createPiPrimerRawProviderFactory(),
15965
16883
  userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
@@ -16029,8 +16947,9 @@ async function awaitInFlightDreamers(registrationOwner) {
16029
16947
  return;
16030
16948
  await Promise.allSettled(runs);
16031
16949
  }
16032
- function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true) {
16950
+ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true, getMaxOutputTokens = () => opts.config.maxTokens, getDreamerConfig = () => opts.config) {
16033
16951
  const runner = piSubagentRunnerFactory();
16952
+ const budgetStates = new Map;
16034
16953
  const assertRegistrationOwnerActive = () => {
16035
16954
  if (!isRegistrationOwnerActive()) {
16036
16955
  throw new Error(`Pi dreamer registration is no longer active for project ${opts.projectIdentity}`);
@@ -16060,6 +16979,13 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16060
16979
  const systemPrompt = extractSystemPrompt(args);
16061
16980
  const perTaskModel = extractBodyModel(args);
16062
16981
  const requestedAgent = extractBodyAgent(args) ?? "magic-context-dreamer";
16982
+ const controller = new AbortController;
16983
+ let controllers = dreamAbortControllers.get(opts.registrationOwner);
16984
+ if (!controllers) {
16985
+ controllers = new Set;
16986
+ dreamAbortControllers.set(opts.registrationOwner, controllers);
16987
+ }
16988
+ controllers.add(controller);
16063
16989
  const runPromise = runner.run({
16064
16990
  agent: requestedAgent,
16065
16991
  systemPrompt,
@@ -16068,17 +16994,39 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16068
16994
  fallbackModels: undefined,
16069
16995
  timeoutMs: 30 * 60 * 1000,
16070
16996
  cwd: dreamSession.directory,
16071
- signal: args.signal ?? undefined,
16997
+ signal: args.signal ? AbortSignal.any([args.signal, controller.signal]) : controller.signal,
16072
16998
  thinkingLevel: extractBodyVariant(args),
16999
+ maxOutputTokens: getMaxOutputTokens(),
16073
17000
  accountingSessionId: opts.projectIdentity,
16074
17001
  accountingSubagent: "dreamer",
16075
- accountingTask: accountingTaskFromTitle(dreamSession.title)
17002
+ accountingTask: accountingTaskFromTitle(dreamSession.title),
17003
+ tokenBudget: (() => {
17004
+ const task = accountingTaskFromTitle(dreamSession.title);
17005
+ if (!task)
17006
+ return;
17007
+ return buildDreamTaskRuntimeConfigs(getDreamerConfig(), opts.harness).find((entry) => entry.task === task)?.tokenBudget;
17008
+ })()
16076
17009
  });
16077
17010
  inFlightDreams.set(runPromise, opts.registrationOwner);
16078
17011
  try {
16079
17012
  const result = await runPromise;
17013
+ const budget = result.meta?.tokenBudget;
17014
+ const budgetTask = accountingTaskFromTitle(dreamSession.title);
17015
+ if (budgetTask && budget && typeof budget.spent === "number") {
17016
+ const prior = budgetStates.get(budgetTask);
17017
+ budgetStates.set(budgetTask, {
17018
+ spent: (prior?.spent ?? 0) + budget.spent,
17019
+ finalizeFired: (prior?.finalizeFired ?? false) || budget.finalizeFired === true
17020
+ });
17021
+ }
16080
17022
  assertRegistrationOwnerActive();
16081
17023
  if (!result.ok) {
17024
+ if (result.reason === "token_budget") {
17025
+ throw new DreamTokenBudgetExceeded(dreamSession.id, Number(result.meta?.tokenBudget?.spent ?? 0));
17026
+ }
17027
+ if (result.reason === "step_limit") {
17028
+ throw new HiddenAgentStepLimit("pi-dreamer", Number(result.meta?.cap));
17029
+ }
16082
17030
  const error = new Error(`Pi dreamer subagent failed (${result.reason}): ${result.error}`);
16083
17031
  if (result.transient) {
16084
17032
  error.transient = true;
@@ -16095,6 +17043,9 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16095
17043
  onAdjunctsRefreshNeeded?.(opts.projectIdentity);
16096
17044
  } finally {
16097
17045
  inFlightDreams.delete(runPromise);
17046
+ controllers.delete(controller);
17047
+ if (controllers.size === 0)
17048
+ dreamAbortControllers.delete(opts.registrationOwner);
16098
17049
  }
16099
17050
  },
16100
17051
  messages: async (args) => {
@@ -16107,7 +17058,11 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16107
17058
  return {};
16108
17059
  }
16109
17060
  };
16110
- return { session };
17061
+ return {
17062
+ session,
17063
+ readTokenBudget: (task) => budgetStates.get(task),
17064
+ resetDreamTokenBudget: (task) => budgetStates.delete(task)
17065
+ };
16111
17066
  }
16112
17067
  function readDirectory(args) {
16113
17068
  const query = args.query;
@@ -16416,6 +17371,7 @@ Unknown task "${requested}".`,
16416
17371
  try {
16417
17372
  await deps.ensureRegistered?.(ctx);
16418
17373
  const result = await runPiDreamForProject(project.projectIdentity, task, deps.registrationOwner);
17374
+ const backlogAfter = getDreamTaskBacklogs(deps.db, project.projectIdentity, backlogTasks);
16419
17375
  const lines = [];
16420
17376
  if (result.ran.length > 0)
16421
17377
  lines.push(`Ran: ${result.ran.join(", ")}`);
@@ -16431,8 +17387,8 @@ Unknown task "${requested}".`,
16431
17387
  lines.push(`Skipped (no work): ${result.skippedNoWork.join(", ")}`);
16432
17388
  if (result.deferredBusy.length > 0)
16433
17389
  lines.push(`Busy: ${result.deferredBusy.join(", ")} — another dream task holds this domain's lease; retry in a minute`);
16434
- if (Object.keys(result.backlogAfter ?? {}).length > 0) {
16435
- lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(result.backlogAfter));
17390
+ if (Object.keys(backlogAfter).length > 0) {
17391
+ lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(backlogAfter, backlogTasks));
16436
17392
  }
16437
17393
  if (lines.length === 0)
16438
17394
  lines.push("No enabled dream tasks to run.");
@@ -16461,26 +17417,6 @@ Unknown task "${requested}".`,
16461
17417
  });
16462
17418
  }
16463
17419
 
16464
- // ../plugin/src/hooks/magic-context/embed-session-state.ts
16465
- var embedPauseBySession = new Set;
16466
- var embedRunStateBySession = new Map;
16467
- var autoEmbedAttemptedBySession = new Set;
16468
- function getEmbedDrainUiStatus(sessionId, progress) {
16469
- if (embedPauseBySession.has(sessionId)) {
16470
- return { status: "paused" };
16471
- }
16472
- const activeRun = embedRunStateBySession.get(sessionId);
16473
- if (activeRun && !activeRun.signal.aborted || progress?.kind === "embed" && progress.phase === "recomp") {
16474
- return { status: "running" };
16475
- }
16476
- if (progress?.kind === "embed" && (progress.phase === "failed" || progress.phase === "skipped") && progress.message) {
16477
- if (/provider/i.test(progress.message)) {
16478
- return { status: "stopped", detail: progress.message };
16479
- }
16480
- }
16481
- return { status: "idle" };
16482
- }
16483
-
16484
17420
  // ../plugin/src/hooks/magic-context/format-embed-failure.ts
16485
17421
  function formatEmbedFailureSummary(embedded, remaining, failure, style = "markdown") {
16486
17422
  const historyBlocks = `history block${embedded === 1 ? "" : "s"}`;
@@ -16544,7 +17480,7 @@ function clearPiEmbedSessionState(sessionId) {
16544
17480
  ctrl.abort();
16545
17481
  embedRunStateBySession.delete(sessionId);
16546
17482
  }
16547
- autoEmbedAttemptedBySession.delete(sessionId);
17483
+ invalidateAutoEmbedSession(sessionId);
16548
17484
  }
16549
17485
  async function runEmbedDrain(db, projectIdentity, sessionId, options = {}) {
16550
17486
  const activeCtrl = embedRunStateBySession.get(sessionId);
@@ -16724,28 +17660,36 @@ ${statusText}`,
16724
17660
  });
16725
17661
  }
16726
17662
  function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity) {
17663
+ if (!isUsableProjectIdentity(projectIdentity))
17664
+ return;
17665
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
17666
+ const embedIdentity = snapshot ? JSON.stringify([
17667
+ snapshot.providerIdentity,
17668
+ snapshot.chunkModelId,
17669
+ snapshot.runtimeFingerprint
17670
+ ]) : "off";
17671
+ if (autoEmbedIdentityBySession.get(sessionId) !== embedIdentity)
17672
+ invalidateAutoEmbedSession(sessionId);
16727
17673
  if (autoEmbedAttemptedBySession.has(sessionId))
16728
17674
  return;
16729
17675
  if (embedPauseBySession.has(sessionId))
16730
17676
  return;
16731
17677
  autoEmbedAttemptedBySession.add(sessionId);
17678
+ autoEmbedIdentityBySession.set(sessionId, embedIdentity);
16732
17679
  (async () => {
16733
- let drainReachedTerminal = false;
16734
17680
  try {
16735
17681
  await new Promise((resolve) => setTimeout(resolve, 0));
16736
17682
  await ensureProjectRegisteredFromPiDirectory(projectDir, deps.db);
16737
- const coverage = getEmbeddingCoverageStatus(deps.db, projectIdentity, sessionId);
16738
- if (!coverage.enabled)
17683
+ const coverage = await getAutoEmbeddingSessionCoverage(deps.db, projectIdentity, sessionId);
17684
+ if (!coverage.enabled) {
17685
+ invalidateAutoEmbedSession(sessionId);
16739
17686
  return;
16740
- const remaining = coverage.session.total - coverage.session.embedded;
17687
+ }
17688
+ const remaining = coverage.total - coverage.embedded;
16741
17689
  if (remaining <= 0)
16742
17690
  return;
16743
17691
  await runEmbedDrain(deps.db, projectIdentity, sessionId);
16744
- drainReachedTerminal = true;
16745
- } catch {} finally {
16746
- if (!drainReachedTerminal)
16747
- autoEmbedAttemptedBySession.delete(sessionId);
16748
- }
17692
+ } catch {}
16749
17693
  })();
16750
17694
  }
16751
17695
 
@@ -16872,6 +17816,135 @@ function commitPiCompactionModeRecord(db, sessionId, record) {
16872
17816
  // src/context-handler.ts
16873
17817
  import * as crypto3 from "node:crypto";
16874
17818
 
17819
+ // ../plugin/src/hooks/magic-context/event-resolvers.ts
17820
+ var DEFAULT_CONTEXT_LIMIT = 200000;
17821
+ var clampWarnSeen = new Set;
17822
+ function isFinitePositive(v) {
17823
+ return typeof v === "number" && Number.isFinite(v) && v > 0;
17824
+ }
17825
+ function* modelKeyLookupOrder(modelKey) {
17826
+ const slash = modelKey.indexOf("/");
17827
+ const providerRefs = slash >= 0 ? modelRefLookupOrder(modelKey) : [];
17828
+ let modelId = slash >= 0 ? modelKey.slice(slash + 1) : modelKey;
17829
+ while (modelId.length > 0) {
17830
+ for (const providerRef of providerRefs) {
17831
+ const providerSlash = providerRef.indexOf("/");
17832
+ yield `${providerRef.slice(0, providerSlash)}/${modelId}`;
17833
+ }
17834
+ yield modelId;
17835
+ const lastDash = modelId.lastIndexOf("-");
17836
+ if (lastDash <= 0)
17837
+ break;
17838
+ modelId = modelId.slice(0, lastDash);
17839
+ }
17840
+ }
17841
+ function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
17842
+ if (options?.tokensConfig && isFinitePositive(options.contextLimit)) {
17843
+ const contextLimit = options.contextLimit;
17844
+ const tokenMatch = resolveTokensMatchWithKey(options.tokensConfig, modelKey);
17845
+ if (tokenMatch && isFinitePositive(tokenMatch.value)) {
17846
+ const cap = contextLimit * (MAX_EXECUTE_THRESHOLD / 100);
17847
+ const effectiveTokens = Math.min(tokenMatch.value, cap);
17848
+ if (effectiveTokens < tokenMatch.value) {
17849
+ const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
17850
+ if (!clampWarnSeen.has(dedupeKey)) {
17851
+ clampWarnSeen.add(dedupeKey);
17852
+ const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (${MAX_EXECUTE_THRESHOLD}% of ${contextLimit}) for ${modelKey ?? "default"}`;
17853
+ if (options.sessionId) {
17854
+ sessionLog(options.sessionId, `WARN: ${msg}`);
17855
+ } else {
17856
+ log(`[magic-context] WARN: ${msg}`);
17857
+ }
17858
+ }
17859
+ }
17860
+ const percentage = effectiveTokens / contextLimit * 100;
17861
+ const detail = {
17862
+ percentage: Math.min(percentage, MAX_EXECUTE_THRESHOLD),
17863
+ mode: "tokens",
17864
+ absoluteTokens: Math.floor(effectiveTokens),
17865
+ matchedKey: tokenMatch.matchedKey
17866
+ };
17867
+ if (effectiveTokens < tokenMatch.value) {
17868
+ detail.clamped = true;
17869
+ detail.configuredValue = tokenMatch.value;
17870
+ }
17871
+ return detail;
17872
+ }
17873
+ }
17874
+ let resolved;
17875
+ let matchedKey;
17876
+ if (typeof config === "number") {
17877
+ resolved = config;
17878
+ } else if (modelKey) {
17879
+ let matched;
17880
+ for (const candidate of modelKeyLookupOrder(modelKey)) {
17881
+ if (typeof config[candidate] === "number") {
17882
+ matched = config[candidate];
17883
+ matchedKey = candidate;
17884
+ break;
17885
+ }
17886
+ }
17887
+ if (matched === undefined && typeof config.default === "number") {
17888
+ resolved = config.default;
17889
+ matchedKey = "default";
17890
+ } else {
17891
+ resolved = matched ?? fallback;
17892
+ }
17893
+ } else if (typeof config.default === "number") {
17894
+ resolved = config.default;
17895
+ matchedKey = "default";
17896
+ } else {
17897
+ resolved = fallback;
17898
+ }
17899
+ if (!Number.isFinite(resolved) || resolved < 0) {
17900
+ resolved = fallback;
17901
+ }
17902
+ const cappedPercentage = Math.min(resolved, MAX_EXECUTE_THRESHOLD);
17903
+ const percentageClamped = cappedPercentage < resolved;
17904
+ if (percentageClamped) {
17905
+ const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
17906
+ if (!clampWarnSeen.has(dedupeKey)) {
17907
+ clampWarnSeen.add(dedupeKey);
17908
+ 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)`;
17909
+ if (options?.sessionId) {
17910
+ sessionLog(options.sessionId, `WARN: ${msg}`);
17911
+ } else {
17912
+ log(`[magic-context] WARN: ${msg}`);
17913
+ }
17914
+ }
17915
+ }
17916
+ const detail = {
17917
+ percentage: cappedPercentage,
17918
+ mode: "percentage",
17919
+ matchedKey
17920
+ };
17921
+ if (percentageClamped) {
17922
+ detail.clamped = true;
17923
+ detail.configuredValue = resolved;
17924
+ }
17925
+ return detail;
17926
+ }
17927
+ function resolveExecuteThreshold(config, modelKey, fallback, options) {
17928
+ return resolveExecuteThresholdDetail(config, modelKey, fallback, options).percentage;
17929
+ }
17930
+ function resolveTokensMatchWithKey(tokensConfig, modelKey) {
17931
+ if (!tokensConfig) {
17932
+ return;
17933
+ }
17934
+ if (modelKey) {
17935
+ for (const candidate of modelKeyLookupOrder(modelKey)) {
17936
+ const value = tokensConfig[candidate];
17937
+ if (typeof value === "number") {
17938
+ return { value, matchedKey: candidate };
17939
+ }
17940
+ }
17941
+ }
17942
+ if (typeof tokensConfig.default === "number") {
17943
+ return { value: tokensConfig.default, matchedKey: "default" };
17944
+ }
17945
+ return;
17946
+ }
17947
+
16875
17948
  // ../plugin/src/features/magic-context/scheduler.ts
16876
17949
  var TTL_PATTERN = /^(\d+)([smh])$/;
16877
17950
  var NUMERIC_PATTERN = /^\d+$/;
@@ -20461,6 +21534,8 @@ async function runAutoSearchHintForPi(args) {
20461
21534
  }
20462
21535
  if (found.index !== messages.length - 1)
20463
21536
  return messages;
21537
+ if (!isUsableProjectIdentity(options.projectPath))
21538
+ return messages;
20464
21539
  await args.ensureProjectRegistered?.();
20465
21540
  const writeNoHintAndReconcile = (reason) => {
20466
21541
  const outcome = appendAutoSearchHintDecision(db, sessionId, {
@@ -25285,9 +26360,7 @@ ${decayed}
25285
26360
  if (memoryBlock)
25286
26361
  sections.push(memoryBlock);
25287
26362
  if (mural?.enabled && mural.supportsVision && mural.dataUrl) {
25288
- sections.push(`<memory-mural>
25289
- The project memory mural image follows.
25290
- </memory-mural>`);
26363
+ sections.push(MEMORY_MURAL_BLOCK);
25291
26364
  }
25292
26365
  return sections.join(`
25293
26366
 
@@ -25359,7 +26432,7 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), m
25359
26432
  };
25360
26433
  return { docs, markers, compartments, memories, userProfile, workspace };
25361
26434
  });
25362
- return read();
26435
+ return read.deferred();
25363
26436
  }
25364
26437
  function renderFreshM0PiNonPersisted(state, db) {
25365
26438
  const docs = readProjectDocsForPiM0(state);
@@ -26836,19 +27909,22 @@ function validateAnthropicReasoningRuns(messages) {
26836
27909
  index += 1;
26837
27910
  continue;
26838
27911
  }
26839
- let thinkingBlocks = 0;
27912
+ let firstMessageInRun = null;
26840
27913
  let sawOtherContent = false;
26841
27914
  while (index < messages.length && messageRole(messages[index]) === "assistant") {
26842
27915
  for (const part of messageParts(messages[index])) {
26843
- if (partIsAnthropicThinking(part)) {
26844
- thinkingBlocks += 1;
26845
- if (thinkingBlocks > 1 || sawOtherContent)
26846
- return false;
26847
- } else if (partEndsAnthropicAssistantRun(part)) {
26848
- thinkingBlocks = 0;
27916
+ if (partEndsAnthropicAssistantRun(part)) {
27917
+ firstMessageInRun = null;
26849
27918
  sawOtherContent = false;
26850
27919
  } else if (!partIsOpenCodeStepMetadata(part)) {
26851
- sawOtherContent = true;
27920
+ if (firstMessageInRun === null)
27921
+ firstMessageInRun = index;
27922
+ if (partIsAnthropicThinking(part)) {
27923
+ if (sawOtherContent || index !== firstMessageInRun)
27924
+ return false;
27925
+ } else {
27926
+ sawOtherContent = true;
27927
+ }
26852
27928
  }
26853
27929
  }
26854
27930
  index += 1;
@@ -26956,6 +28032,8 @@ function replayLkg(args) {
26956
28032
  dropSlot(args.sessionId, "lkg_seam_invalid");
26957
28033
  return { ok: false, reason: "lkg_seam_invalid" };
26958
28034
  }
28035
+ const replayed = [...prefix, ...entry.pristineTail];
28036
+ args.prepareReplay?.(replayed);
26959
28037
  if (!args.skipSeamValidation) {
26960
28038
  if (!validateLkgSeamBoundary(prefix, entry.pristineTail)) {
26961
28039
  dropSlot(args.sessionId, "lkg_unsafe_seam");
@@ -26966,7 +28044,6 @@ function replayLkg(args) {
26966
28044
  return { ok: false, reason: "lkg_seam_invalid" };
26967
28045
  }
26968
28046
  }
26969
- const replayed = [...prefix, ...entry.pristineTail];
26970
28047
  if (requestedModelKeys.providerKey === "anthropic" && !validateAnthropicReasoningRuns(replayed)) {
26971
28048
  dropSlot(args.sessionId, "lkg_anthropic_reasoning_run_invalid");
26972
28049
  return { ok: false, reason: "lkg_anthropic_reasoning_run_invalid" };
@@ -28431,13 +29508,10 @@ function capturePiServedArray(sessionId, messages, options = {}) {
28431
29508
  process.once("beforeExit", flushPiServedArrayLedger);
28432
29509
 
28433
29510
  // src/strip-placeholders-pi.ts
28434
- var DROPPED_SEGMENT_PATTERN = /^\[dropped(?: §[^§]+§)?\]$/;
28435
- function isDroppedOnlyText(text) {
29511
+ var MARKER_ONLY_PATTERN = /^(?:(?:§\d+§|\[dropped(?: §\d+§)?\]|\[cleared\])\s*)+$/;
29512
+ function isPiMarkerOnlyText(text) {
28436
29513
  const trimmed = text.trim();
28437
- if (trimmed.length === 0)
28438
- return true;
28439
- const segments = trimmed.split(/(?=\[dropped(?: §[^§]+§)?\])/).map((segment) => segment.trim()).filter((segment) => segment.length > 0);
28440
- return segments.length > 0 && segments.every((s) => DROPPED_SEGMENT_PATTERN.test(s));
29514
+ return trimmed.length > 0 && MARKER_ONLY_PATTERN.test(trimmed);
28441
29515
  }
28442
29516
  function messageIsPlaceholderOnly(message) {
28443
29517
  if (!message || typeof message !== "object")
@@ -28446,7 +29520,7 @@ function messageIsPlaceholderOnly(message) {
28446
29520
  if (msg.role !== "assistant")
28447
29521
  return false;
28448
29522
  if (typeof msg.content === "string")
28449
- return isDroppedOnlyText(msg.content);
29523
+ return msg.content.trim().length === 0 || isPiMarkerOnlyText(msg.content);
28450
29524
  if (!Array.isArray(msg.content))
28451
29525
  return false;
28452
29526
  if (msg.content.length === 0)
@@ -28456,12 +29530,11 @@ function messageIsPlaceholderOnly(message) {
28456
29530
  if (!part || typeof part !== "object")
28457
29531
  return false;
28458
29532
  const p = part;
28459
- if (p.type !== "text")
28460
- return false;
28461
- if (typeof p.text !== "string")
29533
+ const text = p.type === "text" ? p.text : p.type === "thinking" ? p.thinking : null;
29534
+ if (typeof text !== "string")
28462
29535
  return false;
28463
29536
  sawVisibleContent = true;
28464
- if (!isDroppedOnlyText(p.text))
29537
+ if (text.trim().length > 0 && !isPiMarkerOnlyText(text))
28465
29538
  return false;
28466
29539
  }
28467
29540
  return sawVisibleContent;
@@ -28601,144 +29674,6 @@ function stripPiProcessedImages(args) {
28601
29674
 
28602
29675
  // src/system-prompt.ts
28603
29676
  import { createHash as createHash13 } from "node:crypto";
28604
-
28605
- // ../plugin/src/agents/magic-context-prompt.ts
28606
- var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
28607
-
28608
- 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.
28609
-
28610
- Because of this:
28611
- - **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.
28612
- - **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.
28613
- - **Finishing a task does not end the session.** Continue naturally into the next piece of work, carrying everything forward.
28614
- - **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.`;
28615
- var PARTNER_FRAME_CLOSER_REDUCE = `
28616
- 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.`;
28617
- var PARTNER_FRAME_CLOSER_NO_REDUCE = `
28618
- 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.`;
28619
- var PARTNER_FRAME_CLOSER_REDUCE_LIGHT = `
28620
- When ctx_reduce is available, use it only as routine housekeeping; never cut task scope or depth because context is large.`;
28621
- var PARTNER_FRAME_CLOSER_NO_REDUCE_LIGHT = `
28622
- When ctx_reduce is unavailable, context is automatic; never prune, heed reduction warnings, or cut task scope or depth because context is large.`;
28623
- 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.`;
28624
- 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.`;
28625
- 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.`;
28626
- 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.`;
28627
- 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)`;
28628
- var TEMPORAL_AWARENESS_OVERRIDE_GUIDANCE = `
28629
- 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.`;
28630
- var BASE_INTRO = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28631
-
28632
- Think of your context as a desk. Every message and every tool output lands on it, and each item arrives with a §N§ tag (§1§, §42§) — the tag is the item's handle.
28633
-
28634
- When an item no longer needs to stay on the desk for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping does not remove anything — the item stays on the desk, fully readable. From time to time, when stamped items have piled up and the desk needs room, Magic Context clears them all in one sweep; you don't pick the moment, you only stamp. Stamp as soon as an item has served its purpose, not at the end of the turn, and do it silently: nobody wants to read "I'll drop these outputs". An output is used once you've taken what you need from it into your reasoning or your answer. Never stamp a user message for what it asks of you; a large paste inside one is fine once you have used it.
28635
-
28636
- Nothing stamped is ever lost. A cleared item goes to the archive — a recent one leaves a \`[dropped §N§]\` placeholder on the desk, an older one leaves nothing — and \`ctx_expand(message=N)\` brings it back whole: text, tool input, tool output. Now and then Magic Context leaves a short reminder on the desk saying how much unstamped material is lying around. A reminder means stamping is overdue: act on it in your next step. It isn't a warning about the desk's size. The desk never gets smaller for it.
28637
-
28638
- ${TOOL_HISTORY_GUIDANCE}
28639
-
28640
- \`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.
28641
-
28642
- ${memoryEnabled ? `${MEMORY_GUIDANCE} ` : ""}${CTX_NOTE_GUIDANCE}${dreamerEnabled ? ` ${SMART_NOTE_GUIDANCE}` : ""}
28643
-
28644
- 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.`;
28645
- var BASE_INTRO_NO_REDUCE = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28646
-
28647
- Think of your context as a desk. Every message and every tool output lands on it.
28648
-
28649
- 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.
28650
-
28651
- ${TOOL_HISTORY_GUIDANCE}
28652
-
28653
- \`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.
28654
-
28655
- ${memoryEnabled ? `${MEMORY_GUIDANCE} ` : ""}${CTX_NOTE_GUIDANCE}${dreamerEnabled ? ` ${SMART_NOTE_GUIDANCE}` : ""}
28656
-
28657
- 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.`;
28658
- 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`;
28659
- 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.`;
28660
- 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.`;
28661
- var SMART_NOTE_GUIDANCE_LIGHT = `; a \`surface_condition\` leaves the note with an outside checker that returns it when the condition holds`;
28662
- var TEMPORAL_AWARENESS_GUIDANCE_LIGHT = `, \`<!-- +Xm -->\` (time since your last reply)`;
28663
- var BASE_INTRO_LIGHT = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28664
-
28665
- Your context is a desk. Every message and tool output lands on it with a §N§ tag as its handle. When an item no longer needs to stay for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping removes nothing — the item stays readable; from time to time, when stamped items have piled up and the desk needs room, Magic Context clears them in one sweep (you only stamp, you don't pick the moment). Stamp as soon as an item has served its purpose, silently; never stamp a user message for what it asks (a used paste inside one is fine). An output is used once you've taken what you need from it into your reasoning or your answer. Nothing is lost: a cleared item goes to the archive (a recent one leaves \`[dropped §N§]\`, an older one nothing) and \`ctx_expand(message=N)\` brings it back whole. Occasional reminders about unstamped material may appear. A reminder means stamping is overdue: act on it in your next step. It isn't a warning about the desk's size. The desk never gets smaller.
28666
-
28667
- ${TOOL_HISTORY_GUIDANCE_LIGHT}
28668
-
28669
- \`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 : ""}.
28670
-
28671
- 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.`;
28672
- var BASE_INTRO_NO_REDUCE_LIGHT = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28673
-
28674
- 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.
28675
-
28676
- ${TOOL_HISTORY_GUIDANCE_LIGHT}
28677
-
28678
- \`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 : ""}.
28679
-
28680
- 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.`;
28681
- 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".
28682
-
28683
- 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.
28684
-
28685
- \`<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.`;
28686
- 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".
28687
-
28688
- 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.
28689
-
28690
- \`<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.`;
28691
- var CAVEMAN_COMPRESSION_WARNING = `
28692
- **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.`;
28693
- function buildMagicContextSection(_agent, _legacyProtectionCount, ctxReduceCallable = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true, preset = "full", primaryOverride) {
28694
- if (subagentMode) {
28695
- const intro = preset === "light" ? SUBAGENT_REDUCE_INTRO_LIGHT() : SUBAGENT_REDUCE_INTRO();
28696
- return `## Magic Context
28697
-
28698
- ${intro}`;
28699
- }
28700
- const temporalOverrideGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_OVERRIDE_GUIDANCE : "";
28701
- const cavemanWarning = cavemanTextCompressionEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
28702
- const languageDirective = buildPrimaryLanguageDirective(language);
28703
- const languageGuidance = languageDirective ? `
28704
-
28705
- ${languageDirective}` : "";
28706
- if (primaryOverride !== undefined) {
28707
- return `${primaryOverride}${temporalOverrideGuidance}${cavemanWarning}${languageGuidance}`;
28708
- }
28709
- if (!ctxReduceCallable) {
28710
- if (preset === "light") {
28711
- return `## Magic Context
28712
-
28713
- ${LONG_TERM_PARTNER_FRAME}
28714
- ${PARTNER_FRAME_CLOSER_NO_REDUCE_LIGHT}
28715
-
28716
- ${BASE_INTRO_NO_REDUCE_LIGHT(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28717
- }
28718
- return `## Magic Context
28719
-
28720
- ${LONG_TERM_PARTNER_FRAME}
28721
- ${PARTNER_FRAME_CLOSER_NO_REDUCE}
28722
-
28723
- ${BASE_INTRO_NO_REDUCE(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28724
- }
28725
- if (preset === "light") {
28726
- return `## Magic Context
28727
-
28728
- ${LONG_TERM_PARTNER_FRAME}
28729
- ${PARTNER_FRAME_CLOSER_REDUCE_LIGHT}
28730
-
28731
- ${BASE_INTRO_LIGHT(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28732
- }
28733
- return `## Magic Context
28734
-
28735
- ${LONG_TERM_PARTNER_FRAME}
28736
- ${PARTNER_FRAME_CLOSER_REDUCE}
28737
-
28738
- ${BASE_INTRO(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28739
- }
28740
-
28741
- // src/system-prompt.ts
28742
29677
  var MAGIC_CONTEXT_MARKER = "## Magic Context";
28743
29678
  var stickyDateBySession = new Map;
28744
29679
  function buildMagicContextBlock(opts) {
@@ -29489,6 +30424,7 @@ var deferredHistoryRefreshSessions = new Set;
29489
30424
  var deferredMaterializationSessions = new Set;
29490
30425
  var sessionsByProject = new Map;
29491
30426
  var lastSeenProjectIdentityBySession = new Map;
30427
+ var persistedProjectIdentityBySession = new Map;
29492
30428
  var rawMessageProviderUnregistersBySession = new Map;
29493
30429
  var activeContextHandlerSessions = new Set;
29494
30430
  var lastHeuristicsTurnIdBySession = new Map;
@@ -29681,12 +30617,14 @@ var MAX_TRACKED_SESSIONS = 100;
29681
30617
  function trackSessionForProject(projectIdentity, sessionId) {
29682
30618
  activeContextHandlerSessions.delete(sessionId);
29683
30619
  activeContextHandlerSessions.add(sessionId);
29684
- let sessions = sessionsByProject.get(projectIdentity);
29685
- if (!sessions) {
29686
- sessions = new Set;
29687
- sessionsByProject.set(projectIdentity, sessions);
30620
+ if (isUsableProjectIdentity(projectIdentity)) {
30621
+ let sessions = sessionsByProject.get(projectIdentity);
30622
+ if (!sessions) {
30623
+ sessions = new Set;
30624
+ sessionsByProject.set(projectIdentity, sessions);
30625
+ }
30626
+ sessions.add(sessionId);
29688
30627
  }
29689
- sessions.add(sessionId);
29690
30628
  while (activeContextHandlerSessions.size > MAX_TRACKED_SESSIONS) {
29691
30629
  const oldest = activeContextHandlerSessions.values().next().value;
29692
30630
  if (oldest === undefined || oldest === sessionId)
@@ -29708,9 +30646,10 @@ function updateSessionProjectTracking(sessionId, projectIdentity, db) {
29708
30646
  sessionsByProject.delete(prev);
29709
30647
  clearPiSystemPromptSession(sessionId);
29710
30648
  }
29711
- if (db && prev !== projectIdentity) {
30649
+ if (db && persistedProjectIdentityBySession.get(sessionId) !== projectIdentity) {
29712
30650
  try {
29713
30651
  recordSessionProjectIdentity(db, sessionId, projectIdentity);
30652
+ persistedProjectIdentityBySession.set(sessionId, projectIdentity);
29714
30653
  } catch {}
29715
30654
  }
29716
30655
  trackSessionForProject(projectIdentity, sessionId);
@@ -30444,6 +31383,8 @@ function registerPiContextHandler(pi, baseOptions) {
30444
31383
  }
30445
31384
  const previousModelKey = liveModelBySession.get(sessionId);
30446
31385
  const currentModelKey = resolvePiContextModelKey(ctx);
31386
+ if (options.cacheTtlConfig !== undefined)
31387
+ resolveSessionCacheTtl(options.db, sessionId, options.cacheTtlConfig, currentModelKey);
30447
31388
  const modelChanged = previousModelKey !== undefined && currentModelKey !== undefined && piModelRefToCanonical(previousModelKey) !== piModelRefToCanonical(currentModelKey);
30448
31389
  if (currentModelKey !== undefined) {
30449
31390
  liveModelBySession.set(sessionId, currentModelKey);
@@ -30837,7 +31778,7 @@ function registerPiContextHandler(pi, baseOptions) {
30837
31778
  const tTodoCapture = performance.now();
30838
31779
  try {
30839
31780
  const sessionMetaForTodo = sessionMeta;
30840
- if (!options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
31781
+ if (options.todowriteEnabled === true && !options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
30841
31782
  const isCacheBustingForTodo = isCacheBusting || result.executedWorkThisPass;
30842
31783
  outputMessages = injectSyntheticTodowriteForPi({
30843
31784
  db: options.db,
@@ -31067,6 +32008,10 @@ function registerPiContextHandler(pi, baseOptions) {
31067
32008
  log("[magic-context][pi] registered context handler (tagging + drops + nudges)");
31068
32009
  }
31069
32010
  var inFlightHistorian = new Map;
32011
+ var historianAbortControllers = new Map;
32012
+ function abortInFlightHistorians(sessionId) {
32013
+ historianAbortControllers.get(sessionId)?.abort();
32014
+ }
31070
32015
  async function awaitInFlightHistorians(sessionId) {
31071
32016
  const runs = sessionId ? [inFlightHistorian.get(sessionId)].filter((run) => run !== undefined) : [...inFlightHistorian.values()];
31072
32017
  if (runs.length === 0)
@@ -31179,7 +32124,11 @@ function spawnPiHistorianRun(args) {
31179
32124
  fallbackModelId
31180
32125
  } = args;
31181
32126
  const holderId = crypto3.randomUUID();
32127
+ const controller = new AbortController;
32128
+ historianAbortControllers.set(sessionId, controller);
31182
32129
  const runPromise = (async () => {
32130
+ if (controller.signal.aborted)
32131
+ return;
31183
32132
  const lease = acquireCompartmentLease(db, sessionId, holderId);
31184
32133
  if (!lease) {
31185
32134
  sessionLog(sessionId, "historian skipped: compartment lease held by another process");
@@ -31200,6 +32149,7 @@ function spawnPiHistorianRun(args) {
31200
32149
  appendCompaction: resolvePiAppendCompaction(ctx),
31201
32150
  readBranchEntries: resolvePiReadBranchEntries(ctx),
31202
32151
  runner: historian.runner,
32152
+ signal: controller.signal,
31203
32153
  historianModel: historian.model,
31204
32154
  fallbackModels: historian.fallbackModels,
31205
32155
  fallbackModelId,
@@ -31268,6 +32218,8 @@ function spawnPiHistorianRun(args) {
31268
32218
  }).finally(() => {
31269
32219
  try {
31270
32220
  inFlightHistorian.delete(sessionId);
32221
+ if (historianAbortControllers.get(sessionId) === controller)
32222
+ historianAbortControllers.delete(sessionId);
31271
32223
  unregister();
31272
32224
  if (isContextHandlerSessionActive(sessionId)) {
31273
32225
  historian.onStatusChange?.(ctx, sessionId);
@@ -31470,6 +32422,8 @@ Historian previously failed ${failureCount} time(s), so Magic Context is retryin
31470
32422
  }
31471
32423
  return;
31472
32424
  }
32425
+ if (!ctx.hasUI)
32426
+ return;
31473
32427
  triggered = true;
31474
32428
  sessionLog(sessionId, `historian trigger fired (reason=${trigger.reason ?? "unknown"}) usage=${usage.percentage.toFixed(1)}% — spawning subagent`);
31475
32429
  spawnPiHistorianRun({
@@ -32665,6 +33619,7 @@ function clearContextHandlerSession(sessionId) {
32665
33619
  lastHeuristicsTurnIdBySession.delete(sessionId);
32666
33620
  routinePressureAppliedBySession.delete(sessionId);
32667
33621
  lastSeenProjectIdentityBySession.delete(sessionId);
33622
+ persistedProjectIdentityBySession.delete(sessionId);
32668
33623
  for (const [projectIdentity, sessions] of sessionsByProject) {
32669
33624
  sessions.delete(sessionId);
32670
33625
  if (sessions.size === 0)
@@ -32819,6 +33774,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
32819
33774
  finished = true;
32820
33775
  return null;
32821
33776
  }
33777
+ deleteChunkEmbedBackoffForSession(db, sessionId);
32822
33778
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
32823
33779
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
32824
33780
  insertRecompCompartmentRows(db, sessionId, staging.compartments, now);
@@ -32833,6 +33789,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
32833
33789
  clearCachedM0M1(db, sessionId);
32834
33790
  db.exec("COMMIT");
32835
33791
  finished = true;
33792
+ invalidateAutoEmbedSession(sessionId);
32836
33793
  logSlowWriteTransaction("historian-publish:recomp", transactionStartedAt);
32837
33794
  return { compartments: staging.compartments, facts: staging.facts };
32838
33795
  } finally {
@@ -34287,6 +35244,8 @@ ${c.content}
34287
35244
 
34288
35245
  // ../plugin/src/shared/cache-ttl-display.ts
34289
35246
  function resolveCacheTtlDisplay(args) {
35247
+ if (args.frozen)
35248
+ return args.frozen;
34290
35249
  if (args.sessionModelKey && (!args.modelKey || args.sessionModelKey === args.modelKey) || !args.modelKey && !args.sessionModelKey && args.sessionValue !== "5m") {
34291
35250
  return {
34292
35251
  value: args.sessionValue || "5m",
@@ -34294,24 +35253,14 @@ function resolveCacheTtlDisplay(args) {
34294
35253
  modelKey: args.modelKey ?? args.sessionModelKey ?? undefined
34295
35254
  };
34296
35255
  }
34297
- if (typeof args.configured === "string") {
34298
- return {
34299
- value: args.configured,
34300
- source: args.configuredExplicitly ? "config" : "default",
34301
- modelKey: args.modelKey
34302
- };
34303
- }
34304
- const matched = resolveModelConfigValue(args.configured, args.modelKey);
34305
- if (matched) {
34306
- return { value: matched.value, source: "config", modelKey: args.modelKey };
34307
- }
34308
- return {
34309
- value: args.configured.default ?? "5m",
34310
- source: "default",
34311
- modelKey: args.modelKey
34312
- };
35256
+ const resolved = resolveModelCacheTtl(args.configured, args.modelKey);
35257
+ if (resolved.source === "default" && typeof args.configured === "string" && args.configuredExplicitly)
35258
+ return { ...resolved, source: "config" };
35259
+ return resolved;
34313
35260
  }
34314
35261
  function formatCacheTtlDisplay(display) {
35262
+ if (display.source === "OpenAI GPT-5.6+ default")
35263
+ return `Cache TTL: ${display.value} (${display.source})`;
34315
35264
  if (display.source === "session")
34316
35265
  return `Cache TTL: ${display.value} (session)`;
34317
35266
  if (display.source === "config") {
@@ -34867,7 +35816,7 @@ function buildStatusView(source, options) {
34867
35816
  // package.json
34868
35817
  var package_default = {
34869
35818
  name: "@cortexkit/pi-magic-context",
34870
- version: "0.44.0",
35819
+ version: "0.44.2",
34871
35820
  type: "module",
34872
35821
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
34873
35822
  main: "dist/index.js",
@@ -34985,6 +35934,11 @@ async function showStatusDialog(pi, ctx, deps) {
34985
35934
  overlayOptions: { anchor: "center", width: 78 }
34986
35935
  });
34987
35936
  }
35937
+ var openStatusDialogs = new Set;
35938
+ function stopStatusDialogRefresh() {
35939
+ for (const dialog of openStatusDialogs)
35940
+ dialog.close();
35941
+ }
34988
35942
 
34989
35943
  class StatusDialogComponent {
34990
35944
  props;
@@ -34994,6 +35948,7 @@ class StatusDialogComponent {
34994
35948
  constructor(props) {
34995
35949
  this.props = props;
34996
35950
  this.detail = buildPiStatusDetail(props.pi, props.ctx, props.deps, props.sessionId);
35951
+ openStatusDialogs.add(this);
34997
35952
  this.refreshTimer = setInterval(() => {
34998
35953
  if (this.closed)
34999
35954
  return;
@@ -35011,11 +35966,7 @@ class StatusDialogComponent {
35011
35966
  close() {
35012
35967
  if (this.closed)
35013
35968
  return;
35014
- this.closed = true;
35015
- if (this.refreshTimer) {
35016
- clearInterval(this.refreshTimer);
35017
- this.refreshTimer = null;
35018
- }
35969
+ this.dispose();
35019
35970
  this.props.done(undefined);
35020
35971
  }
35021
35972
  invalidate() {}
@@ -35025,6 +35976,8 @@ class StatusDialogComponent {
35025
35976
  return drawBorder(inner, width, this.props.theme);
35026
35977
  }
35027
35978
  dispose() {
35979
+ this.closed = true;
35980
+ openStatusDialogs.delete(this);
35028
35981
  if (this.refreshTimer) {
35029
35982
  clearInterval(this.refreshTimer);
35030
35983
  this.refreshTimer = null;
@@ -35279,6 +36232,7 @@ ${safeStringify(tool.parameters)}`);
35279
36232
  sessionId
35280
36233
  });
35281
36234
  const cacheTtlDisplay = resolveCacheTtlDisplay({
36235
+ frozen: readSessionCacheTtl(deps.db, sessionId),
35282
36236
  configured: deps.cacheTtlConfig ?? "5m",
35283
36237
  configuredExplicitly: deps.cacheTtlConfigured === true,
35284
36238
  modelKey,
@@ -35567,7 +36521,10 @@ No active Pi session is available.`,
35567
36521
  });
35568
36522
  return;
35569
36523
  }
35570
- const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
36524
+ const currentDeps = {
36525
+ ...deps.resolveRuntimeDeps?.(ctx) ?? deps,
36526
+ shutdownSignal: deps.shutdownSignal
36527
+ };
35571
36528
  if (currentDeps.compactionOff) {
35572
36529
  sendStatus({
35573
36530
  title: "/ctx-wrapup",
@@ -35700,6 +36657,8 @@ No runnable wrapup boundary is available yet; wrapped up 0 messages into 0 compa
35700
36657
  console.warn(`[magic-context][pi] /ctx-wrapup marker renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
35701
36658
  }
35702
36659
  }, 60000);
36660
+ const stopRenewal = () => clearInterval(renewal);
36661
+ deps.shutdownSignal?.addEventListener("abort", stopRenewal, { once: true });
35703
36662
  try {
35704
36663
  sendStatus({
35705
36664
  title: "/ctx-wrapup",
@@ -35766,7 +36725,7 @@ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleS
35766
36725
  Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1} (~${plan.snapshot.trueRawEligibleTokens.toLocaleString()} eligible tokens remain).`,
35767
36726
  level: "info"
35768
36727
  });
35769
- const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps));
36728
+ const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps), deps.shutdownSignal);
35770
36729
  if (!leaseResult.ok) {
35771
36730
  failure = ownershipLost ? `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : leaseResult.reason === "timeout" ? `Timed out waiting for another process to release the compartment-state lease; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : "Another Magic Context rebuild started while wrapup was waiting. Run /ctx-wrapup again to continue.";
35772
36731
  break;
@@ -35779,6 +36738,10 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
35779
36738
  console.warn(`[magic-context][pi] /ctx-wrapup compartment lease renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
35780
36739
  }
35781
36740
  }, COMPARTMENT_LEASE_RENEWAL_MS);
36741
+ const stopLeaseRenewal = () => clearInterval(leaseRenewal);
36742
+ deps.shutdownSignal?.addEventListener("abort", stopLeaseRenewal, {
36743
+ once: true
36744
+ });
35782
36745
  try {
35783
36746
  const runHistorian = deps.runPiHistorianForWrapup ?? runPiHistorian;
35784
36747
  await runHistorian({
@@ -35787,6 +36750,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
35787
36750
  directory: ctx.cwd,
35788
36751
  provider,
35789
36752
  runner: deps.runner,
36753
+ signal: deps.shutdownSignal,
35790
36754
  historianModel: deps.historianModel,
35791
36755
  fallbackModels: deps.historianFallbacks,
35792
36756
  fallbackModelId: modelKey,
@@ -35830,6 +36794,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
35830
36794
  });
35831
36795
  } finally {
35832
36796
  clearInterval(leaseRenewal);
36797
+ deps.shutdownSignal?.removeEventListener("abort", stopLeaseRenewal);
35833
36798
  releaseCompartmentLeaseBestEffort(deps.db, sessionId, leaseHolder, sessionLog);
35834
36799
  }
35835
36800
  const afterEnd = getLastCompartmentEndMessage(deps.db, sessionId);
@@ -35859,6 +36824,7 @@ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments;
35859
36824
  Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments. The compacted history is queued and materializes on your next message.`;
35860
36825
  } finally {
35861
36826
  clearInterval(renewal);
36827
+ deps.shutdownSignal?.removeEventListener("abort", stopRenewal);
35862
36828
  releaseWrapupInProgress(deps.db, sessionId, holderId);
35863
36829
  }
35864
36830
  } finally {
@@ -35880,10 +36846,12 @@ function resolvePiContextLimit(ctx, db, sessionId) {
35880
36846
  detectedContextLimit
35881
36847
  }) ?? 128000;
35882
36848
  }
35883
- async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs) {
36849
+ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs, signal) {
35884
36850
  const waitStartedAt = Date.now();
35885
36851
  const remainingMs = () => Math.max(0, waitStartedAt + maxWaitMs - Date.now());
35886
36852
  for (;; ) {
36853
+ if (signal?.aborted)
36854
+ return { ok: false, reason: "ownership_lost" };
35887
36855
  if (remainingMs() <= 0)
35888
36856
  return { ok: false, reason: "timeout" };
35889
36857
  const holderId = crypto4.randomUUID();
@@ -35892,7 +36860,17 @@ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarke
35892
36860
  return { ok: true, holderId };
35893
36861
  if (!renewWrapupMarker({}))
35894
36862
  return { ok: false, reason: "ownership_lost" };
35895
- await new Promise((resolve) => setTimeout(resolve, Math.min(LEASE_WAIT_MS, remainingMs())));
36863
+ await new Promise((resolve) => {
36864
+ const finish = () => {
36865
+ clearTimeout(timer);
36866
+ signal?.removeEventListener("abort", finish);
36867
+ resolve();
36868
+ };
36869
+ const timer = setTimeout(finish, Math.min(LEASE_WAIT_MS, remainingMs()));
36870
+ signal?.addEventListener("abort", finish, { once: true });
36871
+ if (signal?.aborted)
36872
+ finish();
36873
+ });
35896
36874
  }
35897
36875
  }
35898
36876
  function estimateChunks(tokens, chunkTokens) {
@@ -35916,6 +36894,32 @@ function readBranchEntries(ctx) {
35916
36894
  return Array.isArray(branch?.entries) ? branch.entries : [];
35917
36895
  }
35918
36896
 
36897
+ // src/dreamer/session-activity-pi.ts
36898
+ function observePiMessageActivity(db, sessionId, timestamp) {
36899
+ observeSessionActivity(db, sessionId, timestamp);
36900
+ }
36901
+ function latestPiMessageTime(path) {
36902
+ let latest = 0;
36903
+ for (const entry of sessionEntries(path)) {
36904
+ const row = entry;
36905
+ if (row.type === "message" && typeof row.message?.timestamp === "number")
36906
+ latest = Math.max(latest, row.message.timestamp);
36907
+ }
36908
+ return latest || undefined;
36909
+ }
36910
+ async function backfillPiSessionActivity(db, harness, paths) {
36911
+ await backfillSessionActivity(db, harness, (sessionId) => {
36912
+ const path = paths.get(sessionId);
36913
+ if (!path)
36914
+ return;
36915
+ try {
36916
+ return latestPiMessageTime(path);
36917
+ } catch {
36918
+ return;
36919
+ }
36920
+ });
36921
+ }
36922
+
35919
36923
  // src/dropped-input-guard-pi.ts
35920
36924
  function createPiDroppedInputGuard(options = {}) {
35921
36925
  const guard = createDroppedInputGuard({
@@ -36114,6 +37118,8 @@ function stripTagPrefixFromAssistantMessage(message) {
36114
37118
  var PI_HARNESS_DETECTION = await resolvePiHarnessDetection();
36115
37119
  var PI_HARNESS_KIND = PI_HARNESS_DETECTION.kind;
36116
37120
  var PREFIX2 = `[magic-context][${PI_HARNESS_KIND}]`;
37121
+ var databaseExitHookRegistered = false;
37122
+ var activePiRuntimes = new Set;
36117
37123
  var PI_BOOT_DEADLINE_MS = 15000;
36118
37124
  var PI_CHILD_INIT_CONTEXT = Symbol.for("magic-context.pi.child-init-context");
36119
37125
  var SUBAGENT_CHILD_SESSION_CREATED = "subagents:child:session-created";
@@ -36188,7 +37194,7 @@ function persistPiMessageEndModelMeta(args) {
36188
37194
  const modelKey = canonicalPiModelKey(msg.provider, msg.model);
36189
37195
  if (!recordPiLiveModel(args.sessionId, modelKey, msg.timestamp))
36190
37196
  return;
36191
- const cacheTtl = resolveCacheTtl(args.cacheTtlConfig, modelKey);
37197
+ const cacheTtl = resolveSessionCacheTtl(args.db, args.sessionId, args.cacheTtlConfig, modelKey).value;
36192
37198
  const currentMeta = getOrCreateSessionMeta(args.db, args.sessionId);
36193
37199
  updateSessionMeta(args.db, args.sessionId, {
36194
37200
  ...currentMeta.cacheTtl === cacheTtl ? {} : { cacheTtl },
@@ -36254,6 +37260,13 @@ function info(message, data) {
36254
37260
  function warn(message, data) {
36255
37261
  log(`${PREFIX2} WARN ${message}`, data);
36256
37262
  }
37263
+ var loggedNoProjectIdentityDirs = new Set;
37264
+ function logNoProjectIdentityOnce(directory) {
37265
+ if (loggedNoProjectIdentityDirs.has(directory))
37266
+ return;
37267
+ loggedNoProjectIdentityDirs.add(directory);
37268
+ 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.");
37269
+ }
36257
37270
  var migratedConfigDirs = new Set;
36258
37271
  var loggedPiConfigDirs = new Set;
36259
37272
  function ensureConfigLocationsMigrated(dir) {
@@ -36294,6 +37307,14 @@ function claimProtectedTagsDeprecationNoticeOnce() {
36294
37307
  function resetClaimedProtectedTagsDeprecationNoticeForTesting() {
36295
37308
  protectedTagsDeprecationClaimed = false;
36296
37309
  }
37310
+ function registerConfiguredTodoLifecycle(pi, options) {
37311
+ if (!options.configured)
37312
+ return;
37313
+ registerTodoStateLifecycle(pi, {
37314
+ readLastTodoState: options.readLastTodoState
37315
+ });
37316
+ return options.overlay ? registerTodoOverlay(pi, { readLastTodoState: options.readLastTodoState }) : undefined;
37317
+ }
36297
37318
  var __test = {
36298
37319
  logPiConfigLoad,
36299
37320
  resetLoggedPiConfigDirs() {
@@ -36475,7 +37496,7 @@ function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND, modelRegi
36475
37496
  model: resolved.primary,
36476
37497
  fallbackModels: resolved.fallbacks
36477
37498
  }
36478
- ], modelRegistry)[0] : undefined;
37499
+ ], modelRegistry, harness)[0] : undefined;
36479
37500
  const primary = validated ? typeof validated.model === "string" ? { model: validated.model } : validated.model : resolved.primary;
36480
37501
  const model = primary?.model;
36481
37502
  if (!model)
@@ -36546,8 +37567,12 @@ async function src_default(pi) {
36546
37567
  cacheSizeMb: bootConfig.config.sqlite.cache_size_mb,
36547
37568
  mmapSizeMb: bootConfig.config.sqlite.mmap_size_mb
36548
37569
  });
37570
+ if (!databaseExitHookRegistered) {
37571
+ process.once("exit", closeDatabase);
37572
+ databaseExitHookRegistered = true;
37573
+ }
36549
37574
  const storageDir = getMagicContextStorageDir();
36550
- const dbPath = join8(storageDir, "context.db");
37575
+ const dbPath = join11(storageDir, "context.db");
36551
37576
  let openFailureCause = null;
36552
37577
  const openStorage = async () => {
36553
37578
  try {
@@ -36619,6 +37644,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36619
37644
  const offset = afterSessionId === null ? 0 : sessions.findIndex((session) => session.sessionId === afterSessionId) + 1;
36620
37645
  return sessions.slice(offset, offset + limit);
36621
37646
  });
37647
+ const api = await loadDefaultPiSessionApi();
37648
+ const paths = new Map;
37649
+ for (const session of await api.listSessions()) {
37650
+ if (typeof session.id === "string" && typeof session.path === "string")
37651
+ paths.set(session.id, session.path);
37652
+ }
37653
+ await backfillPiSessionActivity(database, PI_HARNESS_KIND, paths);
36622
37654
  } catch (err) {
36623
37655
  warn(`[session-projects] background runner failed: ${err}`);
36624
37656
  }
@@ -36628,6 +37660,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36628
37660
  const projectDir = process.cwd();
36629
37661
  const seenDreamerProjectIdentities = new Set;
36630
37662
  const dreamerRegistrationOwner = {};
37663
+ activePiRuntimes.add(dreamerRegistrationOwner);
36631
37664
  const commandLifecycleController = new AbortController;
36632
37665
  registerCtxStatusLifecycleSignal(pi, commandLifecycleController.signal);
36633
37666
  let sessionShuttingDown = false;
@@ -36652,7 +37685,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36652
37685
  const projectIdentity = resolveProjectIdentityForSession(projectDir, config.allow_home_project) ?? "";
36653
37686
  if (projectIdentity)
36654
37687
  seenDreamerProjectIdentities.add(projectIdentity);
36655
- info(`loaded v${PLUGIN_VERSION} | harness=${PI_HARNESS_KIND} (via ${PI_HARNESS_DETECTION.via}) | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
37688
+ info(`loaded v${PLUGIN_VERSION} | harness=${PI_HARNESS_KIND} (via ${PI_HARNESS_DETECTION.via}) | db=${dbPath} | ` + `project=${projectIdentity || "(none)"} | dir=${projectDir}`);
36656
37689
  const compactionOff = !isCompactionEnabled(config);
36657
37690
  setCtxReduceRegisteredGlobally(!compactionOff);
36658
37691
  if (!compactionOff) {
@@ -36687,7 +37720,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36687
37720
  return;
36688
37721
  }
36689
37722
  await ensureProjectRegisteredFromPiDirectory(projectDir, db);
36690
- info(`registered embedding config for project ${projectIdentity ?? "(no project identity; cwd is $HOME)"}`);
37723
+ if (isUsableProjectIdentity(projectIdentity)) {
37724
+ info(`registered embedding config for project ${projectIdentity}`);
37725
+ } else {
37726
+ logNoProjectIdentityOnce(projectDir);
37727
+ }
36691
37728
  const projectDepsByDir = new Map;
36692
37729
  const liveReaders = new Map;
36693
37730
  function liveReaderFor(dir, boot) {
@@ -36711,6 +37748,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36711
37748
  }
36712
37749
  const buildContextOptions = (cfg, hist, auto) => ({
36713
37750
  db: database,
37751
+ cacheTtlConfig: cfg.cache_ttl,
36714
37752
  smartDrops: cfg.smart_drops === true,
36715
37753
  protectedTokens: cfg.protected_tokens,
36716
37754
  protectedTokenTierOverrides: getProtectedTokensTierOverrides(cfg) ?? {},
@@ -36737,6 +37775,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36737
37775
  language: cfg.language,
36738
37776
  autoSearch: auto,
36739
37777
  resolveForProject: resolveContextOptionsForProject,
37778
+ todowriteEnabled: cfg.todowrite.enabled,
36740
37779
  compactionOff,
36741
37780
  allowHomeProject: cfg.allow_home_project,
36742
37781
  maybeAutoEmbedSession: (sessionId, dir, identity) => {
@@ -36814,6 +37853,10 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36814
37853
  function syncDreamerProjectRegistration(current, modelRegistry) {
36815
37854
  if (sessionShuttingDown)
36816
37855
  return;
37856
+ if (!isUsableProjectIdentity(current.projectIdentity)) {
37857
+ logNoProjectIdentityOnce(current.projectDir);
37858
+ return;
37859
+ }
36817
37860
  seenDreamerProjectIdentities.add(current.projectIdentity);
36818
37861
  if (!current.dreamerConfig) {
36819
37862
  unregisterPiDreamerProject({
@@ -36849,8 +37892,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36849
37892
  });
36850
37893
  }
36851
37894
  registerPiDroppedInputGuard(pi);
36852
- const todowriteEnabled = bootProjectDeps.config.todowrite.enabled !== false;
36853
- const todowriteOverlayEnabled = todowriteEnabled && bootProjectDeps.config.todowrite.overlay !== false;
37895
+ const todowriteEnabled = bootProjectDeps.config.todowrite.enabled;
37896
+ const todowriteOverlayEnabled = todowriteEnabled && bootProjectDeps.config.todowrite.overlay;
36854
37897
  registerMagicContextTools(pi, {
36855
37898
  db,
36856
37899
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
@@ -36871,7 +37914,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36871
37914
  projectDepsByDir.delete(ctx.cwd);
36872
37915
  const current = resolveCurrentProjectDeps(ctx);
36873
37916
  activeModelRegistry = ctx.modelRegistry;
36874
- syncDreamerProjectRegistration(current, ctx.modelRegistry);
37917
+ if (ctx.hasUI)
37918
+ syncDreamerProjectRegistration(current, ctx.modelRegistry);
36875
37919
  syncCtxMemoryToolEnabled(pi, current.config.memory.enabled);
36876
37920
  const failuresToShow = claimConfigParseFailuresOnce("pi", current.configParseFailures);
36877
37921
  if (ctx.hasUI && failuresToShow.length > 0) {
@@ -36896,12 +37940,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36896
37940
  });
36897
37941
  });
36898
37942
  const readLastTodoState = (sessionId) => getOrCreateSessionMeta(db, sessionId).lastTodoState;
36899
- if (todowriteEnabled) {
36900
- registerTodoStateLifecycle(pi, { readLastTodoState });
36901
- }
36902
- const todoOverlay = todowriteOverlayEnabled ? registerTodoOverlay(pi, {
37943
+ const todoOverlay = registerConfiguredTodoLifecycle(pi, {
37944
+ configured: todowriteEnabled,
37945
+ overlay: bootProjectDeps.config.todowrite.overlay,
36903
37946
  readLastTodoState
36904
- }) : undefined;
37947
+ });
36905
37948
  info(todowriteOverlayEnabled ? "registered todowrite overlay" : "registered todowrite overlay: DISABLED (todowrite.enabled=false or todowrite.overlay=false)");
36906
37949
  registerPiContextHandler(pi, bootProjectDeps.contextOptions);
36907
37950
  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)");
@@ -36954,7 +37997,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36954
37997
  const registry = activeModelRegistry;
36955
37998
  if (!registry)
36956
37999
  return;
36957
- const tasks = validatePiDreamerModels(buildDreamTaskRuntimeConfigs(current.config.dreamer, PI_HARNESS_KIND, current.config.language, current.config.mural.model), registry);
38000
+ const tasks = validatePiDreamerModels(buildDreamTaskRuntimeConfigs(current.config.dreamer, PI_HARNESS_KIND, current.config.language, current.config.mural.model), registry, PI_HARNESS_KIND);
36958
38001
  const empty = tasks.filter((task) => task.modelChainUnavailable).map((task) => task.task);
36959
38002
  if (!resolveHistorianFromConfig(current.config, PI_HARNESS_KIND, registry) && current.historianConfig)
36960
38003
  empty.push("historian");
@@ -37007,6 +38050,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37007
38050
  });
37008
38051
  info("registered /ctx-recomp");
37009
38052
  registerCtxWrapupCommand(pi, {
38053
+ shutdownSignal: commandLifecycleController.signal,
37010
38054
  db,
37011
38055
  runner: wrapupRunner,
37012
38056
  historianModel: bootProjectDeps.historianConfig?.model,
@@ -37058,7 +38102,10 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37058
38102
  dreamerEnabled: bootProjectDeps.dreamerEnabled,
37059
38103
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
37060
38104
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity),
37061
- ensureRegistered: (ctx) => syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry),
38105
+ ensureRegistered: (ctx) => {
38106
+ if (ctx.hasUI)
38107
+ syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry);
38108
+ },
37062
38109
  registrationOwner: dreamerRegistrationOwner
37063
38110
  });
37064
38111
  info("registered /ctx-dream");
@@ -37077,7 +38124,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37077
38124
  info("registered /ctx-embed");
37078
38125
  const dreamerConfig = bootProjectDeps.dreamerConfig;
37079
38126
  if (dreamerConfig) {
37080
- syncDreamerProjectRegistration(bootProjectDeps);
37081
38127
  info(`registered dreamer (${summarizeDreamSchedule(dreamerConfig)})`);
37082
38128
  } else {
37083
38129
  info(bootProjectDeps.dreamerEnabled ? "registered dreamer: DISABLED (no dreamer config)" : "registered dreamer: DISABLED (dreamer.disable=true or no dreamer config)");
@@ -37110,7 +38156,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37110
38156
  const effectiveConfig = effectiveProjectDeps.config;
37111
38157
  try {
37112
38158
  activeModelRegistry = ctx.modelRegistry;
37113
- syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
38159
+ if (ctx.hasUI)
38160
+ syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
37114
38161
  } catch (err) {
37115
38162
  warn("before_agent_start: dreamer registration sync threw:", err);
37116
38163
  }
@@ -37206,6 +38253,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37206
38253
  } catch (err) {
37207
38254
  log(`agent_end: channel2 delivery skipped: ${String(err)}`);
37208
38255
  }
38256
+ if (!ctx.hasUI)
38257
+ flushLogger();
37209
38258
  });
37210
38259
  pi.on("tool_execution_start", async (event, ctx) => {
37211
38260
  try {
@@ -37286,6 +38335,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37286
38335
  if (typeof sessionId !== "string" || sessionId.length === 0)
37287
38336
  return;
37288
38337
  const endedMsg = event.message;
38338
+ observePiMessageActivity(db, sessionId, endedMsg?.timestamp);
37289
38339
  if (endedMsg?.role === "assistant" && typeof endedMsg.id === "string" && endedMsg.id.length > 0) {
37290
38340
  const messageId = endedMsg.id;
37291
38341
  scheduleIncrementalIndex(db, sessionId, messageId, () => {
@@ -37366,6 +38416,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37366
38416
  pi.on("session_shutdown", async (_event, ctx) => {
37367
38417
  sessionShuttingDown = true;
37368
38418
  commandLifecycleController.abort();
38419
+ stopStatusDialogRefresh();
37369
38420
  const SHUTDOWN_DRAIN_MS = 5000;
37370
38421
  const sessionId = resolveSessionId(ctx);
37371
38422
  try {
@@ -37379,6 +38430,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37379
38430
  warn("shutdown: unregisterPiDreamerProject threw:", err);
37380
38431
  }
37381
38432
  if (sessionId) {
38433
+ abortInFlightHistorians(sessionId);
38434
+ abortInFlightRecomps(sessionId);
37382
38435
  try {
37383
38436
  await withTimeout(awaitInFlightHistorians(sessionId), SHUTDOWN_DRAIN_MS);
37384
38437
  } catch (err) {
@@ -37391,6 +38444,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37391
38444
  }
37392
38445
  abortInFlightRecomps(sessionId);
37393
38446
  }
38447
+ abortInFlightDreamers(dreamerRegistrationOwner);
37394
38448
  try {
37395
38449
  await withTimeout(awaitInFlightDreamers(dreamerRegistrationOwner), SHUTDOWN_DRAIN_MS);
37396
38450
  } catch (err) {
@@ -37405,6 +38459,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37405
38459
  clearContextHandlerSession(sessionId);
37406
38460
  }
37407
38461
  } catch {}
38462
+ activePiRuntimes.delete(dreamerRegistrationOwner);
38463
+ if (activePiRuntimes.size === 0)
38464
+ unregisterPiProjectEmbeddings(db);
37408
38465
  });
37409
38466
  pi.on("session_before_switch", (_event, ctx) => {
37410
38467
  try {
@@ -37441,6 +38498,7 @@ export {
37441
38498
  handlePiSessionBeforeCompact,
37442
38499
  persistPiMessageEndModelMeta,
37443
38500
  persistPiPressureFromMessageEnd,
38501
+ registerConfiguredTodoLifecycle,
37444
38502
  resetClaimedProtectedTagsDeprecationNoticeForTesting,
37445
38503
  resolveDreamerFromConfig,
37446
38504
  resolveHistorianFromConfig,