@cortexkit/pi-magic-context 0.44.1 → 0.44.3

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
@@ -38,8 +38,10 @@ import {
38
38
  formatWindowDerivationLine,
39
39
  isSaneLimit,
40
40
  resolveOutputReserve,
41
+ getSdkWindowGeometry,
41
42
  getSdkContextLimit,
42
43
  getSdkOutputLimit,
44
+ getSdkInputLimit,
43
45
  cortexKitUserConfigBasePath,
44
46
  cortexKitProjectConfigBasePath,
45
47
  migrateMagicContextConfigLocations,
@@ -49,6 +51,7 @@ import {
49
51
  nextDueAtMs,
50
52
  resolveModelConfigValue,
51
53
  DEFAULT_HISTORIAN_TIMEOUT_MS,
54
+ DREAM_TASK_PROMOTION_DEFAULTS,
52
55
  LIVE_RELOAD_CONFIG_PATHS,
53
56
  getProtectedTokensTierOverrides,
54
57
  FAIL_CLOSED_DOCTOR_COMMAND,
@@ -57,8 +60,10 @@ import {
57
60
  isFailClosedBlockingError,
58
61
  shouldBypassFailClosedBlock,
59
62
  createFailClosedController,
63
+ setHomeProjectPermission,
60
64
  ProjectIdentityError,
61
65
  resolveProjectIdentityStrict,
66
+ isUserHomeDirectory,
62
67
  resolveProjectIdentity,
63
68
  isUsableProjectIdentity,
64
69
  resolveProjectIdentityForSession,
@@ -66,6 +71,17 @@ import {
66
71
  scheduleAfterBootQuiet,
67
72
  clearCompressionDepth,
68
73
  clearCompressionDepthRange,
74
+ formatSynapseLaneDescriptor,
75
+ buildCanonicalChunkTextFromFts,
76
+ buildCompartmentSummaryFallbackText,
77
+ canonicalizeInMemoryChunkTextForEmbedding,
78
+ chunkCanonicalText,
79
+ chunkEmbeddingWindowsAreCurrent,
80
+ replaceCompartmentChunkEmbeddings,
81
+ deleteChunkEmbedBackoffForSession,
82
+ readSessionActivity,
83
+ observeSessionActivity,
84
+ backfillSessionActivity,
69
85
  getMessageIndexSourceIdentity,
70
86
  isMessageIndexSourceCurrent,
71
87
  getLastIndexedOrdinal,
@@ -168,6 +184,12 @@ import {
168
184
  retryPendingSessionCleanups,
169
185
  retryPendingRustSessionCleanupsForProject,
170
186
  recordSessionProjectIdentity,
187
+ embedPauseBySession,
188
+ embedRunStateBySession,
189
+ autoEmbedAttemptedBySession,
190
+ autoEmbedIdentityBySession,
191
+ invalidateAutoEmbedSession,
192
+ getEmbedDrainUiStatus,
171
193
  piHarnessKindFromExecutable,
172
194
  COMPARTMENT_LEASE_RENEWAL_MS,
173
195
  acquireCompartmentLease,
@@ -231,6 +253,7 @@ import {
231
253
  runSqliteOptimize,
232
254
  openDatabase,
233
255
  openDatabaseAsync,
256
+ closeDatabase,
234
257
  queueM0Mutation,
235
258
  getMaxM0MutationId,
236
259
  queueMemoryMutation,
@@ -325,13 +348,6 @@ import {
325
348
  peekCurateCategoryScope,
326
349
  beginCurateCategoryRun,
327
350
  curateTaskStateAfterSuccess,
328
- formatSynapseLaneDescriptor,
329
- buildCanonicalChunkTextFromFts,
330
- buildCompartmentSummaryFallbackText,
331
- canonicalizeInMemoryChunkTextForEmbedding,
332
- chunkCanonicalText,
333
- chunkEmbeddingWindowsAreCurrent,
334
- replaceCompartmentChunkEmbeddings,
335
351
  cosineSimilarity,
336
352
  GIT_SWEEP_LEASE_RENEWAL_MS,
337
353
  acquireGitSweepLease,
@@ -353,6 +369,7 @@ import {
353
369
  drainCommitBacklogForProject,
354
370
  embedSessionCompartmentChunks,
355
371
  getEmbeddingCoverageStatus,
372
+ getAutoEmbeddingSessionCoverage,
356
373
  promoteSessionFactsDurable,
357
374
  embedPromotedFacts,
358
375
  recordMemoryMapping,
@@ -373,6 +390,7 @@ import {
373
390
  embedUnembeddedCommits,
374
391
  loadPiConfig,
375
392
  loadPiConfigDetailed,
393
+ unregisterPiProjectEmbeddings,
376
394
  ensureProjectRegisteredFromPiDirectory,
377
395
  resolvePiHarnessDetection,
378
396
  resolvePiHarnessKind,
@@ -408,8 +426,10 @@ import {
408
426
  registerTodoStateLifecycle,
409
427
  syncCtxMemoryToolEnabled,
410
428
  registerMagicContextTools
411
- } from "./index-mwpk0wn9.js";
429
+ } from "./index-atb1ecv9.js";
412
430
  import {
431
+ Database,
432
+ withSqliteBackgroundWriter,
413
433
  CTX_REDUCE_KEEP,
414
434
  newestCtxReduceTagNumbers,
415
435
  textMentionsRecentCommit,
@@ -485,7 +505,6 @@ import {
485
505
  recordOpenCodeDbReadFailure,
486
506
  clearOpenCodeDbReadFailure,
487
507
  claimOpenCodeDbDiagnosticOnce,
488
- Database,
489
508
  closeQuietly,
490
509
  RAW_SUMMARY_TEXT_MAX_CHARS,
491
510
  completedToolArcCrossesBoundary,
@@ -496,6 +515,12 @@ import {
496
515
  buildTrueRawTokenIndex,
497
516
  computeRawRangeFingerprint,
498
517
  invalidateTrueRawTokenCache,
518
+ DreamTokenBudgetExceeded,
519
+ TOKEN_BUDGET_FINALIZE_MESSAGE,
520
+ TOKEN_BUDGET_TOOL_REFUSAL,
521
+ registerBudgetFinalizeChild,
522
+ releaseBudgetFinalizeChild,
523
+ createDreamTokenBudget,
499
524
  droppedInputMarker,
500
525
  createDroppedInputGuard,
501
526
  isEditTool,
@@ -522,10 +547,14 @@ import {
522
547
  getRawSessionTagKeysThrough2,
523
548
  getLegacyProtectedTailStartOrdinal2,
524
549
  readSessionChunk2
525
- } from "./index-nm3c0sn8.js";
550
+ } from "./index-tckvezry.js";
526
551
  import {
527
552
  pushNotification2
528
553
  } from "./index-b3eqj1g6.js";
554
+ import {
555
+ subagentStepCap,
556
+ SubagentTelemetry
557
+ } from "./index-n8kg008j.js";
529
558
  import"./index-5fw91enm.js";
530
559
  import {
531
560
  logSlowWriteTransaction
@@ -822,6 +851,15 @@ var CANONICAL_DREAM_TASKS = [
822
851
  "promote-primers",
823
852
  "refresh-primers"
824
853
  ];
854
+ var DREAM_TOOL_LOOP_TOKEN_BUDGETS = {
855
+ "map-memories": 1500000,
856
+ verify: 1700000,
857
+ "verify-broad": 1700000,
858
+ curate: 1500000,
859
+ retrospective: 300000,
860
+ "maintain-docs": 1600000,
861
+ "refresh-primers": 350000
862
+ };
825
863
  var DREAM_TASK_CAPABILITIES = {
826
864
  "map-memories": {
827
865
  requiresTools: true,
@@ -934,6 +972,7 @@ function buildDreamTaskRuntimeConfigs(dreamer, harness, language, muralModel) {
934
972
  thinkingLevel: harness === "pi" ? resolved.primary?.qualifier : undefined,
935
973
  language,
936
974
  timeoutMinutes: resolved.timeoutMinutes ?? 20,
975
+ tokenBudget: task in DREAM_TOOL_LOOP_TOKEN_BUDGETS ? dreamer?.tasks?.[task]?.token_budget ?? DREAM_TOOL_LOOP_TOKEN_BUDGETS[task] : undefined,
937
976
  promotionThreshold: resolved.promotionThreshold,
938
977
  docsMaxTokens: task === "maintain-docs" ? dreamer?.tasks?.["maintain-docs"]?.max_tokens ?? 12000 : undefined,
939
978
  retrospectiveRecencyDays: task === "retrospective" ? dreamer?.tasks?.retrospective?.recency_days ?? 30 : undefined
@@ -1036,7 +1075,7 @@ async function reconcileSessionIndex(db, sessionId, readMessages) {
1036
1075
  const messages = readMessages.readPage ? readMessages.readPage(sessionId, cursor, RECONCILIATION_BATCH_SIZE, finalWatermark) : (fallbackSnapshot ?? []).filter((message) => message.ordinal > cursor && message.ordinal <= pageEnd);
1037
1076
  const retainedMessages = fallbackSnapshot ?? messages;
1038
1077
  heapHolder.activeReconcilerBuffers.set(sessionId, retainedMessages);
1039
- indexMessagesAfterOrdinal(db, sessionId, messages, cursor, pageEnd);
1078
+ withSqliteBackgroundWriter(() => indexMessagesAfterOrdinal(db, sessionId, messages, cursor, pageEnd));
1040
1079
  const nextCursor = getMessageIndexReconciliationStartOrdinal(db, sessionId);
1041
1080
  if (nextCursor <= cursor)
1042
1081
  break;
@@ -1062,6 +1101,8 @@ function scheduleReconciliation(db, sessionId, readMessages) {
1062
1101
  defer(() => {
1063
1102
  reconcileSessionIndex(db, sessionId, readMessages).catch((error) => {
1064
1103
  logIndexingError(sessionId, "reconciliation", error);
1104
+ if (isDatabaseLockedError(error))
1105
+ setTimeout(() => scheduleReconciliation(db, sessionId, readMessages), 1000).unref();
1065
1106
  }).finally(() => {
1066
1107
  heapHolder.reconciliationScheduledSessions.delete(sessionId);
1067
1108
  });
@@ -1091,7 +1132,7 @@ function scheduleIncrementalIndex(db, sessionId, messageId, messageSource) {
1091
1132
  return;
1092
1133
  }
1093
1134
  const wasReconciled = heapHolder.reconciledSessions.delete(sessionId);
1094
- indexSingleMessage(db, sessionId, message);
1135
+ withSqliteBackgroundWriter(() => indexSingleMessage(db, sessionId, message));
1095
1136
  const finalWatermark = getLastIndexedOrdinal(db, sessionId);
1096
1137
  if (wasReconciled && isMessageIndexReconciledThrough(db, sessionId, finalWatermark)) {
1097
1138
  heapHolder.reconciledSessions.add(sessionId);
@@ -1100,6 +1141,8 @@ function scheduleIncrementalIndex(db, sessionId, messageId, messageSource) {
1100
1141
  }).catch((error) => {
1101
1142
  heapHolder.reconciledSessions.delete(sessionId);
1102
1143
  logIndexingError(sessionId, `incremental index for ${messageId}`, error);
1144
+ if (isDatabaseLockedError(error))
1145
+ setTimeout(() => scheduleIncrementalIndex(db, sessionId, messageId, messageSource), 1000).unref();
1103
1146
  }).finally(() => {
1104
1147
  heapHolder.pendingIncrementalKeys.delete(schedulingKey);
1105
1148
  });
@@ -1527,7 +1570,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1527
1570
  const startedAt = performance.now();
1528
1571
  const harness = getHarness();
1529
1572
  const now = options.now ?? Date.now;
1530
- const resolveIdentity = options.resolveIdentity ?? resolveProjectIdentity;
1573
+ const resolveIdentity = options.resolveIdentity ?? ((directory) => resolveProjectIdentityForSession(directory, options.allowHomeProject));
1531
1574
  const yieldFn = options.yieldFn ?? defaultYieldFn;
1532
1575
  const holderId = options.holderId ?? randomUUID();
1533
1576
  const readPage = createPageReader(source);
@@ -1541,7 +1584,8 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1541
1584
  skippedEmptyDirectories: 0,
1542
1585
  durationMs: 0
1543
1586
  };
1544
- const leaseStatus = acquireBackfillLease(db, harness, holderId, now());
1587
+ const leaseKey = options.leaseKey ?? harness;
1588
+ const leaseStatus = acquireBackfillLease(db, leaseKey, holderId, now());
1545
1589
  if (leaseStatus !== "acquired") {
1546
1590
  result.status = leaseStatus;
1547
1591
  result.durationMs = performance.now() - startedAt;
@@ -1553,10 +1597,10 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1553
1597
  return result;
1554
1598
  };
1555
1599
  const yieldAndRenew = async () => {
1556
- if (!renewBackfillLease(db, harness, holderId, now()))
1600
+ if (!renewBackfillLease(db, leaseKey, holderId, now()))
1557
1601
  return false;
1558
1602
  await yieldFn();
1559
- return renewBackfillLease(db, harness, holderId, now());
1603
+ return renewBackfillLease(db, leaseKey, holderId, now());
1560
1604
  };
1561
1605
  const existenceCache = new Map;
1562
1606
  const identityCache = new Map;
@@ -1570,7 +1614,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1570
1614
  sourcePage = await readPage(afterSessionId, SESSION_PAGE_SIZE);
1571
1615
  } catch (error) {
1572
1616
  try {
1573
- if (!markBackfillRetryPending(db, harness, holderId, now())) {
1617
+ if (!markBackfillRetryPending(db, leaseKey, holderId, now())) {
1574
1618
  log("[session-projects] backfill lease changed before failure cleanup");
1575
1619
  }
1576
1620
  } catch (releaseError) {
@@ -1595,7 +1639,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1595
1639
  result.alreadyMappedSessions += 1;
1596
1640
  } else {
1597
1641
  result.unmappedSessions += 1;
1598
- if (!session.directory) {
1642
+ if (!session.directory || !options.allowHomeProject && isUserHomeDirectory(session.directory)) {
1599
1643
  result.skippedEmptyDirectories += 1;
1600
1644
  } else {
1601
1645
  const stillExists = existenceCache.get(session.directory) ?? existsSync(session.directory);
@@ -1613,13 +1657,18 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1613
1657
  }
1614
1658
  identity = await resolveIdentity(session.directory);
1615
1659
  identityResolutionsSinceYield += 1;
1616
- if (!renewBackfillLease(db, harness, holderId, now())) {
1660
+ if (!renewBackfillLease(db, leaseKey, holderId, now())) {
1617
1661
  return finishWithLostLease();
1618
1662
  }
1619
- identityCache.set(session.directory, identity);
1663
+ if (identity)
1664
+ identityCache.set(session.directory, identity);
1665
+ }
1666
+ if (identity) {
1667
+ recordSessionProjectIdentity(db, session.sessionId, identity);
1668
+ result.backfilledSessions += 1;
1669
+ } else {
1670
+ result.skippedEmptyDirectories += 1;
1620
1671
  }
1621
- recordSessionProjectIdentity(db, session.sessionId, identity);
1622
- result.backfilledSessions += 1;
1623
1672
  }
1624
1673
  }
1625
1674
  }
@@ -1635,11 +1684,11 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1635
1684
  }
1636
1685
  const hasSkippedSessions = result.skippedDeadDirectories > 0 || result.skippedEmptyDirectories > 0;
1637
1686
  if (hasSkippedSessions) {
1638
- if (!markBackfillRetryPending(db, harness, holderId, now())) {
1687
+ if (!markBackfillRetryPending(db, leaseKey, holderId, now())) {
1639
1688
  return finishWithLostLease();
1640
1689
  }
1641
1690
  result.status = "retry_pending";
1642
- } else if (!markBackfillCompleted(db, harness, holderId, now())) {
1691
+ } else if (!markBackfillCompleted(db, leaseKey, holderId, now())) {
1643
1692
  return finishWithLostLease();
1644
1693
  }
1645
1694
  result.durationMs = performance.now() - startedAt;
@@ -1709,7 +1758,7 @@ function countFailures(db) {
1709
1758
  function classifyBackfillError(error) {
1710
1759
  if (error instanceof ProjectIdentityError) {
1711
1760
  return {
1712
- errorClass: error.errorClass === "dubious_ownership" ? "unknown" : error.errorClass,
1761
+ errorClass: error.errorClass === "dubious_ownership" || error.errorClass === "home_project_disabled" || error.errorClass === "git_identity_unavailable" ? "unknown" : error.errorClass,
1713
1762
  errorMessage: error.message
1714
1763
  };
1715
1764
  }
@@ -1948,6 +1997,21 @@ function resolveKnownHistorianContextLimit(historianModelOverride) {
1948
1997
  const limit = getSdkContextLimit(providerID, modelID, undefined, { reservation: "none" });
1949
1998
  return typeof limit === "number" && limit > 0 ? limit : undefined;
1950
1999
  }
2000
+ function resolveHistorianProducerLimits(modelKey) {
2001
+ if (!modelKey?.includes("/"))
2002
+ return {};
2003
+ const [provider, ...parts] = modelKey.split("/");
2004
+ const model = parts.join("/");
2005
+ if (!provider || !model)
2006
+ return {};
2007
+ const input = getSdkInputLimit(provider, model);
2008
+ const window = getSdkWindowGeometry(provider, model)?.derivation.window;
2009
+ const context = window ?? (input === undefined ? resolveKnownHistorianContextLimit(modelKey) : undefined);
2010
+ return {
2011
+ ...context !== undefined ? { context } : {},
2012
+ ...input !== undefined ? { input } : {}
2013
+ };
2014
+ }
1951
2015
  function resolveHistorianContextLimit(historianModelOverride) {
1952
2016
  if (typeof historianModelOverride === "string" && historianModelOverride.includes("/")) {
1953
2017
  return resolveKnownHistorianContextLimit(historianModelOverride) ?? DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
@@ -2313,10 +2377,12 @@ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
2313
2377
  class SmartNoteNetworkError extends Error {
2314
2378
  isSmartNoteNetworkError = true;
2315
2379
  terminal;
2380
+ persistent;
2316
2381
  constructor(message, options = {}) {
2317
2382
  super(message);
2318
2383
  this.name = "SmartNoteNetworkError";
2319
2384
  this.terminal = options.terminal ?? false;
2385
+ this.persistent = options.persistent ?? false;
2320
2386
  }
2321
2387
  }
2322
2388
 
@@ -2438,6 +2504,7 @@ function storeCompiledSmartNoteCheck(db, args) {
2438
2504
  check_failure_count = 0,
2439
2505
  check_network_failure_count = 0,
2440
2506
  check_quarantined_until = NULL,
2507
+ ready_reason = NULL,
2441
2508
  check_next_due_at = ?,
2442
2509
  check_compiled_at = ?,
2443
2510
  check_false_since_at = COALESCE(check_false_since_at, ?),
@@ -2486,15 +2553,17 @@ function markSmartNoteLivenessChecked(db, noteId, now) {
2486
2553
  function markSmartNoteCheckStatus(db, noteId, status, now) {
2487
2554
  db.prepare(`UPDATE notes SET check_status = ?, updated_at = ? WHERE id = ? AND type = 'smart'`).run(status, now, noteId);
2488
2555
  }
2489
- function markSmartNoteCompilationFailure(db, noteId, now, maxFailures) {
2556
+ function markSmartNoteCompilationFailure(db, noteId, now, maxFailures, error, persistent) {
2490
2557
  const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
2491
- const status = failureCount >= maxFailures ? "fallback" : "uncompiled";
2558
+ const status = persistent ? "uncompiled" : failureCount >= maxFailures ? "fallback" : "uncompiled";
2559
+ const nextDueAt = now + (persistent ? 7 * 24 * 60 * 60 * 1000 : backoffMs(failureCount));
2492
2560
  db.prepare(`UPDATE notes
2493
2561
  SET check_failure_count = ?,
2494
2562
  check_status = ?,
2495
2563
  check_next_due_at = ?,
2564
+ ready_reason = ?,
2496
2565
  updated_at = ?
2497
- WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
2566
+ WHERE id = ? AND type = 'smart'`).run(failureCount, status, nextDueAt, persistent ? `Condition can't be checked: ${error}; rewrite it` : null, now, noteId);
2498
2567
  }
2499
2568
  function readFailureCount(db, noteId, column) {
2500
2569
  if (column !== "check_failure_count" && column !== "check_network_failure_count")
@@ -2542,7 +2611,10 @@ function countCompartmentsSince(db, projectPath, since) {
2542
2611
  return row?.cnt ?? 0;
2543
2612
  }
2544
2613
  function countProjectSessionsSince(db, projectPath, since) {
2545
- 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);
2614
+ 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
2615
+ JOIN schema_migrations_meta activity
2616
+ ON activity.key = 'retrospective_activity:' || sp.session_id
2617
+ WHERE sp.project_path = ? AND CAST(activity.value AS INTEGER) > ?`).get(projectPath, since);
2546
2618
  return row?.cnt ?? 0;
2547
2619
  }
2548
2620
  function countMappedMemories(db, projectPath) {
@@ -2651,12 +2723,14 @@ function getDreamTaskBacklog(db, projectPath, task, options = {}) {
2651
2723
  };
2652
2724
  }
2653
2725
  case "retrospective": {
2654
- const pending = countProjectSessionsSince(db, projectPath, options.retrospectiveWatermarkMs ?? null);
2726
+ const watermark = options.retrospectiveWatermarkMs !== undefined ? options.retrospectiveWatermarkMs : getTaskScheduleState(db, projectPath, task)?.retrospectiveWatermarkMs;
2727
+ const pending = countProjectSessionsSince(db, projectPath, watermark ?? null);
2655
2728
  return { pending, total: pending };
2656
2729
  }
2657
2730
  case "maintain-docs": {
2658
2731
  const total = countCompartmentsSince(db, projectPath, 0);
2659
- const pending = countCompartmentsSince(db, projectPath, options.lastRunAt ?? 0);
2732
+ const lastRunAt = options.lastRunAt !== undefined ? options.lastRunAt : getTaskScheduleState(db, projectPath, task)?.lastRunAt;
2733
+ const pending = countCompartmentsSince(db, projectPath, lastRunAt ?? 0);
2660
2734
  return { pending, total };
2661
2735
  }
2662
2736
  case "evaluate-smart-notes": {
@@ -2711,7 +2785,7 @@ function evaluateTaskGate(task, ctx) {
2711
2785
  case "review-user-memories":
2712
2786
  return getUserMemoryCandidates(db).length >= ctx.promotionThreshold;
2713
2787
  case "promote-primers":
2714
- return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? 2);
2788
+ return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"]);
2715
2789
  case "refresh-primers":
2716
2790
  return getActivePrimers(db, project).some((primer) => !primer.answer.trim() || primer.answerRefreshedAt == null || (primer.lastObservedAt ?? 0) > primer.answerRefreshedAt);
2717
2791
  default: {
@@ -2758,6 +2832,11 @@ var USER_FACING_FAILURES = {
2758
2832
  sentence: "Memory maintenance stopped at its hidden agent step limit.",
2759
2833
  action: "This task needs less work per run; changing the model connection will not help."
2760
2834
  },
2835
+ dream_token_budget: {
2836
+ code: "MC-D11",
2837
+ sentence: "Memory maintenance reached its prompt-token budget.",
2838
+ action: "The unfinished items will be retried on the next run."
2839
+ },
2761
2840
  dream_empty_completion: {
2762
2841
  code: "MC-D03",
2763
2842
  sentence: "Memory maintenance received no usable response.",
@@ -2975,6 +3054,7 @@ var DREAM_FAILURE_KEYS = {
2975
3054
  provider_timeout: "dream_provider_timeout",
2976
3055
  provider_error: "dream_provider_error",
2977
3056
  step_limit: "dream_step_limit",
3057
+ token_budget: "dream_token_budget",
2978
3058
  empty_completion: "dream_empty_completion",
2979
3059
  no_models: "dream_no_models",
2980
3060
  child_aborted: "dream_child_aborted",
@@ -3042,7 +3122,7 @@ function openOpenCodeDb() {
3042
3122
 
3043
3123
  // ../plugin/src/features/magic-context/dreamer/task-executor.ts
3044
3124
  import { createHash as createHash10 } from "node:crypto";
3045
- import { existsSync as existsSync6 } from "node:fs";
3125
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
3046
3126
 
3047
3127
  // ../plugin/src/agents/dreamer.ts
3048
3128
  var DREAMER_AGENT = "dreamer";
@@ -3175,7 +3255,7 @@ var READ_TOOLS = ["read", "grep", "glob"];
3175
3255
  var AGENT_TOOLS = {
3176
3256
  [HIDDEN_HISTORIAN_AGENT]: [],
3177
3257
  [HIDDEN_DREAMER_AGENT]: [],
3178
- [HIDDEN_CURATE_AGENT]: ["ctx_memory", "ctx_memory_list"],
3258
+ [HIDDEN_CURATE_AGENT]: ["ctx_memory"],
3179
3259
  [DREAMER_MEMORY_MAPPER_AGENT]: READ_TOOLS,
3180
3260
  [DREAMER_PRIMER_INVESTIGATOR_AGENT]: [...READ_TOOLS, "ctx_search"],
3181
3261
  [DREAMER_RETROSPECTIVE_AGENT]: ["ctx_search"]
@@ -3424,7 +3504,7 @@ async function abortChildRun(client, sessionId) {
3424
3504
  }
3425
3505
  }
3426
3506
  function isNonRetryable(error, externalSignal) {
3427
- if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit)
3507
+ if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit || error instanceof DreamTokenBudgetExceeded)
3428
3508
  return true;
3429
3509
  if (error instanceof Error) {
3430
3510
  if (error.name === "AbortError")
@@ -3450,6 +3530,8 @@ function classifyPromptFailure(error, phase, externalSignal) {
3450
3530
  const message = extractMessage(error);
3451
3531
  if (error instanceof HiddenAgentStepLimit)
3452
3532
  return "step_limit";
3533
+ if (error instanceof DreamTokenBudgetExceeded)
3534
+ return "token_budget";
3453
3535
  if (externalSignal?.aborted || message === "prompt aborted by external signal") {
3454
3536
  return "child_aborted";
3455
3537
  }
@@ -3700,256 +3782,6 @@ function normalizeSDKResponse(response, fallback, options) {
3700
3782
  }
3701
3783
  return fallback;
3702
3784
  }
3703
- // ../plugin/src/shared/prompt-async-transport.ts
3704
- var DEFAULT_POLL_INTERVAL_MS = 1000;
3705
- var DEFAULT_START_GRACE_MS = 30000;
3706
- var MESSAGE_WINDOW = 20;
3707
- function sessionApi(client) {
3708
- return client?.session;
3709
- }
3710
- function supportsPromptAsync(client) {
3711
- const session = sessionApi(client);
3712
- return typeof session?.promptAsync === "function" && typeof session?.status === "function" && typeof session?.messages === "function";
3713
- }
3714
- function createPromptAsyncTransport(client, childSessionId, options = {}) {
3715
- if (!supportsPromptAsync(client))
3716
- return;
3717
- return Object.assign((request) => promptAsyncAndWaitForIdle(client, request, options), { childSessionId });
3718
- }
3719
- function infoOf(message) {
3720
- const info = message?.info;
3721
- return info && typeof info === "object" ? info : {};
3722
- }
3723
- function messageId(message) {
3724
- const id = infoOf(message).id;
3725
- return typeof id === "string" ? id : null;
3726
- }
3727
- function isSettledAssistant(message) {
3728
- const info = infoOf(message);
3729
- if (info.role !== "assistant")
3730
- return false;
3731
- return info.time?.completed != null || info.error != null || info.finish != null;
3732
- }
3733
- function unwrapData(response) {
3734
- if (response && typeof response === "object" && "data" in response) {
3735
- return response.data;
3736
- }
3737
- return response;
3738
- }
3739
- function describeRejection(error) {
3740
- if (error instanceof Error)
3741
- return error.message;
3742
- try {
3743
- return JSON.stringify(error);
3744
- } catch {
3745
- return String(error);
3746
- }
3747
- }
3748
- function abortError(signal) {
3749
- return signal.reason ?? new Error("prompt_async wait aborted");
3750
- }
3751
- function sleep(ms, signal) {
3752
- return new Promise((resolve, reject) => {
3753
- if (signal?.aborted) {
3754
- reject(abortError(signal));
3755
- return;
3756
- }
3757
- const onAbort = () => {
3758
- clearTimeout(timer);
3759
- reject(abortError(signal));
3760
- };
3761
- const timer = setTimeout(() => {
3762
- signal?.removeEventListener("abort", onAbort);
3763
- resolve();
3764
- }, ms);
3765
- signal?.addEventListener("abort", onAbort, { once: true });
3766
- });
3767
- }
3768
- async function readMessages(session, sessionId, directory, signal) {
3769
- const response = await session.messages?.({
3770
- path: { id: sessionId },
3771
- query: { ...directory ? { directory } : {}, limit: MESSAGE_WINDOW },
3772
- ...signal ? { signal } : {}
3773
- });
3774
- const data = unwrapData(response);
3775
- return Array.isArray(data) ? data : [];
3776
- }
3777
- async function readStatus(session, sessionId, directory, signal) {
3778
- try {
3779
- const response = await session.status?.({
3780
- ...directory ? { query: { directory } } : {},
3781
- ...signal ? { signal } : {}
3782
- });
3783
- if (response && typeof response === "object" && "error" in response) {
3784
- if (response.error)
3785
- return null;
3786
- }
3787
- const data = unwrapData(response);
3788
- if (!data || typeof data !== "object")
3789
- return null;
3790
- const entry = data[sessionId];
3791
- const type = entry?.type;
3792
- return typeof type === "string" ? type : "idle";
3793
- } catch (error) {
3794
- if (signal?.aborted)
3795
- throw error;
3796
- return null;
3797
- }
3798
- }
3799
- async function promptAsyncAndWaitForIdle(client, request, options = {}) {
3800
- const session = sessionApi(client);
3801
- if (!session || !supportsPromptAsync(client)) {
3802
- throw new Error("prompt_async transport is unavailable on this client");
3803
- }
3804
- const sessionId = request.path.id;
3805
- const directory = request.query?.directory;
3806
- const dir = typeof directory === "string" ? directory : undefined;
3807
- const signal = request.signal;
3808
- const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
3809
- const startGraceMs = options.startGraceMs ?? DEFAULT_START_GRACE_MS;
3810
- const baseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((id) => id !== null));
3811
- const sent = await session.promptAsync?.(request);
3812
- if (sent && typeof sent === "object" && "error" in sent) {
3813
- const rejection = sent.error;
3814
- if (rejection)
3815
- throw new Error(`prompt_async was rejected: ${describeRejection(rejection)}`);
3816
- }
3817
- const sentAt = Date.now();
3818
- let sawBusy = false;
3819
- for (;; ) {
3820
- await sleep(pollIntervalMs, signal);
3821
- const status = await readStatus(session, sessionId, dir, signal);
3822
- if (status === "busy" || status === "retry") {
3823
- sawBusy = true;
3824
- continue;
3825
- }
3826
- if (status === null)
3827
- continue;
3828
- const messages = await readMessages(session, sessionId, dir, signal);
3829
- const last = messages.at(-1);
3830
- const lastId = messageId(last);
3831
- if (last && lastId !== null && !baseline.has(lastId) && isSettledAssistant(last))
3832
- return;
3833
- const fresh = messages.some((message) => {
3834
- const id = messageId(message);
3835
- return id !== null && !baseline.has(id);
3836
- });
3837
- if (sawBusy && fresh)
3838
- return;
3839
- if (Date.now() - sentAt >= startGraceMs) {
3840
- if (fresh)
3841
- return;
3842
- throw new Error(`prompt_async did not start a run in child session ${sessionId} within ${startGraceMs}ms`);
3843
- }
3844
- }
3845
- }
3846
- // ../plugin/src/shared/summarize-child-stderr.ts
3847
- var MAX_CHILD_STDERR_SUMMARY_LENGTH = 500;
3848
- var CHILD_ERROR_LINE_PATTERN = /^(?:[A-Za-z]*Error|SqliteError|Error)\b[^\n]*/;
3849
- var STACK_FRAME_PATTERN = /^\s*at\b/;
3850
- function summarizeChildStderr(stderr) {
3851
- const lines = stderr.split(/\r?\n/);
3852
- const errorLineIndex = lines.findIndex((line) => CHILD_ERROR_LINE_PATTERN.test(line));
3853
- if (errorLineIndex < 0) {
3854
- return stderr.length > MAX_CHILD_STDERR_SUMMARY_LENGTH ? stderr.slice(-MAX_CHILD_STDERR_SUMMARY_LENGTH) : stderr;
3855
- }
3856
- const excerptLines = [lines[errorLineIndex]];
3857
- let stackFrames = 0;
3858
- for (let index = errorLineIndex + 1;index < lines.length && stackFrames < 2; index += 1) {
3859
- if (!STACK_FRAME_PATTERN.test(lines[index]))
3860
- continue;
3861
- excerptLines.push(lines[index]);
3862
- stackFrames += 1;
3863
- }
3864
- return excerptLines.join(`
3865
- `).slice(0, MAX_CHILD_STDERR_SUMMARY_LENGTH);
3866
- }
3867
- // ../plugin/src/shared/child-session-teardown.ts
3868
- async function teardownChildSession(args) {
3869
- const { client, sessionId, sessionDirectory, promptSettled, context, log } = args;
3870
- if (!sessionId)
3871
- return;
3872
- const request = {
3873
- path: { id: sessionId },
3874
- ...sessionDirectory ? { query: { directory: sessionDirectory } } : {}
3875
- };
3876
- if (promptSettled && !shouldKeepSubagents()) {
3877
- await client.session.delete(request).catch((error) => {
3878
- log(`${context}: session cleanup failed: ${String(error)}`);
3879
- });
3880
- return;
3881
- }
3882
- if (!promptSettled) {
3883
- const archiveSession = client.session;
3884
- await archiveSession.update?.({
3885
- ...request,
3886
- body: { time: { archived: Date.now() } }
3887
- }).catch(() => {});
3888
- log(`${context}: prompt unsettled — session ${sessionId} left to the age-gated sweep`);
3889
- }
3890
- }
3891
-
3892
- // ../plugin/src/features/magic-context/compartment-events.ts
3893
- function insertCompartmentEvents(db, sessionId, events, compartmentIds) {
3894
- if (events.length === 0)
3895
- return;
3896
- const now = Date.now();
3897
- const harness = getHarness();
3898
- const stmt = db.prepare("INSERT INTO compartment_events (session_id, compartment_id, kind, at_compartment, fields_json, created_at, harness) VALUES (?, ?, ?, ?, ?, ?, ?)");
3899
- for (const ev of events) {
3900
- const idx = ev.atCompartment != null && ev.atCompartment >= 1 ? ev.atCompartment - 1 : -1;
3901
- const compartmentId = idx >= 0 && idx < compartmentIds.length ? compartmentIds[idx] : null;
3902
- stmt.run(sessionId, compartmentId, ev.kind, ev.atCompartment, JSON.stringify(ev.fields ?? {}), now, harness);
3903
- }
3904
- }
3905
- function getCompartmentEvents(db, sessionId) {
3906
- const rows = db.prepare("SELECT id, session_id, compartment_id, kind, at_compartment, fields_json, created_at FROM compartment_events WHERE session_id = ? ORDER BY id DESC").all(sessionId);
3907
- return rows.map((r) => ({
3908
- id: r.id,
3909
- sessionId: r.session_id,
3910
- compartmentId: r.compartment_id,
3911
- kind: r.kind,
3912
- atCompartment: r.at_compartment,
3913
- fields: parseFields(r.fields_json),
3914
- createdAt: r.created_at
3915
- }));
3916
- }
3917
- function parseFields(json) {
3918
- try {
3919
- const parsed = JSON.parse(json);
3920
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3921
- const out = {};
3922
- for (const [k, v] of Object.entries(parsed)) {
3923
- if (typeof v === "string")
3924
- out[k] = v;
3925
- }
3926
- return out;
3927
- }
3928
- } catch {}
3929
- return {};
3930
- }
3931
-
3932
- // ../plugin/src/features/magic-context/mural/compress-cues.ts
3933
- import { createHash as createHash4 } from "node:crypto";
3934
-
3935
- // ../plugin/src/hooks/magic-context/compartment-runner-historian.ts
3936
- import { mkdirSync as mkdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
3937
- import { join as join2 } from "node:path";
3938
-
3939
- // ../plugin/src/agents/historian.ts
3940
- var HISTORIAN_AGENT = "historian";
3941
- var HISTORIAN_RECOMP_AGENT = "historian-recomp";
3942
- var HISTORIAN_EDITOR_AGENT = "historian-editor";
3943
-
3944
- // ../plugin/src/config/live-child-output-cap.ts
3945
- var historianCaps = new BoundedSessionMap(4096);
3946
- function rememberHistorianOutputCap(sessionId, cap) {
3947
- historianCaps.set(sessionId, { value: cap });
3948
- }
3949
- function forgetHistorianOutputCap(sessionId) {
3950
- historianCaps.delete(sessionId);
3951
- }
3952
-
3953
3785
  // ../plugin/src/features/magic-context/subagent-token-capture.ts
3954
3786
  function asNumber(value) {
3955
3787
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -4055,6 +3887,348 @@ function recordChildInvocation(input) {
4055
3887
  }
4056
3888
  }
4057
3889
 
3890
+ // ../plugin/src/shared/prompt-async-transport.ts
3891
+ var DEFAULT_POLL_INTERVAL_MS = 1000;
3892
+ var DEFAULT_START_GRACE_MS = 30000;
3893
+ var MESSAGE_WINDOW = 100;
3894
+ function sessionApi(client) {
3895
+ return client?.session;
3896
+ }
3897
+ function supportsPromptAsync(client) {
3898
+ const session = sessionApi(client);
3899
+ return typeof session?.promptAsync === "function" && typeof session?.status === "function" && typeof session?.messages === "function";
3900
+ }
3901
+ function createPromptAsyncTransport(client, childSessionId, options = {}) {
3902
+ if (!supportsPromptAsync(client))
3903
+ return;
3904
+ const budgetGuard = options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined;
3905
+ return Object.assign((request) => promptAsyncAndWaitForIdle(client, request, { ...options, budgetGuard }), { childSessionId });
3906
+ }
3907
+ function infoOf(message) {
3908
+ const info = message?.info;
3909
+ return info && typeof info === "object" ? info : {};
3910
+ }
3911
+ function messageId(message) {
3912
+ const id = infoOf(message).id;
3913
+ return typeof id === "string" ? id : null;
3914
+ }
3915
+ function isSettledAssistant(message) {
3916
+ const info = infoOf(message);
3917
+ if (info.role !== "assistant")
3918
+ return false;
3919
+ return info.time?.completed != null || info.error != null || info.finish != null;
3920
+ }
3921
+ function isTerminalAssistant(message) {
3922
+ const info = infoOf(message);
3923
+ 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"));
3924
+ }
3925
+ function unwrapData(response) {
3926
+ if (response && typeof response === "object" && "data" in response) {
3927
+ return response.data;
3928
+ }
3929
+ return response;
3930
+ }
3931
+ function describeRejection(error) {
3932
+ if (error instanceof Error)
3933
+ return error.message;
3934
+ try {
3935
+ return JSON.stringify(error);
3936
+ } catch {
3937
+ return String(error);
3938
+ }
3939
+ }
3940
+ function abortError(signal) {
3941
+ return signal.reason ?? new Error("prompt_async wait aborted");
3942
+ }
3943
+ function sleep(ms, signal) {
3944
+ return new Promise((resolve, reject) => {
3945
+ if (signal?.aborted) {
3946
+ reject(abortError(signal));
3947
+ return;
3948
+ }
3949
+ const onAbort = () => {
3950
+ clearTimeout(timer);
3951
+ reject(abortError(signal));
3952
+ };
3953
+ const timer = setTimeout(() => {
3954
+ signal?.removeEventListener("abort", onAbort);
3955
+ resolve();
3956
+ }, ms);
3957
+ signal?.addEventListener("abort", onAbort, { once: true });
3958
+ });
3959
+ }
3960
+ async function readMessages(session, sessionId, directory, signal) {
3961
+ const response = await session.messages?.({
3962
+ path: { id: sessionId },
3963
+ query: { ...directory ? { directory } : {}, limit: MESSAGE_WINDOW },
3964
+ ...signal ? { signal } : {}
3965
+ });
3966
+ const data = unwrapData(response);
3967
+ return Array.isArray(data) ? data : [];
3968
+ }
3969
+ async function readStatus(session, sessionId, directory, signal) {
3970
+ try {
3971
+ const response = await session.status?.({
3972
+ ...directory ? { query: { directory } } : {},
3973
+ ...signal ? { signal } : {}
3974
+ });
3975
+ if (response && typeof response === "object" && "error" in response) {
3976
+ if (response.error)
3977
+ return null;
3978
+ }
3979
+ const data = unwrapData(response);
3980
+ if (!data || typeof data !== "object")
3981
+ return null;
3982
+ const entry = data[sessionId];
3983
+ const type = entry?.type;
3984
+ return typeof type === "string" ? type : "idle";
3985
+ } catch (error) {
3986
+ if (signal?.aborted)
3987
+ throw error;
3988
+ return null;
3989
+ }
3990
+ }
3991
+ async function promptAsyncAndWaitForIdle(client, request, options = {}) {
3992
+ const session = sessionApi(client);
3993
+ if (!session || !supportsPromptAsync(client)) {
3994
+ throw new Error("prompt_async transport is unavailable on this client");
3995
+ }
3996
+ const sessionId = request.path.id;
3997
+ const directory = request.query?.directory;
3998
+ const dir = typeof directory === "string" ? directory : undefined;
3999
+ const signal = request.signal;
4000
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
4001
+ const startGraceMs = options.startGraceMs ?? DEFAULT_START_GRACE_MS;
4002
+ const budget = options.budgetGuard ?? (options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined);
4003
+ if (budget?.snapshot().finalizeFired)
4004
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4005
+ const seenUsage = new Set;
4006
+ let finalizing = false;
4007
+ let completionBaseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((id) => id !== null));
4008
+ const initialBaseline = new Set(completionBaseline);
4009
+ const sent = await session.promptAsync?.(request);
4010
+ if (sent && typeof sent === "object" && "error" in sent) {
4011
+ const rejection = sent.error;
4012
+ if (rejection)
4013
+ throw new Error(`prompt_async was rejected: ${describeRejection(rejection)}`);
4014
+ }
4015
+ let sentAt = Date.now();
4016
+ let sawBusy = false;
4017
+ try {
4018
+ for (;; ) {
4019
+ await sleep(pollIntervalMs, signal);
4020
+ const status = await readStatus(session, sessionId, dir, signal);
4021
+ const messages = await readMessages(session, sessionId, dir, signal);
4022
+ if (budget) {
4023
+ for (const message of messages) {
4024
+ const id = messageId(message);
4025
+ if (!id || seenUsage.has(id) || infoOf(message).role !== "assistant")
4026
+ continue;
4027
+ if (initialBaseline.has(id))
4028
+ continue;
4029
+ const tokens = sumTokensFromChildMessages([message]);
4030
+ if (tokens.input + tokens.cacheRead + tokens.cacheWrite === 0)
4031
+ continue;
4032
+ seenUsage.add(id);
4033
+ const decision = budget.charge(tokens.input, tokens.cacheRead, tokens.cacheWrite, isTerminalAssistant(message));
4034
+ options.onBudgetUpdate?.({ ...budget.snapshot(), sessionId });
4035
+ if (decision === "stop") {
4036
+ await session.abort?.({ path: { id: sessionId } });
4037
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4038
+ }
4039
+ if (decision === "finalize") {
4040
+ finalizing = true;
4041
+ registerBudgetFinalizeChild(sessionId, budget);
4042
+ if (!session.abort)
4043
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4044
+ await session.abort({ path: { id: sessionId } });
4045
+ const abortDeadline = Date.now() + startGraceMs;
4046
+ while (Date.now() < abortDeadline) {
4047
+ const state = await readStatus(session, sessionId, dir, signal);
4048
+ if (state === "idle")
4049
+ break;
4050
+ await sleep(pollIntervalMs, signal);
4051
+ }
4052
+ if (await readStatus(session, sessionId, dir, signal) !== "idle") {
4053
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4054
+ }
4055
+ completionBaseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((value) => value !== null));
4056
+ const finalizeResponse = await session.promptAsync?.({
4057
+ ...request,
4058
+ body: {
4059
+ ...request.body,
4060
+ parts: [{ type: "text", text: TOKEN_BUDGET_FINALIZE_MESSAGE }]
4061
+ }
4062
+ });
4063
+ if (finalizeResponse && typeof finalizeResponse === "object" && "error" in finalizeResponse && finalizeResponse.error) {
4064
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4065
+ }
4066
+ sentAt = Date.now();
4067
+ sawBusy = false;
4068
+ break;
4069
+ }
4070
+ }
4071
+ if (budget.snapshot().hardStopped) {
4072
+ await session.abort?.({ path: { id: sessionId } });
4073
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4074
+ }
4075
+ }
4076
+ if (status === "busy" || status === "retry") {
4077
+ sawBusy = true;
4078
+ continue;
4079
+ }
4080
+ if (status === null)
4081
+ continue;
4082
+ const current = finalizing ? await readMessages(session, sessionId, dir, signal) : messages;
4083
+ const last = current.at(-1);
4084
+ const lastId = messageId(last);
4085
+ if (last && lastId !== null && !completionBaseline.has(lastId) && isSettledAssistant(last))
4086
+ return;
4087
+ const fresh = current.some((message) => {
4088
+ const id = messageId(message);
4089
+ return id !== null && !completionBaseline.has(id);
4090
+ });
4091
+ if (sawBusy && fresh)
4092
+ return;
4093
+ if (Date.now() - sentAt >= startGraceMs) {
4094
+ if (fresh)
4095
+ return;
4096
+ throw new Error(`prompt_async did not start a run in child session ${sessionId} within ${startGraceMs}ms`);
4097
+ }
4098
+ }
4099
+ } finally {
4100
+ if (budget)
4101
+ releaseBudgetFinalizeChild(sessionId);
4102
+ }
4103
+ }
4104
+ // ../plugin/src/shared/summarize-child-stderr.ts
4105
+ var MAX_CHILD_STDERR_SUMMARY_LENGTH = 500;
4106
+ var CHILD_ERROR_LINE_PATTERN = /^(?:[A-Za-z]*Error|SqliteError|Error)\b[^\n]*/;
4107
+ var STACK_FRAME_PATTERN = /^\s*at\b/;
4108
+ function summarizeChildStderr(stderr) {
4109
+ const lines = stderr.split(/\r?\n/);
4110
+ const errorLineIndex = lines.findIndex((line) => CHILD_ERROR_LINE_PATTERN.test(line));
4111
+ if (errorLineIndex < 0) {
4112
+ return stderr.length > MAX_CHILD_STDERR_SUMMARY_LENGTH ? stderr.slice(-MAX_CHILD_STDERR_SUMMARY_LENGTH) : stderr;
4113
+ }
4114
+ const excerptLines = [lines[errorLineIndex]];
4115
+ let stackFrames = 0;
4116
+ for (let index = errorLineIndex + 1;index < lines.length && stackFrames < 2; index += 1) {
4117
+ if (!STACK_FRAME_PATTERN.test(lines[index]))
4118
+ continue;
4119
+ excerptLines.push(lines[index]);
4120
+ stackFrames += 1;
4121
+ }
4122
+ return excerptLines.join(`
4123
+ `).slice(0, MAX_CHILD_STDERR_SUMMARY_LENGTH);
4124
+ }
4125
+ // ../plugin/src/shared/child-session-teardown.ts
4126
+ async function teardownChildSession(args) {
4127
+ const { client, sessionId, sessionDirectory, promptSettled, context, log } = args;
4128
+ if (!sessionId)
4129
+ return;
4130
+ const request = {
4131
+ path: { id: sessionId },
4132
+ ...sessionDirectory ? { query: { directory: sessionDirectory } } : {}
4133
+ };
4134
+ if (promptSettled && !shouldKeepSubagents()) {
4135
+ await client.session.delete(request).catch((error) => {
4136
+ log(`${context}: session cleanup failed: ${String(error)}`);
4137
+ });
4138
+ return;
4139
+ }
4140
+ if (!promptSettled) {
4141
+ const archiveSession = client.session;
4142
+ await archiveSession.update?.({
4143
+ ...request,
4144
+ body: { time: { archived: Date.now() } }
4145
+ }).catch(() => {});
4146
+ log(`${context}: prompt unsettled — session ${sessionId} left to the age-gated sweep`);
4147
+ }
4148
+ }
4149
+
4150
+ // ../plugin/src/features/magic-context/compartment-events.ts
4151
+ function insertCompartmentEvents(db, sessionId, events, compartmentIds) {
4152
+ if (events.length === 0)
4153
+ return;
4154
+ const now = Date.now();
4155
+ const harness = getHarness();
4156
+ const stmt = db.prepare("INSERT INTO compartment_events (session_id, compartment_id, kind, at_compartment, fields_json, created_at, harness) VALUES (?, ?, ?, ?, ?, ?, ?)");
4157
+ for (const ev of events) {
4158
+ const idx = ev.atCompartment != null && ev.atCompartment >= 1 ? ev.atCompartment - 1 : -1;
4159
+ const compartmentId = idx >= 0 && idx < compartmentIds.length ? compartmentIds[idx] : null;
4160
+ stmt.run(sessionId, compartmentId, ev.kind, ev.atCompartment, JSON.stringify(ev.fields ?? {}), now, harness);
4161
+ }
4162
+ }
4163
+ function getCompartmentEvents(db, sessionId) {
4164
+ const rows = db.prepare("SELECT id, session_id, compartment_id, kind, at_compartment, fields_json, created_at FROM compartment_events WHERE session_id = ? ORDER BY id DESC").all(sessionId);
4165
+ return rows.map((r) => ({
4166
+ id: r.id,
4167
+ sessionId: r.session_id,
4168
+ compartmentId: r.compartment_id,
4169
+ kind: r.kind,
4170
+ atCompartment: r.at_compartment,
4171
+ fields: parseFields(r.fields_json),
4172
+ createdAt: r.created_at
4173
+ }));
4174
+ }
4175
+ function parseFields(json) {
4176
+ try {
4177
+ const parsed = JSON.parse(json);
4178
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4179
+ const out = {};
4180
+ for (const [k, v] of Object.entries(parsed)) {
4181
+ if (typeof v === "string")
4182
+ out[k] = v;
4183
+ }
4184
+ return out;
4185
+ }
4186
+ } catch {}
4187
+ return {};
4188
+ }
4189
+
4190
+ // ../plugin/src/features/magic-context/mural/compress-cues.ts
4191
+ import { createHash as createHash4 } from "node:crypto";
4192
+
4193
+ // ../plugin/src/hooks/magic-context/compartment-runner-historian.ts
4194
+ import { mkdirSync as mkdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
4195
+ import { join as join2 } from "node:path";
4196
+
4197
+ // ../plugin/src/agents/historian.ts
4198
+ var HISTORIAN_AGENT = "historian";
4199
+ var HISTORIAN_RECOMP_AGENT = "historian-recomp";
4200
+ var HISTORIAN_EDITOR_AGENT = "historian-editor";
4201
+
4202
+ // ../plugin/src/config/live-child-output-cap.ts
4203
+ var historianCaps = new BoundedSessionMap(4096);
4204
+ function rememberHistorianOutputCap(sessionId, cap) {
4205
+ historianCaps.set(sessionId, { value: cap });
4206
+ }
4207
+ function forgetHistorianOutputCap(sessionId) {
4208
+ historianCaps.delete(sessionId);
4209
+ }
4210
+
4211
+ // ../plugin/src/shared/run-token-log.ts
4212
+ function count(value) {
4213
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
4214
+ }
4215
+ function runTokenLog(tokens, maxTokens, finish) {
4216
+ const usage = tokens && typeof tokens === "object" ? tokens : {};
4217
+ const cache = usage.cache && typeof usage.cache === "object" ? usage.cache : {};
4218
+ return {
4219
+ input: count(usage.input ?? usage.input_tokens),
4220
+ output: count(usage.output ?? usage.output_tokens),
4221
+ reasoning: count(usage.reasoning ?? usage.reasoning_tokens),
4222
+ cache_read: count(usage.cacheRead ?? usage.cache_read ?? usage.cached_input_tokens ?? cache.read),
4223
+ cache_write: count(usage.cacheWrite ?? usage.cache_write ?? usage.cache_write_tokens ?? cache.write),
4224
+ max_tokens: count(maxTokens),
4225
+ finish_reason: typeof finish === "string" ? finish : null
4226
+ };
4227
+ }
4228
+ function formatRunTokenLog(tokens) {
4229
+ return `tokens=${JSON.stringify(tokens)}`;
4230
+ }
4231
+
4058
4232
  // ../plugin/src/hooks/magic-context/historian-prompt.generated.ts
4059
4233
  var COMPARTMENT_AGENT_SYSTEM_PROMPT = `# Historian
4060
4234
 
@@ -5217,11 +5391,14 @@ function clampProducerAtomChars(text, maxChars, marker) {
5217
5391
  const tailChars = Math.floor(keptChars / 2);
5218
5392
  return `${text.slice(0, headChars)}${marker}${text.slice(text.length - tailChars)}`;
5219
5393
  }
5220
- function producerInputTokenLimit(contextLimitTokens, maxOutputTokens) {
5221
- if (typeof contextLimitTokens !== "number" || !Number.isFinite(contextLimitTokens) || contextLimitTokens <= 0 || !Number.isFinite(maxOutputTokens) || maxOutputTokens < 0) {
5394
+ function producerInputTokenLimit(contextLimitTokens, maxOutputTokens, inputLimitTokens) {
5395
+ if (!Number.isFinite(maxOutputTokens) || maxOutputTokens < 0)
5222
5396
  return;
5223
- }
5224
- const usableInputTokens = Math.floor(contextLimitTokens - maxOutputTokens);
5397
+ const shared = typeof contextLimitTokens === "number" && Number.isFinite(contextLimitTokens) && contextLimitTokens > 0 ? contextLimitTokens - maxOutputTokens : undefined;
5398
+ const input = typeof inputLimitTokens === "number" && Number.isFinite(inputLimitTokens) && inputLimitTokens > 0 ? inputLimitTokens : undefined;
5399
+ if (shared === undefined && input === undefined)
5400
+ return;
5401
+ const usableInputTokens = Math.floor(Math.min(shared ?? Infinity, input ?? Infinity));
5225
5402
  if (usableInputTokens <= 0)
5226
5403
  return;
5227
5404
  const limit = Math.floor(usableInputTokens * (1 - PRODUCER_WINDOW_REFUSAL_MARGIN));
@@ -5233,15 +5410,15 @@ function historianProducerReserve(window, configuredOutput, catalogOutput) {
5233
5410
  return window === undefined ? 0 : catalogOutputReserve(window, catalogOutput);
5234
5411
  }
5235
5412
  function producerWindowFailureReason(input) {
5236
- const { producerSourceTokens, contextLimitTokens, maxOutputTokens } = input;
5237
- const producerInputLimitTokens = producerInputTokenLimit(contextLimitTokens, maxOutputTokens);
5238
- if (producerInputLimitTokens === undefined || typeof contextLimitTokens !== "number" || !Number.isFinite(producerSourceTokens) || producerSourceTokens <= 0) {
5413
+ const { producerSourceTokens, contextLimitTokens, inputLimitTokens, maxOutputTokens } = input;
5414
+ const producerInputLimitTokens = producerInputTokenLimit(contextLimitTokens, maxOutputTokens, inputLimitTokens);
5415
+ if (producerInputLimitTokens === undefined || !Number.isFinite(producerSourceTokens) || producerSourceTokens <= 0) {
5239
5416
  return null;
5240
5417
  }
5241
- const usableInputTokens = Math.max(0, Math.floor(contextLimitTokens - maxOutputTokens));
5418
+ const usableInputTokens = Math.floor(Math.min(contextLimitTokens === undefined ? Infinity : Math.max(0, contextLimitTokens - maxOutputTokens), inputLimitTokens ?? Infinity));
5242
5419
  if (producerSourceTokens <= producerInputLimitTokens)
5243
5420
  return null;
5244
- return `producer_source_exceeds_window producer_source_tokens=${Math.round(producerSourceTokens)} usable_input_tokens=${usableInputTokens} producer_input_limit_tokens=${producerInputLimitTokens} context_limit_tokens=${Math.round(contextLimitTokens)} max_output_tokens=${Math.round(maxOutputTokens)} estimator_margin=${PRODUCER_WINDOW_REFUSAL_MARGIN}`;
5421
+ return `producer_source_exceeds_window producer_source_tokens=${Math.round(producerSourceTokens)} usable_input_tokens=${usableInputTokens} producer_input_limit_tokens=${producerInputLimitTokens} context_limit_tokens=${contextLimitTokens === undefined ? "unknown" : Math.round(contextLimitTokens)} max_output_tokens=${Math.round(maxOutputTokens)} estimator_margin=${PRODUCER_WINDOW_REFUSAL_MARGIN}`;
5245
5422
  }
5246
5423
  function splitMarkerPair() {
5247
5424
  return `
@@ -5250,7 +5427,7 @@ ${HISTORIAN_TRUNCATION_MARKER}
5250
5427
  `;
5251
5428
  }
5252
5429
  function fitAtomicHistorianSourceToProducerWindow(args) {
5253
- const producerInputLimitTokens = producerInputTokenLimit(args.contextLimitTokens, args.maxOutputTokens);
5430
+ const producerInputLimitTokens = producerInputTokenLimit(args.contextLimitTokens, args.maxOutputTokens, args.inputLimitTokens);
5254
5431
  const originalTokens = estimateTokens(args.text);
5255
5432
  if (producerInputLimitTokens === undefined || originalTokens < producerInputLimitTokens || producerInputLimitTokens <= 0) {
5256
5433
  return { text: args.text, producerInputLimitTokens, removedTokens: 0 };
@@ -5284,7 +5461,7 @@ function fitAtomicHistorianSourceToProducerWindow(args) {
5284
5461
  };
5285
5462
  }
5286
5463
  function producerPromptFailureReason(input) {
5287
- const limit = producerInputTokenLimit(input.contextLimitTokens, input.maxOutputTokens);
5464
+ const limit = producerInputTokenLimit(input.contextLimitTokens, input.maxOutputTokens, input.inputLimitTokens);
5288
5465
  const tokens = providerMass({ prose: input.sourceLocal, system: input.systemLocal, tools: input.toolsLocal }, calibrationForModelKey(input.modelKey), true);
5289
5466
  if (limit === undefined)
5290
5467
  return null;
@@ -5395,12 +5572,15 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5395
5572
  throw new Error(`Historian host recorded an assistant error${finish}: ${describeAssistantFailure(assistantFailure.error)}`);
5396
5573
  }
5397
5574
  const text = extractLatestAssistantText(messages);
5575
+ const latest = Array.isArray(messages) ? messages.filter((message) => isRecord(message) && isRecord(message.info) && message.info.role === "assistant").sort((left, right) => historianMessageCreatedAt(right) - historianMessageCreatedAt(left))[0] : undefined;
5576
+ const info = latest && isRecord(latest.info) ? latest.info : {};
5398
5577
  return {
5399
5578
  messages,
5400
5579
  text,
5401
5580
  reasoning: text ? null : extractLatestHistorianReasoning(messages),
5402
5581
  lengthCapped: hasLengthCappedOutput(messages),
5403
- usage: sumTokensFromChildMessages(messages)
5582
+ usage: sumTokensFromChildMessages(messages),
5583
+ tokenLog: runTokenLog(info.tokens, undefined, info.finish ?? info.finish_reason ?? info.finishReason)
5404
5584
  };
5405
5585
  },
5406
5586
  async close(handle, settlement) {
@@ -5419,8 +5599,8 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5419
5599
  }
5420
5600
  };
5421
5601
  }
5422
- function historianReasoningBudgetDiagnostic(outputTokens) {
5423
- return `historian ran out of output budget while reasoning (length-capped at ${outputTokens} tokens, no text) — set historian.maxTokens or route historian.model to a low-reasoning lane/variant`;
5602
+ function historianReasoningBudgetDiagnostic(outputTokens, tokens) {
5603
+ return `historian ran out of output budget while reasoning (length-capped at ${outputTokens} tokens, no text${tokens ? `; ${formatRunTokenLog(tokens)}` : ""}) — set historian.maxTokens or route historian.model to a low-reasoning lane/variant`;
5424
5604
  }
5425
5605
  async function runValidatedHistorianPass(args) {
5426
5606
  const firstRun = await runHistorianPrompt({
@@ -5609,8 +5789,10 @@ async function runHistorianPrompt(args) {
5609
5789
  const modelKey = selected ? `${selected.providerID}/${selected.modelID}` : undefined;
5610
5790
  const system = withContentLanguageDirective(agentId === HISTORIAN_EDITOR_AGENT ? HISTORIAN_EDITOR_SYSTEM_PROMPT : COMPARTMENT_AGENT_SYSTEM_PROMPT, args.language);
5611
5791
  const contextLimitTokens = selected ? getSdkContextLimit(selected.providerID, selected.modelID, undefined, { reservation: "none" }) : undefined;
5612
- const reserve = historianProducerReserve(contextLimitTokens, args.maxOutputTokens, selected ? getSdkOutputLimit(selected.providerID, selected.modelID) : undefined);
5613
- if (contextLimitTokens !== undefined && producerInputTokenLimit(contextLimitTokens, reserve) === undefined && modelKey && !unknownProducerWindows.has(modelKey)) {
5792
+ const producerLimits = selected ? resolveHistorianProducerLimits(modelKey) : {};
5793
+ const producerContext = producerLimits.context ?? (producerLimits.input === undefined ? contextLimitTokens : undefined);
5794
+ const reserve = historianProducerReserve(producerContext, args.maxOutputTokens, selected ? getSdkOutputLimit(selected.providerID, selected.modelID) : undefined);
5795
+ if (contextLimitTokens !== undefined && producerInputTokenLimit(producerContext, reserve, producerLimits.input) === undefined && modelKey && !unknownProducerWindows.has(modelKey)) {
5614
5796
  unknownProducerWindows.add(modelKey);
5615
5797
  sessionLog(parentSessionId, `producer window inconsistent for ${modelKey}: window=${contextLimitTokens} reserve=${reserve}; sending unguarded`);
5616
5798
  }
@@ -5619,7 +5801,8 @@ async function runHistorianPrompt(args) {
5619
5801
  systemLocal: estimateTokens(system),
5620
5802
  toolsLocal: 0,
5621
5803
  modelKey,
5622
- contextLimitTokens,
5804
+ contextLimitTokens: producerContext,
5805
+ inputLimitTokens: producerLimits.input,
5623
5806
  maxOutputTokens: reserve
5624
5807
  });
5625
5808
  if (failure)
@@ -5653,9 +5836,15 @@ async function runHistorianPrompt(args) {
5653
5836
  }
5654
5837
  completion = await executor.collect(handle, 50);
5655
5838
  const lengthCapped = completion.lengthCapped;
5839
+ const tokens = {
5840
+ ...runTokenLog(undefined, args.maxOutputTokens),
5841
+ ...completion.tokenLog,
5842
+ max_tokens: args.maxOutputTokens ?? null
5843
+ };
5844
+ sessionLog(parentSessionId, `historian response_chars=${(completion.text ?? completion.reasoning ?? "").length} ${formatRunTokenLog(tokens)}`);
5656
5845
  const textResult = completion.text;
5657
5846
  const reasoningResult = textResult ? null : completion.reasoning;
5658
- const emptyError = !textResult && reasoningResult && lengthCapped ? historianReasoningBudgetDiagnostic(completion.usage.output) : !textResult && !reasoningResult ? "Historian returned no assistant output." : !textResult ? "Historian returned reasoning but no assistant text." : lengthCapped ? "Historian returned length-capped output." : null;
5847
+ const emptyError = !textResult && reasoningResult && lengthCapped ? historianReasoningBudgetDiagnostic(completion.usage.output, tokens) : !textResult && !reasoningResult ? "Historian returned no assistant output." : !textResult ? "Historian returned reasoning but no assistant text." : lengthCapped ? `Historian returned length-capped output. ${formatRunTokenLog(tokens)}` : null;
5659
5848
  const invocationId = recordInvocation({
5660
5849
  status: emptyError ? "empty" : "completed",
5661
5850
  messages: completion.messages,
@@ -6029,7 +6218,7 @@ function startLeaseHeartbeat(db, holderId, leaseKey, onLost, intervalOrAcquisiti
6029
6218
  }
6030
6219
  };
6031
6220
  beat();
6032
- const timer = lost ? undefined : setInterval(beat, intervalMs);
6221
+ const timer = lost ? undefined : setInterval(() => withSqliteBackgroundWriter(beat), intervalMs);
6033
6222
  return {
6034
6223
  stop: () => {
6035
6224
  if (timer)
@@ -6748,8 +6937,10 @@ async function runHiddenSingleShotPrompt(args) {
6748
6937
  callContext: args.callContext,
6749
6938
  fetchOutput: () => args.executor.collect(handle, 50),
6750
6939
  validateOutput: (completion) => {
6940
+ const tokens = completion.tokenLog ?? runTokenLog(undefined);
6941
+ sessionLog(args.parentSessionId ?? "dreamer", `${args.callContext} response_chars=${(completion.text ?? completion.reasoning ?? "").length} ${formatRunTokenLog(tokens)}`);
6751
6942
  if (completion.lengthCapped) {
6752
- throw new Error(completion.reasoning && !completion.text ? `${args.callContext} ran out of output budget while reasoning (length-capped at ${completion.usage.output} tokens, no text) — set dreamer.maxTokens or use a low-reasoning model` : `${args.callContext} returned length-capped output`);
6943
+ throw new Error(completion.reasoning && !completion.text ? `${args.callContext} ran out of output budget while reasoning (length-capped at ${completion.usage.output} tokens, no text; ${formatRunTokenLog(tokens)}) — set dreamer.maxTokens or use a low-reasoning model` : `${args.callContext} returned length-capped output; ${formatRunTokenLog(tokens)}`);
6753
6944
  }
6754
6945
  const text = completion.text;
6755
6946
  if (!text && !args.allowEmpty)
@@ -6783,7 +6974,7 @@ Project memory uses exactly 5 categories. Every memory belongs to one:
6783
6974
  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.
6784
6975
 
6785
6976
  ## Memory operations
6786
- - \`ctx_memory_list(category="...")\` — browse active memories, optionally filter by category
6977
+ - The scoped category snapshot arrives in the first message. Do not list or get it again; track mutation deltas.
6787
6978
  - \`action="merge", ids=[N,M,...], content="...", category="..."\` — consolidate duplicates into one canonical memory
6788
6979
  - \`action="update", ids=[N], content="...", superseded_by=M\` — rewrite content; name where removed detail survives when cutting more than half
6789
6980
  - \`action="write", category="...", content="..."\` — create a memory (SPLITS ONLY — never mint new facts)
@@ -6813,7 +7004,7 @@ var PRIMER_INVESTIGATOR_SYSTEM_PROMPT = `You are a read-only code investigator f
6813
7004
  function renderMemoryList(memories) {
6814
7005
  return memories.map((memory) => {
6815
7006
  const files = memory.mappedFiles.length ? memory.mappedFiles.join(", ") : "(none mapped yet)";
6816
- return `[${memory.id}] ${memory.category}
7007
+ return `[${memory.id}] ${memory.category} importance=${memory.importance} retrieval_count=${memory.retrievalCount} seen_count=${memory.seenCount}
6817
7008
  Content: ${memory.content}
6818
7009
  Mapped files: ${files}${memory.hasNoFileSentinel ? " (file-independent)" : ""}`;
6819
7010
  }).join(`
@@ -6841,9 +7032,41 @@ Rewrite narrative/historical → operational present tense ("X uses Y because Z"
6841
7032
  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.
6842
7033
  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.
6843
7034
 
6844
- ### Memory pool
7035
+ ### Cross-chunk duplicate candidates (normalized content matches)
7036
+ ${args.crossChunkCandidates?.join(`
7037
+ `) || "(none)"}
7038
+
7039
+ ### Category snapshot (do not re-enumerate)
6845
7040
  ${renderMemoryList(args.memories)}`;
6846
7041
  }
7042
+ function chunkCurateMemories(memories, maxCharacters) {
7043
+ const chunks = [];
7044
+ let current = [];
7045
+ let size = 0;
7046
+ for (const memory of memories) {
7047
+ const length = renderMemoryList([memory]).length + 2;
7048
+ if (current.length && size + length > maxCharacters) {
7049
+ chunks.push(current);
7050
+ current = [];
7051
+ size = 0;
7052
+ }
7053
+ current.push(memory);
7054
+ size += length;
7055
+ }
7056
+ if (current.length)
7057
+ chunks.push(current);
7058
+ const keys = new Map;
7059
+ chunks.forEach((chunk, index) => {
7060
+ chunk.forEach((memory) => {
7061
+ const key = memory.content.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
7062
+ keys.set(key, [...keys.get(key) ?? [], { id: memory.id, chunk: index }]);
7063
+ });
7064
+ });
7065
+ return chunks.map((chunk, index) => ({
7066
+ memories: chunk,
7067
+ 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(", ")}`)
7068
+ }));
7069
+ }
6847
7070
  var RETROSPECTIVE_SYSTEM_PROMPT = `You are a retrospective learning agent for Magic Context.
6848
7071
 
6849
7072
  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.
@@ -6902,17 +7125,23 @@ Return only XML in this exact shape:
6902
7125
  <learning route="observation">one recurring user preference</learning>
6903
7126
  </learnings>`;
6904
7127
  }
6905
- function buildMaintainDocsPrompt(projectPath, changeSet, existingDocs, budget = 12000, currentTokens = 0) {
7128
+ function buildMaintainDocsPrompt(projectPath, changeSet, existingDocs, budget = 12000, currentTokens = 0, currentDocs) {
6906
7129
  return `## Task: Propose documentation corrections
6907
7130
 
6908
7131
  Project: ${projectPath}
6909
7132
  Existing docs: ARCHITECTURE.md ${existingDocs.architecture ? "exists" : "missing"}; STRUCTURE.md ${existingDocs.structure ? "exists" : "missing"}.
6910
7133
  Current combined token count: ${currentTokens}. Combined budget: ${budget} tokens.
6911
7134
 
6912
- Host-collected code changes (commit subjects and changed files, not documentation history):
7135
+ Host-collected code changes (diff stat and hunks, or file/line ranges when oversized):
6913
7136
  ${changeSet}
6914
7137
 
6915
- 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.
7138
+ Current ARCHITECTURE.md:
7139
+ ${currentDocs?.architecture ?? "(read from project)"}
7140
+
7141
+ Current STRUCTURE.md:
7142
+ ${currentDocs?.structure ?? "(read from project)"}
7143
+
7144
+ 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.
6916
7145
 
6917
7146
  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.`;
6918
7147
  }
@@ -6922,10 +7151,11 @@ function buildDreamTaskPrompt(task, args) {
6922
7151
  return buildCuratePrompt({
6923
7152
  projectPath: args.projectPath,
6924
7153
  category: args.curate?.category ?? "PROJECT_RULES",
6925
- memories: args.curate?.memories ?? []
7154
+ memories: args.curate?.memories ?? [],
7155
+ crossChunkCandidates: args.curate?.crossChunkCandidates
6926
7156
  });
6927
7157
  case "maintain-docs":
6928
- return buildMaintainDocsPrompt(args.projectPath, args.docsChangeSet ?? "", args.existingDocs ?? { architecture: false, structure: false }, args.docsBudget, args.docsCurrentTokens);
7158
+ return buildMaintainDocsPrompt(args.projectPath, args.docsChangeSet ?? "", args.existingDocs ?? { architecture: false, structure: false }, args.docsBudget, args.docsCurrentTokens, args.docsContents);
6929
7159
  }
6930
7160
  }
6931
7161
 
@@ -7545,8 +7775,10 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
7545
7775
  fetchOutput: () => executor.collect(opened, 50),
7546
7776
  validateOutput: (completion) => {
7547
7777
  const messages = completion.messages ?? [];
7778
+ const tokens = completion.tokenLog ?? runTokenLog(undefined);
7779
+ sessionLog(args.parentSessionId ?? "dreamer", `dreamer:classify response_chars=${(completion.text ?? completion.reasoning ?? "").length} ${formatRunTokenLog(tokens)}`);
7548
7780
  if (completion.lengthCapped) {
7549
- throw new Error(completion.reasoning && !completion.text ? `classify ran out of output budget while reasoning (length-capped at ${completion.usage.output} tokens, no text) — set dreamer.maxTokens or use a low-reasoning model` : "classify returned length-capped output");
7781
+ throw new Error(completion.reasoning && !completion.text ? `classify ran out of output budget while reasoning (length-capped at ${completion.usage.output} tokens, no text; ${formatRunTokenLog(tokens)}) — set dreamer.maxTokens or use a low-reasoning model` : `classify returned length-capped output; ${formatRunTokenLog(tokens)}`);
7550
7782
  }
7551
7783
  const text = completion.text;
7552
7784
  if (!text)
@@ -7858,6 +8090,7 @@ function hasCurrentDocsProposal(projectDir) {
7858
8090
  }
7859
8091
  function git(projectDir, args) {
7860
8092
  return execFileSync("git", args, {
8093
+ windowsHide: true,
7861
8094
  cwd: projectDir,
7862
8095
  encoding: "utf8",
7863
8096
  timeout: 1e4,
@@ -7869,11 +8102,80 @@ function docsChangeSet(projectDir, storedAnchor) {
7869
8102
  if (realpathSync2(git(projectDir, ["rev-parse", "--show-toplevel"])) !== realpathSync2(projectDir))
7870
8103
  return null;
7871
8104
  const head = git(projectDir, ["rev-parse", "HEAD"]);
8105
+ const recorded = Boolean(storedAnchor);
7872
8106
  let anchor = storedAnchor;
7873
- if (!anchor || !/^[a-f0-9]{40}$/.test(anchor) || !git(projectDir, ["cat-file", "-t", anchor]).includes("commit"))
8107
+ if (anchor && (!/^[a-f0-9]{40}$/.test(anchor) || git(projectDir, ["cat-file", "-t", anchor]) !== "commit"))
8108
+ throw new Error("maintain-docs checkpoint is not a git commit");
8109
+ if (!anchor)
7874
8110
  anchor = git(projectDir, ["log", "-1", "--format=%H", "--", ...FILES]);
7875
8111
  if (!anchor)
7876
8112
  return null;
8113
+ if (recorded && anchor === head)
8114
+ return { head, text: "", unchanged: true, relevant: false };
8115
+ if (recorded) {
8116
+ const changed = git(projectDir, [
8117
+ "diff",
8118
+ "-M",
8119
+ "--name-status",
8120
+ `${anchor}..HEAD`,
8121
+ "--",
8122
+ "."
8123
+ ]).split(`
8124
+ `).filter(Boolean).map((line) => line.split("\t").slice(1));
8125
+ const files = [
8126
+ ...new Set(changed.flat().filter((file) => file !== "ARCHITECTURE.md" && file !== "STRUCTURE.md" && !/(?:^|[./-])(test|spec)(?:[./-]|$)/.test(file) && !file.endsWith(".lock")))
8127
+ ];
8128
+ const docs = FILES.map((file) => existsSync3(join3(projectDir, file)) ? readFileSync3(join3(projectDir, file), "utf8") : "").join(`
8129
+ `);
8130
+ const relevant = files.some((file) => {
8131
+ const directory = file.split("/").slice(0, -1).join("/");
8132
+ return docs.includes(file) || directory.length > 2 && docs.includes(directory);
8133
+ });
8134
+ if (!relevant)
8135
+ return { head, text: "", unchanged: false, relevant: false };
8136
+ const stat = git(projectDir, [
8137
+ "diff",
8138
+ "-M",
8139
+ "--stat",
8140
+ `${anchor}..HEAD`,
8141
+ "--",
8142
+ ...files
8143
+ ]);
8144
+ const patch = git(projectDir, [
8145
+ "diff",
8146
+ "-M",
8147
+ "--unified=3",
8148
+ `${anchor}..HEAD`,
8149
+ "--",
8150
+ ...files
8151
+ ]);
8152
+ const budget = 32000;
8153
+ const text = `Changed files:
8154
+ ${files.join(`
8155
+ `)}
8156
+
8157
+ Stat:
8158
+ ${stat}
8159
+
8160
+ Hunks:
8161
+ ${patch}`;
8162
+ if (text.length <= budget)
8163
+ return { head, text, unchanged: false, relevant: true };
8164
+ const ranges = [...patch.matchAll(/^\+\+\+ b\/(.+)$|^@@ .* \+(\d+)(?:,(\d+))? @@/gm)];
8165
+ return {
8166
+ head,
8167
+ unchanged: false,
8168
+ relevant: true,
8169
+ text: `Changed files and ranges (diff exceeds prompt budget):
8170
+ ${files.join(`
8171
+ `)}
8172
+ ${ranges.map((match) => match[1] ? `file: ${match[1]}` : `line: ${match[2]} +${match[3] ?? 1}`).join(`
8173
+ `).slice(0, budget)}
8174
+
8175
+ Stat:
8176
+ ${stat}`
8177
+ };
8178
+ }
7877
8179
  const excluded = [
7878
8180
  "ARCHITECTURE.md",
7879
8181
  "STRUCTURE.md",
@@ -7903,11 +8205,15 @@ function docsChangeSet(projectDir, storedAnchor) {
7903
8205
  const text = raw.slice(0, 24000);
7904
8206
  return {
7905
8207
  head,
8208
+ unchanged: false,
8209
+ relevant: true,
7906
8210
  text: `${text}${raw.length > text.length ? `
7907
8211
  [cut ${raw.length - text.length} bytes]` : ""}${commits.length > 201 ? `
7908
8212
  [cut commits beyond newest 200]` : ""}`
7909
8213
  };
7910
- } catch {
8214
+ } catch (error) {
8215
+ if (storedAnchor)
8216
+ throw error;
7911
8217
  return null;
7912
8218
  }
7913
8219
  }
@@ -7941,7 +8247,7 @@ function diff(name, before, after) {
7941
8247
  writeFileSync3(join3(dir, "before"), before);
7942
8248
  writeFileSync3(join3(dir, "after"), after);
7943
8249
  try {
7944
- return execFileSync("git", ["diff", "--no-index", "--", join3(dir, "before"), join3(dir, "after")], { encoding: "utf8" });
8250
+ return execFileSync("git", ["diff", "--no-index", "--", join3(dir, "before"), join3(dir, "after")], { windowsHide: true, encoding: "utf8" });
7945
8251
  } catch (error) {
7946
8252
  return error.stdout?.replaceAll(join3(dir, "before"), `a/${name}`).replaceAll(join3(dir, "after"), `b/${name}`) ?? "";
7947
8253
  }
@@ -8192,7 +8498,7 @@ function requestValidatedAddress(validation, candidate, options) {
8192
8498
  const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8193
8499
  bytes += buf.byteLength;
8194
8500
  if (bytes > options.bodyLimitBytes) {
8195
- reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: response body too large", { terminal: true }));
8501
+ 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 }));
8196
8502
  response.destroy();
8197
8503
  request2.destroy();
8198
8504
  return;
@@ -8529,6 +8835,7 @@ async function runGit(projectRoot, args, signal) {
8529
8835
  throwIfAborted2(signal);
8530
8836
  try {
8531
8837
  const result = await execFileAsync("git", ["-C", projectRoot, ...args], {
8838
+ windowsHide: true,
8532
8839
  timeout: DEFAULT_GIT_TIMEOUT_MS,
8533
8840
  maxBuffer: 128 * 1024,
8534
8841
  signal
@@ -8691,6 +8998,7 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
8691
8998
  const controller = new AbortController;
8692
8999
  let externallyCancelled = false;
8693
9000
  let executionTimedOut = false;
9001
+ let persistentNetworkFailure = false;
8694
9002
  const externalAbort = () => {
8695
9003
  externallyCancelled = true;
8696
9004
  controller.abort(options.signal?.reason);
@@ -8709,7 +9017,19 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
8709
9017
  context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
8710
9018
  context.runtime.setMaxStackSize(options.stackLimitBytes ?? DEFAULT_STACK_LIMIT_BYTES);
8711
9019
  context.runtime.setInterruptHandler(() => controller.signal.aborted || Date.now() > deadline);
8712
- installCapabilityObject(context, capabilities);
9020
+ installCapabilityObject(context, {
9021
+ ...capabilities,
9022
+ httpGet: async (url) => {
9023
+ try {
9024
+ return await capabilities.httpGet(url);
9025
+ } catch (error) {
9026
+ if (error instanceof SmartNoteNetworkError && error.persistent) {
9027
+ persistentNetworkFailure = true;
9028
+ }
9029
+ throw error;
9030
+ }
9031
+ }
9032
+ });
8713
9033
  disableAmbientDynamicCode(context);
8714
9034
  const result = await evalCheck(context, options.compiledCheck);
8715
9035
  const checkResult = result;
@@ -8723,14 +9043,14 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
8723
9043
  } catch (error) {
8724
9044
  if (externallyCancelled && !executionTimedOut)
8725
9045
  return cancelledResult(error);
8726
- return failureResult(formatSandboxError(error), isSmartNoteNetworkError(error));
9046
+ return failureResult(formatSandboxError(error), isSmartNoteNetworkError(error), persistentNetworkFailure);
8727
9047
  } finally {
8728
9048
  clearTimeout(timer);
8729
9049
  options.signal?.removeEventListener("abort", externalAbort);
8730
9050
  }
8731
9051
  }
8732
- function failureResult(error, network) {
8733
- return { ok: false, cancelled: false, error: truncate(error), network };
9052
+ function failureResult(error, network, persistent = false) {
9053
+ return { ok: false, cancelled: false, error: truncate(error), network, persistent };
8734
9054
  }
8735
9055
  function cancelledResult(reason) {
8736
9056
  return {
@@ -8828,7 +9148,12 @@ var MAX_CRON_CHARS = 256;
8828
9148
  var MAX_COMPILER_ERROR_CHARS = 2 * 1024;
8829
9149
  async function compileSmartNoteCheck(args) {
8830
9150
  if (!args.note.surfaceCondition) {
8831
- return { ok: false, cancelled: false, error: "note has no surface condition" };
9151
+ return {
9152
+ ok: false,
9153
+ cancelled: false,
9154
+ error: "note has no surface condition",
9155
+ persistent: false
9156
+ };
8832
9157
  }
8833
9158
  const prompt = `Compile this smart note condition into a sandbox check.
8834
9159
 
@@ -8935,15 +9260,13 @@ Remember: output only the JSON object described by the system prompt.`;
8935
9260
  const compiledCheck = normalizeCompiledCheck(response.compiled_check);
8936
9261
  const manifest = normalizeManifest(response.manifest);
8937
9262
  const checkCron = normalizeCron(response.check_cron);
8938
- for (const warning of manifestAdvisoryWarnings(compiledCheck, manifest)) {
9263
+ const dryRun = await dryRunSmartNoteCheck(compiledCheck, args.capabilityFactory, args.signal);
9264
+ for (const warning of [
9265
+ ...manifestAdvisoryWarnings(compiledCheck, manifest),
9266
+ ...dryRun.advisories
9267
+ ]) {
8939
9268
  log(`[dreamer] smart note #${args.note.id}: manifest advisory — ${warning}`);
8940
9269
  }
8941
- const dryRun = await runCompiledSmartNoteCheck({
8942
- compiledCheck,
8943
- capabilityFactory: args.capabilityFactory,
8944
- signal: args.signal,
8945
- timeoutMs: 2000
8946
- });
8947
9270
  if (!dryRun.ok) {
8948
9271
  const error = boundedError(`dry-run failed: ${dryRun.error}`);
8949
9272
  recordInvocation({
@@ -8951,7 +9274,12 @@ Remember: output only the JSON object described by the system prompt.`;
8951
9274
  messages: outputMessages,
8952
9275
  error
8953
9276
  });
8954
- return { ok: false, cancelled: dryRun.cancelled, error };
9277
+ return {
9278
+ ok: false,
9279
+ cancelled: dryRun.cancelled,
9280
+ error,
9281
+ persistent: !dryRun.cancelled && dryRun.persistent
9282
+ };
8955
9283
  }
8956
9284
  recordInvocation({ status: "completed", messages: outputMessages });
8957
9285
  return {
@@ -8966,7 +9294,7 @@ Remember: output only the JSON object described by the system prompt.`;
8966
9294
  const cancelled = args.signal.aborted;
8967
9295
  const message = boundedError(error instanceof Error ? error.message : String(error));
8968
9296
  recordInvocation({ status: cancelled ? "aborted" : "failed", error: message });
8969
- return { ok: false, cancelled, error: message };
9297
+ return { ok: false, cancelled, error: message, persistent: false };
8970
9298
  } finally {
8971
9299
  if (args.client) {
8972
9300
  await teardownChildSession({
@@ -8981,6 +9309,29 @@ Remember: output only the JSON object described by the system prompt.`;
8981
9309
  }
8982
9310
  }
8983
9311
  }
9312
+ async function dryRunSmartNoteCheck(compiledCheck, capabilityFactory, signal) {
9313
+ const responses = [];
9314
+ const dryRun = await runCompiledSmartNoteCheck({
9315
+ compiledCheck,
9316
+ capabilityFactory: (runSignal) => {
9317
+ const capabilities = capabilityFactory(runSignal);
9318
+ return {
9319
+ ...capabilities,
9320
+ httpGet: async (url) => {
9321
+ const response = await capabilities.httpGet(url);
9322
+ responses.push({ url, status: response.status });
9323
+ return response;
9324
+ }
9325
+ };
9326
+ },
9327
+ signal,
9328
+ timeoutMs: 2000
9329
+ });
9330
+ const advisories = dryRun.ok && responses.length > 0 && responses.every(({ status }) => status === 401 || status === 403 || status === 404) ? [
9331
+ `all HTTP sources currently return 401/403/404: ${responses.map(({ url, status }) => `${url} (HTTP ${status})`).join(", ")}`
9332
+ ] : [];
9333
+ return { ...dryRun, advisories };
9334
+ }
8984
9335
  function parseCompilerOutput(output) {
8985
9336
  if (!output)
8986
9337
  throw new Error("smart-note compiler returned no output");
@@ -9435,7 +9786,7 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld,
9435
9786
  expected: sourceRevisionExpectation(note),
9436
9787
  leaseHeld,
9437
9788
  write: () => {
9438
- markSmartNoteCompilationFailure(args.db, note.id, now, MAX_COMPILATION_FAILURES);
9789
+ markSmartNoteCompilationFailure(args.db, note.id, now, MAX_COMPILATION_FAILURES, result.error, result.persistent);
9439
9790
  }
9440
9791
  });
9441
9792
  return false;
@@ -9806,7 +10157,7 @@ var MAP_MEMORIES_SYSTEM_PROMPT = `You are a memory mapper for the magic-context
9806
10157
 
9807
10158
  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.
9808
10159
 
9809
- 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.
10160
+ 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.
9810
10161
 
9811
10162
  For each memory decide ONE of:
9812
10163
  - Backing files found → the COMPLETE set of repo-relative paths whose code the memory is about.
@@ -10063,6 +10414,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
10063
10414
  prompt,
10064
10415
  title: "magic-context-dream-map-memories",
10065
10416
  callContext: "dreamer:map-memories",
10417
+ metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
10066
10418
  model: args.model,
10067
10419
  fallbackModels: args.fallbackModels,
10068
10420
  timeoutMs: sliceMs,
@@ -10103,7 +10455,10 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
10103
10455
  parts: [{ type: "text", text: prompt, synthetic: true }]
10104
10456
  }
10105
10457
  }, {
10106
- transport: createPromptAsyncTransport(client, agentSessionId),
10458
+ transport: createPromptAsyncTransport(client, agentSessionId, {
10459
+ tokenBudget: args.tokenBudget,
10460
+ onBudgetUpdate: args.onBudgetUpdate
10461
+ }),
10107
10462
  timeoutMs: sliceMs,
10108
10463
  signal,
10109
10464
  fallbackModels: args.fallbackModels,
@@ -10143,7 +10498,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
10143
10498
  error,
10144
10499
  messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
10145
10500
  });
10146
- if (error instanceof DreamerModuleFailureError)
10501
+ if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded)
10147
10502
  throw error;
10148
10503
  if (signal.aborted)
10149
10504
  throw error;
@@ -10773,6 +11128,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
10773
11128
  prompt,
10774
11129
  title: "magic-context-dream-refresh-primers",
10775
11130
  callContext: "dreamer:refresh-primers",
11131
+ metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
10776
11132
  model: args.model,
10777
11133
  fallbackModels: args.fallbackModels,
10778
11134
  language: args.language,
@@ -10822,6 +11178,10 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
10822
11178
  signal,
10823
11179
  fallbackModels: args.fallbackModels,
10824
11180
  callContext: "dreamer:refresh-primers",
11181
+ transport: createPromptAsyncTransport(client, agentSessionId, {
11182
+ tokenBudget: args.tokenBudget,
11183
+ onBudgetUpdate: args.onBudgetUpdate
11184
+ }),
10825
11185
  fetchOutput: async () => {
10826
11186
  const messagesResponse = await client.session.messages({
10827
11187
  path: { id: agentSessionId },
@@ -11034,12 +11394,14 @@ class OpenCodeRetrospectiveRawProvider {
11034
11394
  this.openDb = deps.openOpenCodeDb ?? openOpenCodeDb;
11035
11395
  }
11036
11396
  listProjectSessions(projectIdentity) {
11037
- const rows = this.deps.contextDb.prepare(`SELECT sp.session_id, sp.updated_at
11397
+ const rows = this.deps.contextDb.prepare(`SELECT sp.session_id, CAST(activity.value AS INTEGER) AS updated_at
11038
11398
  FROM session_projects sp
11399
+ JOIN schema_migrations_meta activity
11400
+ ON activity.key = 'retrospective_activity:' || sp.session_id
11039
11401
  LEFT JOIN session_meta m ON m.session_id = sp.session_id
11040
11402
  WHERE sp.project_path = ? AND sp.harness = 'opencode'
11041
11403
  AND COALESCE(m.is_subagent, 0) = 0
11042
- ORDER BY sp.updated_at ASC, sp.session_id ASC`).all(projectIdentity);
11404
+ ORDER BY updated_at ASC, sp.session_id ASC`).all(projectIdentity);
11043
11405
  return rows.map((row) => ({
11044
11406
  sessionId: row.session_id,
11045
11407
  updatedAt: typeof row.updated_at === "number" ? row.updated_at : undefined
@@ -11058,15 +11420,13 @@ class OpenCodeRetrospectiveRawProvider {
11058
11420
  const db = this.resolveDb();
11059
11421
  if (!db)
11060
11422
  return { messages: [], truncated: false };
11061
- try {
11062
- return readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession);
11063
- } catch {
11064
- return { messages: [], truncated: false };
11065
- }
11423
+ return readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession);
11066
11424
  }
11067
11425
  readOldestMessageTimesSince(sessionIds, sinceMs) {
11068
11426
  const db = this.resolveDb();
11069
- if (!db || sessionIds.length === 0)
11427
+ if (!db)
11428
+ return null;
11429
+ if (sessionIds.length === 0)
11070
11430
  return new Map;
11071
11431
  return readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs);
11072
11432
  }
@@ -11163,6 +11523,11 @@ async function readRetrospectiveScanWindow(provider, projectIdentity, watermarkM
11163
11523
  }
11164
11524
  const droppedSince = [...budgetDropped, ...countDropped];
11165
11525
  let maxScannedTs = scanSinceMs;
11526
+ if (oldestBySession && sinceReads.every((read) => read.messages.length === 0)) {
11527
+ for (const { session } of eligibleSessions) {
11528
+ maxScannedTs = Math.max(maxScannedTs, session.updatedAt ?? scanSinceMs);
11529
+ }
11530
+ }
11166
11531
  for (const row of keptSince) {
11167
11532
  if (row.ts > maxScannedTs)
11168
11533
  maxScannedTs = row.ts;
@@ -11454,6 +11819,127 @@ function insertDreamRun(db, run) {
11454
11819
  // ../plugin/src/features/magic-context/dreamer/verify.ts
11455
11820
  import { createHash as createHash9 } from "node:crypto";
11456
11821
 
11822
+ // ../plugin/src/features/magic-context/dreamer/verify-diff.ts
11823
+ import { execFile as execFile2 } from "node:child_process";
11824
+ import { promisify as promisify2 } from "node:util";
11825
+ var exec = promisify2(execFile2);
11826
+ var VERIFY_DIFF_BUDGET_TOKENS = 12000;
11827
+ var MAX_DIFF_BYTES = 4 * 1024 * 1024;
11828
+ async function git2(root, args) {
11829
+ const { stdout } = await exec("git", args, {
11830
+ cwd: root,
11831
+ timeout: 1e4,
11832
+ maxBuffer: MAX_DIFF_BYTES,
11833
+ encoding: "utf8"
11834
+ });
11835
+ return stdout;
11836
+ }
11837
+ async function buildVerifyDiffEvidence(directory, memories) {
11838
+ const root = await resolveGitTopLevel(directory);
11839
+ const head = root && await readGitHead(root);
11840
+ if (!root || !head || memories.some((m) => !m.verifiedAt))
11841
+ return null;
11842
+ try {
11843
+ const bases = new Map;
11844
+ for (const memory of memories) {
11845
+ const at = memory.verifiedAt;
11846
+ if (!at)
11847
+ return null;
11848
+ const base = memory.verifiedCommit ?? (await git2(root, [
11849
+ "rev-list",
11850
+ "-1",
11851
+ `--before=${new Date(at).toISOString()}`,
11852
+ head
11853
+ ])).trim();
11854
+ if (!/^[0-9a-f]{40}$/i.test(base))
11855
+ return null;
11856
+ bases.set(memory.id, base);
11857
+ }
11858
+ const grouped = new Map;
11859
+ for (const memory of memories) {
11860
+ const at = memory.verifiedAt;
11861
+ const base = bases.get(memory.id);
11862
+ if (!at || !base)
11863
+ return null;
11864
+ for (const file of memory.mappedFiles) {
11865
+ if (!file || file.startsWith("/") || file.split("/").includes(".."))
11866
+ return null;
11867
+ const previous = grouped.get(file);
11868
+ if (!previous)
11869
+ grouped.set(file, {
11870
+ base,
11871
+ time: at,
11872
+ ids: [memory.id]
11873
+ });
11874
+ else {
11875
+ previous.ids.push(memory.id);
11876
+ if (at < previous.time) {
11877
+ previous.time = at;
11878
+ previous.base = base;
11879
+ }
11880
+ }
11881
+ }
11882
+ }
11883
+ const files = [];
11884
+ const namesByBase = new Map;
11885
+ for (const [file, group] of grouped) {
11886
+ let names = namesByBase.get(group.base);
11887
+ if (names === undefined) {
11888
+ names = await git2(root, ["diff", "-M", "--name-status", group.base, head]);
11889
+ namesByBase.set(group.base, names);
11890
+ }
11891
+ const rename = names.split(`
11892
+ `).map((line) => line.split("\t")).find((parts) => parts[0]?.startsWith("R") && parts[1] === file);
11893
+ const paths = rename ? [file, rename[2]] : [file];
11894
+ const committed = await git2(root, [
11895
+ "diff",
11896
+ "-M",
11897
+ "--no-ext-diff",
11898
+ group.base,
11899
+ head,
11900
+ "--",
11901
+ ...paths
11902
+ ]);
11903
+ const pending = await git2(root, ["diff", "-M", "--no-ext-diff", head, "--", ...paths]);
11904
+ const status = rename ? `RENAMED ${file} -> ${rename[2]}
11905
+ ` : names.split(`
11906
+ `).some((line) => line === `D ${file}`) ? `DELETED ${file}
11907
+ ` : "";
11908
+ files.push({
11909
+ path: file,
11910
+ memoryIds: group.ids,
11911
+ text: `${status}${committed}${pending ? `
11912
+ Uncommitted changes:
11913
+ ${pending}` : ""}` || "No changed hunks in mapped file."
11914
+ });
11915
+ }
11916
+ const size = files.reduce((sum, file) => sum + file.text.length, 0);
11917
+ if (size <= VERIFY_DIFF_BUDGET_TOKENS * 3)
11918
+ return { head, mode: "diff", files };
11919
+ return {
11920
+ head,
11921
+ mode: "ranges",
11922
+ files: files.map((file) => {
11923
+ const headers = file.text.split(`
11924
+ `).filter((line) => /^(?:@@|diff --git|rename |deleted file mode|new file mode|RENAMED|DELETED|Uncommitted changes:)/.test(line));
11925
+ const lines = file.text.split(`
11926
+ `);
11927
+ const added = lines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
11928
+ const removed = lines.filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
11929
+ return {
11930
+ ...file,
11931
+ text: `Changed lines: +${added}/-${removed}.
11932
+ ${headers.join(`
11933
+ `)}
11934
+ Read only these changed line ranges if they may affect the claim.`
11935
+ };
11936
+ })
11937
+ };
11938
+ } catch {
11939
+ return null;
11940
+ }
11941
+ }
11942
+
11457
11943
  // ../plugin/src/features/magic-context/dreamer/verify-gate.ts
11458
11944
  import path5 from "node:path";
11459
11945
  function minOf(values) {
@@ -11488,12 +11974,24 @@ async function partitionVerifyScope(args) {
11488
11974
  const active = getMemoriesByProject(args.db, args.projectIdentity);
11489
11975
  const verById = getMemoryVerifications(args.db, active.map((m) => m.id));
11490
11976
  const candidates = active.filter((m) => (verById.get(m.id)?.files.length ?? 0) > 0);
11491
- const toPrompt = (m) => ({
11492
- id: m.id,
11493
- category: m.category,
11494
- content: m.content,
11495
- mappedFiles: verById.get(m.id)?.files ?? []
11496
- });
11977
+ const toPrompt = (m) => {
11978
+ const verifiedAt = verById.get(m.id)?.verifiedAt ?? 0;
11979
+ let verifiedCommit = null;
11980
+ try {
11981
+ const metadata = JSON.parse(m.metadataJson ?? "{}");
11982
+ if (metadata.dreamerVerifiedAt === verifiedAt && typeof metadata.dreamerVerifiedCommit === "string" && /^[0-9a-f]{40}$/i.test(metadata.dreamerVerifiedCommit)) {
11983
+ verifiedCommit = metadata.dreamerVerifiedCommit;
11984
+ }
11985
+ } catch {}
11986
+ return {
11987
+ id: m.id,
11988
+ category: m.category,
11989
+ content: m.content,
11990
+ mappedFiles: verById.get(m.id)?.files ?? [],
11991
+ verifiedAt,
11992
+ verifiedCommit
11993
+ };
11994
+ };
11497
11995
  if (args.forceBroad) {
11498
11996
  const broadCycleStartAt = ensureBroadCycleStart({
11499
11997
  db: args.db,
@@ -11573,9 +12071,9 @@ async function partitionVerifyScope(args) {
11573
12071
  // ../plugin/src/features/magic-context/dreamer/verify-prompt.ts
11574
12072
  var VERIFY_SYSTEM_PROMPT = `You are a memory verifier for the magic-context system. You verify project memories against the CURRENT code.
11575
12073
 
11576
- 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.
12074
+ 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.
11577
12075
 
11578
- Tools (read-only): read, grep, glob, aft_search, aft_outline, aft_zoom. You read code to check claims; you change nothing.
12076
+ 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.
11579
12077
 
11580
12078
  Decide ONE of four outcomes per memory:
11581
12079
  - VERIFIED — still accurate. Keep it as-is.
@@ -11600,7 +12098,7 @@ Rules:
11600
12098
  - Every input memory id MUST appear exactly once, in exactly one of verified/update/archive/skip.
11601
12099
  - 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.
11602
12100
  - Default to VERIFIED. skip when code cannot decide the claim. update and archive are the exceptions, not the norm.`;
11603
- function buildVerifyPrompt(projectPath, memories) {
12101
+ function buildVerifyPrompt(projectPath, memories, evidence) {
11604
12102
  const list = memories.map((m) => `[${m.id}] ${m.category}
11605
12103
  Content: ${m.content}
11606
12104
  Backing files: ${m.mappedFiles.join(", ")}`).join(`
@@ -11610,11 +12108,18 @@ Backing files: ${m.mappedFiles.join(", ")}`).join(`
11610
12108
 
11611
12109
  Project: ${projectPath}
11612
12110
 
11613
- 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.
12111
+ ${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.
11614
12112
 
11615
12113
  <memories>
11616
12114
  ${list}
11617
- </memories>`;
12115
+ </memories>${evidence ? `
12116
+
12117
+ <changed-files mode="${evidence.mode}">
12118
+ ${evidence.files.map((file) => `File: ${file.path} (memories: ${file.memoryIds.join(", ")})
12119
+ ${file.text}`).join(`
12120
+
12121
+ `)}
12122
+ </changed-files>` : ""}`;
11618
12123
  }
11619
12124
  function attrOf(s, name) {
11620
12125
  const m = s.match(new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`));
@@ -11738,8 +12243,11 @@ async function runVerify(args) {
11738
12243
  return result;
11739
12244
  }
11740
12245
  const batches = [];
11741
- for (let i = 0;i < gate.inScope.length; i += VERIFY_BATCH_SIZE) {
11742
- batches.push(gate.inScope.slice(i, i + VERIFY_BATCH_SIZE));
12246
+ const groups = !args.forceBroad && gate.mode === "incremental" ? [gate.inScope.filter((m) => m.verifiedAt), gate.inScope.filter((m) => !m.verifiedAt)] : [gate.inScope];
12247
+ for (const group of groups) {
12248
+ for (let i = 0;i < group.length; i += VERIFY_BATCH_SIZE) {
12249
+ batches.push(group.slice(i, i + VERIFY_BATCH_SIZE));
12250
+ }
11743
12251
  }
11744
12252
  const abortController = new AbortController;
11745
12253
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
@@ -11803,7 +12311,9 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11803
12311
  let promptSettled = false;
11804
12312
  const startedAt = Date.now();
11805
12313
  try {
11806
- const prompt = buildVerifyPrompt(args.projectIdentity, batch);
12314
+ const headAtPrompt = await readGitHead(args.sessionDirectory);
12315
+ const evidence = !args.forceBroad && batch.every((m) => m.verifiedAt) ? await buildVerifyDiffEvidence(args.sessionDirectory, batch) : null;
12316
+ const prompt = buildVerifyPrompt(args.projectIdentity, batch, evidence);
11807
12317
  if (args.hiddenCompletionExecutor) {
11808
12318
  const run = await runHiddenSingleShotPrompt({
11809
12319
  executor: args.hiddenCompletionExecutor,
@@ -11814,6 +12324,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11814
12324
  prompt,
11815
12325
  title: "magic-context-dream-verify",
11816
12326
  callContext: "dreamer:verify",
12327
+ metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
11817
12328
  model: args.model,
11818
12329
  fallbackModels: args.fallbackModels,
11819
12330
  language: args.language,
@@ -11830,7 +12341,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11830
12341
  status: "completed",
11831
12342
  messages: run.completion.messages ?? []
11832
12343
  });
11833
- return applyParsedVerifyManifest(args, batch, run.validated);
12344
+ return applyParsedVerifyManifest(args, batch, run.validated, evidence?.head ?? headAtPrompt);
11834
12345
  }
11835
12346
  const client = args.client;
11836
12347
  if (!client)
@@ -11858,7 +12369,10 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11858
12369
  parts: [{ type: "text", text: prompt, synthetic: true }]
11859
12370
  }
11860
12371
  }, {
11861
- transport: createPromptAsyncTransport(client, agentSessionId),
12372
+ transport: createPromptAsyncTransport(client, agentSessionId, {
12373
+ tokenBudget: args.tokenBudget,
12374
+ onBudgetUpdate: args.onBudgetUpdate
12375
+ }),
11862
12376
  timeoutMs: sliceMs,
11863
12377
  signal,
11864
12378
  fallbackModels: args.fallbackModels,
@@ -11887,7 +12401,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11887
12401
  });
11888
12402
  promptSettled = true;
11889
12403
  recordInvocation4(args, startedAt, { status: "completed", messages: run.output });
11890
- return await applyParsedVerifyManifest(args, batch, run.validated);
12404
+ return await applyParsedVerifyManifest(args, batch, run.validated, evidence?.head ?? headAtPrompt);
11891
12405
  } catch (error) {
11892
12406
  const desc = describeError(error);
11893
12407
  const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
@@ -11898,7 +12412,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11898
12412
  error,
11899
12413
  messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
11900
12414
  });
11901
- if (error instanceof DreamerModuleFailureError || signal.aborted)
12415
+ if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded || signal.aborted)
11902
12416
  throw error;
11903
12417
  if (promptFailure?.failureClass === "provider_timeout" || isPromptTimeoutError(error)) {
11904
12418
  return {
@@ -11933,7 +12447,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11933
12447
  });
11934
12448
  }
11935
12449
  }
11936
- async function applyParsedVerifyManifest(args, batch, parsed) {
12450
+ async function applyParsedVerifyManifest(args, batch, parsed, headAtPrompt) {
11937
12451
  const batchIds = new Set(batch.map((m) => m.id));
11938
12452
  const batchById = new Map(batch.map((memory) => [memory.id, memory]));
11939
12453
  const valid = {
@@ -11960,6 +12474,7 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
11960
12474
  return { verified: 0, updated: 0, archived: 0, skipped: 0, refused: 0 };
11961
12475
  }
11962
12476
  const now = Date.now();
12477
+ const verifiedHead = headAtPrompt === undefined ? await readGitHead(args.sessionDirectory) : headAtPrompt;
11963
12478
  const writes = [];
11964
12479
  let refused = 0;
11965
12480
  const skipped = valid.skipped.length;
@@ -12071,6 +12586,8 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
12071
12586
  continue;
12072
12587
  if (w.kind === "verify") {
12073
12588
  recordMemoryVerifications(args.db, w.id, w.files, now);
12589
+ if (verifiedHead)
12590
+ recordVerifiedCommit(args.db, memory, now, verifiedHead);
12074
12591
  verified += 1;
12075
12592
  } else if (w.kind === "update") {
12076
12593
  rewriteMemoryContent(args.db, memory, w.content, w.hash);
@@ -12095,6 +12612,15 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
12095
12612
  });
12096
12613
  return { verified, updated, archived, skipped, refused };
12097
12614
  }
12615
+ function recordVerifiedCommit(db, memory, at, head) {
12616
+ let metadata = {};
12617
+ try {
12618
+ const parsed = JSON.parse(memory.metadataJson ?? "{}");
12619
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
12620
+ metadata = parsed;
12621
+ } catch {}
12622
+ db.prepare("UPDATE memories SET metadata_json = ? WHERE id = ?").run(JSON.stringify({ ...metadata, dreamerVerifiedAt: at, dreamerVerifiedCommit: head }), memory.id);
12623
+ }
12098
12624
  async function normalizeFiles(args, rawFiles) {
12099
12625
  if (rawFiles.length === 0)
12100
12626
  return [];
@@ -12152,6 +12678,16 @@ function dreamRunFailureDetail(error) {
12152
12678
  }
12153
12679
  const described = describeError(error);
12154
12680
  const message = described.brief;
12681
+ if (error instanceof DreamTokenBudgetExceeded) {
12682
+ return {
12683
+ failure_class: "token_budget",
12684
+ model_attempted: null,
12685
+ models_tried: [],
12686
+ provider_error: null,
12687
+ timeout_ms: null,
12688
+ child_session_id: error.sessionId
12689
+ };
12690
+ }
12155
12691
  if (error instanceof Error && error.name === "HiddenAgentStepLimit") {
12156
12692
  return {
12157
12693
  failure_class: "step_limit",
@@ -12196,7 +12732,10 @@ function toCuratePromptMemory(memory, verificationById) {
12196
12732
  category: memory.category,
12197
12733
  content: memory.content,
12198
12734
  mappedFiles: verification?.files ?? [],
12199
- hasNoFileSentinel: verification?.hasSentinel ?? false
12735
+ hasNoFileSentinel: verification?.hasSentinel ?? false,
12736
+ importance: memory.importance,
12737
+ retrievalCount: memory.retrievalCount,
12738
+ seenCount: memory.seenCount
12200
12739
  };
12201
12740
  }
12202
12741
  function loadActiveMemoryPromptMemories(db, projectIdentity) {
@@ -12236,7 +12775,10 @@ function inspectCurateMemoryOperations(messages) {
12236
12775
  if (!isRecord(part.state) || part.state.status !== "completed")
12237
12776
  continue;
12238
12777
  const input = isRecord(part.state.input) ? part.state.input : null;
12239
- summary.completedActions.push(typeof input?.action === "string" ? input.action : "unknown");
12778
+ const action = input?.action;
12779
+ const output = typeof part.state.output === "string" ? part.state.output : "";
12780
+ 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"))
12781
+ summary.completedActions.push(action);
12240
12782
  }
12241
12783
  }
12242
12784
  return summary;
@@ -12284,6 +12826,7 @@ function createDreamTaskExecutor(deps) {
12284
12826
  };
12285
12827
  return async (config, ctx) => {
12286
12828
  const { db, projectIdentity, holderId, leaseKey } = ctx;
12829
+ deps.client?.resetDreamTokenBudget?.(config.task);
12287
12830
  const startedAt = Date.now();
12288
12831
  const leaseAcquisition = ctx.leaseAcquisition ?? acquireLeaseWithAcquisition(db, holderId, leaseKey) ?? (() => {
12289
12832
  throw new Error("Dream lease unavailable during executor setup");
@@ -12342,8 +12885,23 @@ function createDreamTaskExecutor(deps) {
12342
12885
  if (!leaseOwnershipMatches(db, holderId, leaseAcquisition.generation, leaseKey)) {
12343
12886
  throw new Error("Dream lease lost during executor setup");
12344
12887
  }
12888
+ let budgetSpent = 0;
12889
+ let budgetFinalized = false;
12890
+ const childSpending = new Map;
12891
+ const onBudgetUpdate = (state) => {
12892
+ if (state.sessionId) {
12893
+ const previous = childSpending.get(state.sessionId) ?? 0;
12894
+ budgetSpent += Math.max(0, state.spent - previous);
12895
+ childSpending.set(state.sessionId, state.spent);
12896
+ } else
12897
+ budgetSpent = Math.max(budgetSpent, state.spent);
12898
+ budgetFinalized ||= state.finalizeFired;
12899
+ };
12345
12900
  const recordRun = (status, error, extra) => {
12346
12901
  try {
12902
+ const hostBudget = deps.readTokenBudget?.(config.task) ?? deps.client?.readTokenBudget?.(config.task);
12903
+ if (hostBudget)
12904
+ onBudgetUpdate(hostBudget);
12347
12905
  insertDreamRun(db, {
12348
12906
  projectPath: projectIdentity,
12349
12907
  startedAt,
@@ -12359,6 +12917,14 @@ function createDreamTaskExecutor(deps) {
12359
12917
  failure: extra?.failure ?? dreamRunFailureDetail(error ?? "unknown dreamer failure")
12360
12918
  } : {},
12361
12919
  ...extra?.progress ? { progress: extra.progress } : {},
12920
+ ...config.tokenBudget ? {
12921
+ tokenBudget: {
12922
+ budget: config.tokenBudget,
12923
+ spent: budgetSpent,
12924
+ finalizeFired: budgetFinalized,
12925
+ banked: extra?.banked ?? processedDreamTaskItems(backlogAtStart.pending, (extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task)).pending)
12926
+ }
12927
+ } : {},
12362
12928
  backlog: (() => {
12363
12929
  const end = extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task);
12364
12930
  const processed = processedDreamTaskItems(backlogAtStart.pending, end.pending);
@@ -12449,7 +13015,7 @@ function createDreamTaskExecutor(deps) {
12449
13015
  leaseKey,
12450
13016
  deadline,
12451
13017
  leaseAcquisition,
12452
- promotionThreshold: config.promotionThreshold ?? 3,
13018
+ promotionThreshold: config.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"],
12453
13019
  model: config.model,
12454
13020
  fallbackModels: config.fallbackModels,
12455
13021
  language: config.language ?? deps.language
@@ -12473,6 +13039,8 @@ function createDreamTaskExecutor(deps) {
12473
13039
  model: config.model,
12474
13040
  fallbackModels: config.fallbackModels,
12475
13041
  moduleRoute,
13042
+ tokenBudget: config.tokenBudget,
13043
+ onBudgetUpdate,
12476
13044
  onProgress: (processed) => reportProgress(processed)
12477
13045
  });
12478
13046
  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}` : ""}`);
@@ -12513,6 +13081,8 @@ function createDreamTaskExecutor(deps) {
12513
13081
  fallbackModels: config.fallbackModels,
12514
13082
  language: config.language ?? deps.language,
12515
13083
  moduleRoute,
13084
+ tokenBudget: config.tokenBudget,
13085
+ onBudgetUpdate,
12516
13086
  onProgress: (processed, refused) => reportProgress(processed, refused)
12517
13087
  });
12518
13088
  const processed = result.verified + result.updated + result.archived + result.skipped + result.refused;
@@ -12524,7 +13094,8 @@ function createDreamTaskExecutor(deps) {
12524
13094
  recordRun("completed", null, {
12525
13095
  progress: broadProgress,
12526
13096
  memoryChanges: computeMemoryDelta(memoryBefore),
12527
- backlogAfter
13097
+ backlogAfter,
13098
+ banked: result.verified + result.updated + result.archived
12528
13099
  });
12529
13100
  return { status: "completed" };
12530
13101
  }
@@ -12533,14 +13104,16 @@ function createDreamTaskExecutor(deps) {
12533
13104
  recordRun("failed", error, {
12534
13105
  progress: verificationProgress,
12535
13106
  memoryChanges: computeMemoryDelta(memoryBefore),
12536
- backlogAfter
13107
+ backlogAfter,
13108
+ banked: result.verified + result.updated + result.archived
12537
13109
  });
12538
13110
  return { status: "failed", transient: !batchTimedOut, error };
12539
13111
  }
12540
13112
  recordRun("completed", null, {
12541
13113
  progress: verificationProgress,
12542
13114
  memoryChanges: computeMemoryDelta(memoryBefore),
12543
- backlogAfter
13115
+ backlogAfter,
13116
+ banked: result.verified + result.updated + result.archived
12544
13117
  });
12545
13118
  return { status: "completed" };
12546
13119
  }
@@ -12592,7 +13165,7 @@ function createDreamTaskExecutor(deps) {
12592
13165
  leaseKey,
12593
13166
  deadline,
12594
13167
  leaseAcquisition,
12595
- promotionThreshold: config.promotionThreshold ?? 2,
13168
+ promotionThreshold: config.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"],
12596
13169
  ensureProjectRegistered: deps.ensureProjectRegistered
12597
13170
  });
12598
13171
  recordRun("completed", null);
@@ -12615,6 +13188,8 @@ function createDreamTaskExecutor(deps) {
12615
13188
  fallbackModels: config.fallbackModels,
12616
13189
  language: config.language ?? deps.language,
12617
13190
  rawProviderFactory: deps.primerRawProviderFactory,
13191
+ tokenBudget: config.tokenBudget,
13192
+ onBudgetUpdate,
12618
13193
  onProgress: (processed) => reportProgress(processed)
12619
13194
  });
12620
13195
  recordRun("completed", null);
@@ -12645,6 +13220,7 @@ function createDreamTaskExecutor(deps) {
12645
13220
  if (config.task === "retrospective") {
12646
13221
  const memoryBefore = getMemoryCountsByStatus(db, projectIdentity);
12647
13222
  const retro = await runRetrospectiveTask(config, ctx, {
13223
+ onBudgetUpdate,
12648
13224
  deps,
12649
13225
  deadline,
12650
13226
  parent,
@@ -12652,7 +13228,10 @@ function createDreamTaskExecutor(deps) {
12652
13228
  moduleRoute
12653
13229
  });
12654
13230
  recordRun("completed", null, {
12655
- memoryChanges: computeMemoryDelta(memoryBefore)
13231
+ memoryChanges: computeMemoryDelta(memoryBefore),
13232
+ backlogAfter: retro.retrospectiveWatermarkMs == null ? undefined : getDreamTaskBacklog(db, projectIdentity, "retrospective", {
13233
+ retrospectiveWatermarkMs: retro.retrospectiveWatermarkMs
13234
+ })
12656
13235
  });
12657
13236
  return {
12658
13237
  status: "completed",
@@ -12665,6 +13244,7 @@ function createDreamTaskExecutor(deps) {
12665
13244
  };
12666
13245
  }
12667
13246
  return await runAgenticTask(config, ctx, {
13247
+ onBudgetUpdate,
12668
13248
  deps,
12669
13249
  deadline,
12670
13250
  parent,
@@ -12675,6 +13255,9 @@ function createDreamTaskExecutor(deps) {
12675
13255
  moduleRoute
12676
13256
  });
12677
13257
  } catch (error) {
13258
+ if (error instanceof DreamTokenBudgetExceeded) {
13259
+ budgetSpent = Math.max(budgetSpent, error.spent);
13260
+ }
12678
13261
  const classified = classifyFailure(error);
12679
13262
  const retrospectiveOverflow = error instanceof RetrospectivePromptOverflowError ? error : null;
12680
13263
  const transient = retrospectiveOverflow ? true : classified.transient;
@@ -12833,7 +13416,7 @@ function resolveRetrospectiveUsableInputTokens(config, deps) {
12833
13416
  }
12834
13417
  async function runRetrospectiveTask(config, ctx, helpers) {
12835
13418
  const { db, projectIdentity, holderId, leaseKey } = ctx;
12836
- const { deps, deadline, parent } = helpers;
13419
+ const { deps, deadline, parent, onBudgetUpdate } = helpers;
12837
13420
  const provider = resolveRetrospectiveProvider(deps, db, projectIdentity);
12838
13421
  if (!provider) {
12839
13422
  log("[dreamer] retrospective: no raw provider available — clean no-op");
@@ -12887,7 +13470,8 @@ async function runRetrospectiveTask(config, ctx, helpers) {
12887
13470
  ],
12888
13471
  timeoutMs: Math.max(1, deadline - Date.now()),
12889
13472
  title: "magic-context-dream-retrospective",
12890
- directory: deps.sessionDirectory
13473
+ directory: deps.sessionDirectory,
13474
+ metadata: { tokenBudget: config.tokenBudget, onBudgetUpdate }
12891
13475
  });
12892
13476
  childSessionId = hiddenHandle.id;
12893
13477
  } else {
@@ -12923,6 +13507,12 @@ async function runRetrospectiveTask(config, ctx, helpers) {
12923
13507
  signal: abortController.signal,
12924
13508
  fallbackModels: config.fallbackModels,
12925
13509
  callContext: "dreamer:retrospective",
13510
+ ...!hiddenHandle && deps.client ? {
13511
+ transport: createPromptAsyncTransport(deps.client, sessionId, {
13512
+ tokenBudget: config.tokenBudget,
13513
+ onBudgetUpdate
13514
+ })
13515
+ } : {},
12926
13516
  ...hiddenHandle && deps.hiddenCompletionExecutor ? {
12927
13517
  transport: Object.assign((request) => deps.hiddenCompletionExecutor?.attempt(hiddenHandle, request) ?? Promise.reject(new Error("Hidden transport unavailable")), { childSessionId: hiddenHandle.childSessionId })
12928
13518
  } : {},
@@ -13098,7 +13688,7 @@ async function runRetrospectiveTask(config, ctx, helpers) {
13098
13688
  }
13099
13689
  async function runAgenticTask(config, ctx, helpers) {
13100
13690
  const { db, projectIdentity, holderId, leaseKey } = ctx;
13101
- const { deps, deadline, parent } = helpers;
13691
+ const { deps, deadline, parent, onBudgetUpdate } = helpers;
13102
13692
  const task = config.task;
13103
13693
  const docsDir = deps.sessionDirectory;
13104
13694
  const invocationStartedAt = Date.now();
@@ -13152,122 +13742,178 @@ async function runAgenticTask(config, ctx, helpers) {
13152
13742
  return { status: "completed", detail: progress };
13153
13743
  }
13154
13744
  }
13155
- if (task === "maintain-docs" && (hasCurrentDocsProposal(docsDir) || !changes)) {
13156
- const reason = hasCurrentDocsProposal(docsDir) ? "pending proposal matches current docs" : "no relevant commits or no git repository";
13157
- log(`[dreamer] maintain-docs skipped: ${reason}`);
13745
+ if (task === "maintain-docs" && !changes) {
13746
+ const reason = "no relevant commits or no git repository";
13158
13747
  helpers.recordRun("completed", null, { progress: reason });
13159
13748
  return { status: "completed", detail: reason };
13160
13749
  }
13161
- const taskPrompt = buildDreamTaskPrompt(task, {
13162
- projectPath: projectIdentity,
13163
- lastDreamAt: lastRunAt ? String(lastRunAt) : null,
13164
- existingDocs,
13165
- docsChangeSet: changes?.text,
13166
- docsBudget: config.docsMaxTokens ?? 12000,
13167
- docsCurrentTokens: maintainDocsSnapshot ? Math.ceil([...maintainDocsSnapshot.values()].join("").length / 3.5) : 0,
13168
- curate: curateMemories && curateCategory ? { category: curateCategory, memories: curateMemories } : undefined
13169
- });
13170
- let run;
13171
- if (deps.hiddenCompletionExecutor) {
13172
- const hidden = await runHiddenSingleShotPrompt({
13173
- executor: deps.hiddenCompletionExecutor,
13174
- parentSessionId: parent ?? undefined,
13175
- sessionDirectory: docsDir,
13176
- agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
13177
- system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : CURATE_SYSTEM_PROMPT,
13178
- prompt: taskPrompt,
13179
- title: `magic-context-dream-${task}`,
13180
- callContext: `dreamer:${task}`,
13181
- allowEmpty: task === "curate",
13182
- model: config.model,
13183
- fallbackModels: config.fallbackModels,
13184
- language: task === "curate" ? config.language ?? deps.language : undefined,
13185
- timeoutMs: Math.min(Math.max(0, deadline - Date.now()), config.timeoutMinutes * 60 * 1000),
13186
- signal: abortController.signal,
13187
- parse: (text, completion) => {
13188
- if (task !== "curate")
13189
- return text;
13190
- const memoryOperations = inspectCurateMemoryOperations(completion.messages);
13191
- if (text)
13192
- validateCurateAssistantText(text);
13193
- if (memoryOperations.totalCalls > 0 && memoryOperations.completedActions.length === 0)
13194
- throw new Error("Curate returned no completed ctx_memory tool result.");
13195
- if (!text && memoryOperations.completedActions.length === 0)
13196
- throw new Error("Dreamer returned no assistant output.");
13197
- return { text: text || null, memoryOperations };
13198
- }
13199
- });
13200
- childSessionId = hidden.childSessionId;
13201
- run = { output: hidden.completion.messages ?? [], validated: hidden.validated };
13202
- promptSettled = true;
13203
- } else {
13204
- const createResponse = await createChildSessionWithFence({
13205
- client: requireDreamClient(deps.client),
13206
- db,
13207
- parentSessionId: parent ?? undefined,
13208
- title: `magic-context-dream-${task}`,
13209
- directory: docsDir
13210
- });
13211
- const created = normalizeSDKResponse(createResponse, null, {
13212
- preferResponseOnMissingData: true
13750
+ if (task === "maintain-docs" && changes && (changes.unchanged || !changes.relevant)) {
13751
+ const reason = changes.unchanged ? "no commits since last run" : "diff does not describe a documented area";
13752
+ helpers.recordRun("completed", null, { progress: reason });
13753
+ return {
13754
+ status: "completed",
13755
+ detail: reason,
13756
+ schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) }
13757
+ };
13758
+ }
13759
+ if (task === "maintain-docs" && hasCurrentDocsProposal(docsDir)) {
13760
+ const reason = "pending proposal matches current docs";
13761
+ helpers.recordRun("completed", null, { progress: reason });
13762
+ return {
13763
+ status: "completed",
13764
+ detail: reason,
13765
+ ...changes ? { schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) } } : {}
13766
+ };
13767
+ }
13768
+ const curateChunks = curateMemories ? chunkCurateMemories(curateMemories, Math.max(4000, Math.floor(resolveRetrospectiveUsableInputTokens(config, deps) * 2))) : [];
13769
+ const batches = task === "curate" ? curateChunks : [{ memories: [], crossChunkCandidates: [] }];
13770
+ const completedActions = [];
13771
+ let curateRefused = 0;
13772
+ let run = {
13773
+ output: [],
13774
+ validated: ""
13775
+ };
13776
+ for (const batch of batches) {
13777
+ const taskPrompt = buildDreamTaskPrompt(task, {
13778
+ projectPath: projectIdentity,
13779
+ lastDreamAt: lastRunAt ? String(lastRunAt) : null,
13780
+ existingDocs,
13781
+ docsChangeSet: changes?.text,
13782
+ docsContents: task === "maintain-docs" ? {
13783
+ architecture: existingDocs?.architecture ? readFileSync5(`${docsDir}/ARCHITECTURE.md`, "utf8") : "(missing)",
13784
+ structure: existingDocs?.structure ? readFileSync5(`${docsDir}/STRUCTURE.md`, "utf8") : "(missing)"
13785
+ } : undefined,
13786
+ docsBudget: config.docsMaxTokens ?? 12000,
13787
+ docsCurrentTokens: maintainDocsSnapshot ? Math.ceil([...maintainDocsSnapshot.values()].join("").length / 3.5) : 0,
13788
+ curate: curateMemories && curateCategory ? {
13789
+ category: curateCategory,
13790
+ memories: batch.memories,
13791
+ crossChunkCandidates: batch.crossChunkCandidates
13792
+ } : undefined
13213
13793
  });
13214
- childSessionId = typeof created?.id === "string" ? created.id : null;
13215
- if (!childSessionId)
13216
- throw new Error("Dreamer could not create its child session.");
13217
- const sessionId = childSessionId;
13218
- const remainingMs = Math.max(0, deadline - Date.now());
13219
- run = await promptSyncWithValidatedOutputRetry(requireDreamClient(deps.client), {
13220
- path: { id: sessionId },
13221
- query: { directory: docsDir },
13222
- body: {
13794
+ if (deps.hiddenCompletionExecutor) {
13795
+ const hidden = await runHiddenSingleShotPrompt({
13796
+ executor: deps.hiddenCompletionExecutor,
13797
+ parentSessionId: parent ?? undefined,
13798
+ sessionDirectory: docsDir,
13223
13799
  agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
13224
- system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : withContentLanguageDirective(CURATE_SYSTEM_PROMPT, config.language ?? deps.language),
13225
- ...modelBodyField(config.model),
13226
- parts: [{ type: "text", text: taskPrompt, synthetic: true }]
13227
- }
13228
- }, {
13229
- timeoutMs: Math.min(remainingMs, config.timeoutMinutes * 60 * 1000),
13230
- signal: abortController.signal,
13231
- fallbackModels: config.fallbackModels,
13232
- callContext: `dreamer:${task}`,
13233
- fetchOutput: async () => {
13234
- const messagesResponse = await requireDreamClient(deps.client).session.messages({
13235
- path: { id: sessionId },
13236
- query: {
13237
- directory: docsDir,
13238
- ...task === "curate" ? {} : { limit: 50 }
13800
+ system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : CURATE_SYSTEM_PROMPT,
13801
+ prompt: taskPrompt,
13802
+ title: `magic-context-dream-${task}`,
13803
+ callContext: `dreamer:${task}`,
13804
+ metadata: { tokenBudget: config.tokenBudget, onBudgetUpdate },
13805
+ allowEmpty: task === "curate",
13806
+ model: config.model,
13807
+ fallbackModels: config.fallbackModels,
13808
+ language: task === "curate" ? config.language ?? deps.language : undefined,
13809
+ timeoutMs: Math.min(Math.max(0, deadline - Date.now()), config.timeoutMinutes * 60 * 1000),
13810
+ signal: abortController.signal,
13811
+ parse: (text, completion) => {
13812
+ if (task !== "curate")
13813
+ return text;
13814
+ const memoryOperations = inspectCurateMemoryOperations(completion.messages);
13815
+ if (text)
13816
+ validateCurateAssistantText(text);
13817
+ if (memoryOperations.totalCalls > 0 && memoryOperations.completedActions.length === 0)
13818
+ throw new Error("Curate returned no completed ctx_memory tool result.");
13819
+ if (!text && memoryOperations.completedActions.length === 0)
13820
+ throw new Error("Dreamer returned no assistant output.");
13821
+ return { text: text || null, memoryOperations };
13822
+ }
13823
+ });
13824
+ childSessionId = hidden.childSessionId;
13825
+ run = { output: hidden.completion.messages ?? [], validated: hidden.validated };
13826
+ promptSettled = true;
13827
+ } else {
13828
+ const createResponse = await createChildSessionWithFence({
13829
+ client: requireDreamClient(deps.client),
13830
+ db,
13831
+ parentSessionId: parent ?? undefined,
13832
+ title: `magic-context-dream-${task}`,
13833
+ directory: docsDir
13834
+ });
13835
+ const created = normalizeSDKResponse(createResponse, null, {
13836
+ preferResponseOnMissingData: true
13837
+ });
13838
+ childSessionId = typeof created?.id === "string" ? created.id : null;
13839
+ if (!childSessionId)
13840
+ throw new Error("Dreamer could not create its child session.");
13841
+ const sessionId = childSessionId;
13842
+ const remainingMs = Math.max(0, deadline - Date.now());
13843
+ run = await promptSyncWithValidatedOutputRetry(requireDreamClient(deps.client), {
13844
+ path: { id: sessionId },
13845
+ query: { directory: docsDir },
13846
+ body: {
13847
+ agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
13848
+ system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : withContentLanguageDirective(CURATE_SYSTEM_PROMPT, config.language ?? deps.language),
13849
+ ...modelBodyField(config.model),
13850
+ parts: [{ type: "text", text: taskPrompt, synthetic: true }]
13851
+ }
13852
+ }, {
13853
+ timeoutMs: Math.min(remainingMs, config.timeoutMinutes * 60 * 1000),
13854
+ signal: abortController.signal,
13855
+ fallbackModels: config.fallbackModels,
13856
+ callContext: `dreamer:${task}`,
13857
+ transport: createPromptAsyncTransport(requireDreamClient(deps.client), sessionId, { tokenBudget: config.tokenBudget, onBudgetUpdate }),
13858
+ fetchOutput: async () => {
13859
+ const messagesResponse = await requireDreamClient(deps.client).session.messages({
13860
+ path: { id: sessionId },
13861
+ query: {
13862
+ directory: docsDir,
13863
+ ...task === "curate" ? {} : { limit: 50 }
13864
+ }
13865
+ });
13866
+ return normalizeSDKResponse(messagesResponse, [], {
13867
+ preferResponseOnMissingData: true
13868
+ });
13869
+ },
13870
+ validateOutput: (messages) => {
13871
+ const text = extractLatestAssistantText(messages);
13872
+ if (task !== "curate") {
13873
+ if (!text)
13874
+ throw new Error("Dreamer returned no assistant output.");
13875
+ return text;
13876
+ }
13877
+ const memoryOperations = inspectCurateMemoryOperations(messages);
13878
+ if (text)
13879
+ validateCurateAssistantText(text);
13880
+ if (memoryOperations.completedActions.length > 0) {
13881
+ return { text, memoryOperations };
13239
13882
  }
13240
- });
13241
- return normalizeSDKResponse(messagesResponse, [], {
13242
- preferResponseOnMissingData: true
13243
- });
13244
- },
13245
- validateOutput: (messages) => {
13246
- const text = extractLatestAssistantText(messages);
13247
- if (task !== "curate") {
13248
13883
  if (!text)
13249
13884
  throw new Error("Dreamer returned no assistant output.");
13250
- return text;
13251
- }
13252
- const memoryOperations = inspectCurateMemoryOperations(messages);
13253
- if (text)
13254
- validateCurateAssistantText(text);
13255
- if (memoryOperations.completedActions.length > 0) {
13885
+ if (memoryOperations.totalCalls > 0) {
13886
+ throw new Error("Curate returned no completed ctx_memory tool result.");
13887
+ }
13256
13888
  return { text, memoryOperations };
13257
13889
  }
13258
- if (!text)
13259
- throw new Error("Dreamer returned no assistant output.");
13260
- if (memoryOperations.totalCalls > 0) {
13261
- throw new Error("Curate returned no completed ctx_memory tool result.");
13262
- }
13263
- return { text, memoryOperations };
13264
- }
13265
- });
13266
- promptSettled = true;
13890
+ });
13891
+ promptSettled = true;
13892
+ }
13893
+ if (task === "curate") {
13894
+ completedActions.push(...run.validated.memoryOperations.completedActions);
13895
+ curateRefused += takeCurateSafetyRefusalCount(childSessionId ?? "");
13896
+ }
13897
+ if (childSessionId && !deps.hiddenCompletionExecutor) {
13898
+ await teardownChildSession({
13899
+ client: requireDreamClient(deps.client),
13900
+ sessionId: childSessionId,
13901
+ sessionDirectory: docsDir,
13902
+ promptSettled,
13903
+ privacySensitive: true,
13904
+ context: `[dreamer] ${task}`,
13905
+ log
13906
+ });
13907
+ childSessionId = null;
13908
+ }
13267
13909
  }
13910
+ if (task === "curate")
13911
+ run.validated = {
13912
+ text: null,
13913
+ memoryOperations: { totalCalls: completedActions.length, completedActions }
13914
+ };
13268
13915
  if (leaseLost)
13269
13916
  throw new Error("Dream lease lost during task");
13270
- const curateRefused = task === "curate" ? takeCurateSafetyRefusalCount(childSessionId ?? "") : 0;
13271
13917
  if (curateRefused > 0) {
13272
13918
  helpers.reportProgress(curateRefused, curateRefused);
13273
13919
  log(`[dreamer] curate safety summary: refused=${curateRefused}`);
@@ -13288,7 +13934,11 @@ async function runAgenticTask(config, ctx, helpers) {
13288
13934
  try {
13289
13935
  if (run.validated === "[]") {
13290
13936
  helpers.recordRun("completed", null, { progress: "no corrections proposed" });
13291
- return { status: "completed", detail: "no corrections proposed" };
13937
+ return {
13938
+ status: "completed",
13939
+ detail: "no corrections proposed",
13940
+ schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) }
13941
+ };
13292
13942
  }
13293
13943
  const path = writeDocsProposal(docsDir, String(run.validated), config.docsMaxTokens ?? 12000, docsHashes, changes.head);
13294
13944
  helpers.recordRun("completed", null, { progress: `proposal: ${path}` });
@@ -13506,7 +14156,7 @@ async function runDomainGroup(deps, group, cb) {
13506
14156
  projectIdentity,
13507
14157
  lastRunAt: readLastRunAt(db, projectIdentity, due.config.task),
13508
14158
  retrospectiveWatermarkMs: readRetrospectiveWatermark(db, projectIdentity, due.config.task),
13509
- promotionThreshold: due.config.promotionThreshold ?? 3
14159
+ promotionThreshold: due.config.promotionThreshold ?? (due.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
13510
14160
  });
13511
14161
  if (!gatePass) {
13512
14162
  advanceAfterRun(db, projectIdentity, due, Date.now(), "skipped", null);
@@ -13586,7 +14236,7 @@ async function runManualDream(deps) {
13586
14236
  projectIdentity: deps.projectIdentity,
13587
14237
  lastRunAt: readLastRunAt(deps.db, deps.projectIdentity, d.config.task),
13588
14238
  retrospectiveWatermarkMs: readRetrospectiveWatermark(deps.db, deps.projectIdentity, d.config.task),
13589
- promotionThreshold: d.config.promotionThreshold ?? 3
14239
+ promotionThreshold: d.config.promotionThreshold ?? (d.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
13590
14240
  });
13591
14241
  if (pass)
13592
14242
  gated.push(d);
@@ -13642,7 +14292,7 @@ async function runDueTasksForProject(deps) {
13642
14292
  projectIdentity: deps.projectIdentity,
13643
14293
  lastRunAt: readLastRunAt(deps.db, deps.projectIdentity, d.config.task),
13644
14294
  retrospectiveWatermarkMs: readRetrospectiveWatermark(deps.db, deps.projectIdentity, d.config.task),
13645
- promotionThreshold: d.config.promotionThreshold ?? 3
14295
+ promotionThreshold: d.config.promotionThreshold ?? (d.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
13646
14296
  });
13647
14297
  if (pass) {
13648
14298
  gated.push(d);
@@ -13846,6 +14496,205 @@ function clearDreamerTickFailure(db) {
13846
14496
  db.prepare("DELETE FROM schema_migrations_meta WHERE key = ?").run(DREAMER_TICK_FAILURE_KEY);
13847
14497
  }
13848
14498
 
14499
+ // ../plugin/src/hooks/magic-context/lkg-persist.ts
14500
+ import { createHash as createHash11 } from "node:crypto";
14501
+ function parseStringArray(value) {
14502
+ if (typeof value !== "string")
14503
+ return null;
14504
+ let parsed;
14505
+ try {
14506
+ parsed = JSON.parse(value);
14507
+ } catch {
14508
+ return null;
14509
+ }
14510
+ if (!Array.isArray(parsed))
14511
+ return null;
14512
+ const result = [];
14513
+ for (const entry of parsed) {
14514
+ if (typeof entry !== "string" || entry.length === 0)
14515
+ return null;
14516
+ result.push(entry);
14517
+ }
14518
+ return result;
14519
+ }
14520
+ function parseNullableString(value) {
14521
+ if (value === null || value === undefined)
14522
+ return null;
14523
+ return typeof value === "string" ? value : undefined;
14524
+ }
14525
+ function parseNullableInteger(value) {
14526
+ if (value === null || value === undefined)
14527
+ return;
14528
+ return typeof value === "number" && Number.isSafeInteger(value) ? value : undefined;
14529
+ }
14530
+ function parsePersistedLkgSlot(row) {
14531
+ if (!row || typeof row !== "object")
14532
+ return;
14533
+ const record = row;
14534
+ const jsonPrefix = record.json_prefix;
14535
+ const lastInputMessageId = record.last_input_message_id;
14536
+ const capturedAt = record.captured_at;
14537
+ let inputIdsRaw = record.input_id_seq;
14538
+ let piOutputEntryIds;
14539
+ if (typeof inputIdsRaw === "string") {
14540
+ try {
14541
+ const metadata = JSON.parse(inputIdsRaw);
14542
+ if (metadata && !Array.isArray(metadata) && metadata.version === 1 && Array.isArray(metadata.piOutputEntryIds)) {
14543
+ if (!metadata.piOutputEntryIds.every((id) => id === null || typeof id === "string" && id.length > 0))
14544
+ return;
14545
+ piOutputEntryIds = metadata.piOutputEntryIds;
14546
+ inputIdsRaw = JSON.stringify(metadata.inputIds);
14547
+ }
14548
+ } catch {
14549
+ return;
14550
+ }
14551
+ }
14552
+ const inputIdSeq = parseStringArray(inputIdsRaw);
14553
+ const inputContentDigests = parseStringArray(record.input_content_digests);
14554
+ const modelKey = parseNullableString(record.model_key);
14555
+ const providerKey = parseNullableString(record.provider_key);
14556
+ if (typeof jsonPrefix !== "string" || typeof lastInputMessageId !== "string" || lastInputMessageId.length === 0 || typeof capturedAt !== "number" || !Number.isFinite(capturedAt) || inputIdSeq === null || inputContentDigests === null || inputContentDigests.length !== inputIdSeq.length || modelKey === undefined || providerKey === undefined) {
14557
+ return;
14558
+ }
14559
+ if (piOutputEntryIds) {
14560
+ try {
14561
+ const output = JSON.parse(jsonPrefix);
14562
+ const inputs = new Set(inputIdSeq);
14563
+ if (!Array.isArray(output) || output.length !== piOutputEntryIds.length || piOutputEntryIds.some((id) => id !== null && !inputs.has(id)))
14564
+ return;
14565
+ } catch {
14566
+ return;
14567
+ }
14568
+ }
14569
+ let inputContentSignatures;
14570
+ if (record.input_content_signatures !== null && record.input_content_signatures !== undefined) {
14571
+ const parsed = parseStringArray(record.input_content_signatures);
14572
+ if (parsed === null || parsed.length !== inputIdSeq.length)
14573
+ return;
14574
+ inputContentSignatures = parsed;
14575
+ }
14576
+ const slot = {
14577
+ jsonPrefix,
14578
+ inputIdSeq,
14579
+ inputContentDigests,
14580
+ lastInputMessageId,
14581
+ modelKey,
14582
+ providerKey,
14583
+ capturedAt
14584
+ };
14585
+ if (inputContentSignatures)
14586
+ slot.inputContentSignatures = inputContentSignatures;
14587
+ if (piOutputEntryIds)
14588
+ slot.piOutputEntryIds = piOutputEntryIds;
14589
+ const rowVersion = parseNullableInteger(record.row_version);
14590
+ if (rowVersion !== undefined)
14591
+ slot.rowVersion = rowVersion;
14592
+ const captureSequence = parseNullableInteger(record.capture_sequence);
14593
+ if (captureSequence !== undefined)
14594
+ slot.captureSequence = captureSequence;
14595
+ return slot;
14596
+ }
14597
+ var persistedFingerprints = new WeakMap;
14598
+ function saveLkgSlotToDb(db, sessionId, slot) {
14599
+ const fingerprint = createHash11("sha256").update(JSON.stringify([
14600
+ slot.jsonPrefix,
14601
+ slot.inputIdSeq,
14602
+ slot.inputContentDigests,
14603
+ slot.inputContentSignatures,
14604
+ slot.piOutputEntryIds,
14605
+ slot.lastInputMessageId,
14606
+ slot.modelKey,
14607
+ slot.providerKey,
14608
+ slot.rowVersion,
14609
+ slot.captureSequence
14610
+ ])).digest("hex");
14611
+ let saved = persistedFingerprints.get(db);
14612
+ if (saved?.get(sessionId) === fingerprint)
14613
+ return true;
14614
+ try {
14615
+ db.prepare(`INSERT INTO lkg_slots (
14616
+ session_id, json_prefix, input_id_seq, input_content_digests,
14617
+ input_content_signatures, last_input_message_id, model_key, provider_key,
14618
+ captured_at, row_version, capture_sequence
14619
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
14620
+ ON CONFLICT(session_id) DO UPDATE SET
14621
+ json_prefix = excluded.json_prefix,
14622
+ input_id_seq = excluded.input_id_seq,
14623
+ input_content_digests = excluded.input_content_digests,
14624
+ input_content_signatures = excluded.input_content_signatures,
14625
+ last_input_message_id = excluded.last_input_message_id,
14626
+ model_key = excluded.model_key,
14627
+ provider_key = excluded.provider_key,
14628
+ captured_at = excluded.captured_at,
14629
+ row_version = excluded.row_version,
14630
+ capture_sequence = excluded.capture_sequence`).run(sessionId, slot.jsonPrefix, JSON.stringify(slot.piOutputEntryIds ? {
14631
+ version: 1,
14632
+ inputIds: slot.inputIdSeq,
14633
+ piOutputEntryIds: slot.piOutputEntryIds
14634
+ } : slot.inputIdSeq), JSON.stringify(slot.inputContentDigests), slot.inputContentSignatures ? JSON.stringify(slot.inputContentSignatures) : null, slot.lastInputMessageId, slot.modelKey, slot.providerKey, slot.capturedAt, slot.rowVersion ?? null, slot.captureSequence ?? null);
14635
+ if (!saved) {
14636
+ saved = new Map;
14637
+ persistedFingerprints.set(db, saved);
14638
+ }
14639
+ if (saved.size >= 1000) {
14640
+ const oldest = saved.keys().next().value;
14641
+ if (oldest !== undefined)
14642
+ saved.delete(oldest);
14643
+ }
14644
+ saved.set(sessionId, fingerprint);
14645
+ return true;
14646
+ } catch (error) {
14647
+ sessionLog(sessionId, "LKG snapshot persistence failed (in-memory slot retained):", error);
14648
+ return false;
14649
+ }
14650
+ }
14651
+ function clearPersistedLkgSlot(db, sessionId) {
14652
+ try {
14653
+ db.prepare("DELETE FROM lkg_slots WHERE session_id = ?").run(sessionId);
14654
+ persistedFingerprints.get(db)?.delete(sessionId);
14655
+ } catch (error) {
14656
+ sessionLog(sessionId, "LKG snapshot durable clear failed:", error);
14657
+ }
14658
+ }
14659
+ function loadPersistedLkgSlot(db, sessionId) {
14660
+ let row;
14661
+ try {
14662
+ row = db.prepare("SELECT * FROM lkg_slots WHERE session_id = ?").get(sessionId);
14663
+ } catch (error) {
14664
+ sessionLog(sessionId, "LKG snapshot durable load failed:", error);
14665
+ return;
14666
+ }
14667
+ if (!row)
14668
+ return;
14669
+ const slot = parsePersistedLkgSlot(row);
14670
+ if (!slot) {
14671
+ clearPersistedLkgSlot(db, sessionId);
14672
+ return;
14673
+ }
14674
+ return slot;
14675
+ }
14676
+ function pruneStaleLkgSlots(db, now = Date.now()) {
14677
+ const previousTimeout = db.prepare("PRAGMA busy_timeout").get();
14678
+ try {
14679
+ db.exec("PRAGMA busy_timeout = 0");
14680
+ const result = db.prepare(`DELETE FROM lkg_slots WHERE captured_at < ? AND NOT EXISTS (
14681
+ SELECT 1 FROM session_projects sp
14682
+ WHERE sp.session_id = lkg_slots.session_id AND sp.updated_at >= ?
14683
+ )`).run(now - 7 * 24 * 60 * 60 * 1000, now - 7 * 24 * 60 * 60 * 1000);
14684
+ if (result.changes)
14685
+ persistedFingerprints.delete(db);
14686
+ return result.changes;
14687
+ } finally {
14688
+ db.exec(`PRAGMA busy_timeout = ${Number(previousTimeout.timeout) || 0}`);
14689
+ }
14690
+ }
14691
+ function createDbLkgPersistence(db) {
14692
+ return {
14693
+ load: (sessionId) => loadPersistedLkgSlot(db, sessionId),
14694
+ clear: (sessionId) => clearPersistedLkgSlot(db, sessionId)
14695
+ };
14696
+ }
14697
+
13849
14698
  // ../plugin/src/plugin/dream-timer.ts
13850
14699
  var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
13851
14700
  var GIT_COMMIT_BACKLOG_DRAIN_MAX_MS = 5 * 60 * 1000;
@@ -14008,6 +14857,11 @@ function persistTickOutcome(db, failure) {
14008
14857
  }
14009
14858
  }
14010
14859
  async function runMessageHistoryMaintenance(db) {
14860
+ try {
14861
+ pruneStaleLkgSlots(db);
14862
+ } catch (error) {
14863
+ log("[magic-context] LKG pruning deferred:", error);
14864
+ }
14011
14865
  const cleanup = retryPendingSessionCleanups(db);
14012
14866
  if (cleanup.cleared > 0 || cleanup.failedSessionIds.length > 0) {
14013
14867
  log(`[message-index] pending session cleanup: cleared=${cleanup.cleared} failed=${cleanup.failedSessionIds.length}`);
@@ -14269,7 +15123,7 @@ import * as childProcess from "node:child_process";
14269
15123
  import {
14270
15124
  existsSync as existsSync7,
14271
15125
  mkdtempSync as mkdtempSync2,
14272
- readFileSync as readFileSync5,
15126
+ readFileSync as readFileSync6,
14273
15127
  realpathSync as realpathSync3,
14274
15128
  rmSync as rmSync2,
14275
15129
  statSync as statSync4,
@@ -14298,7 +15152,7 @@ var DEFAULT_PI_RESOLUTION_FS = {
14298
15152
  existsSync: existsSync7,
14299
15153
  statSync: statSync4,
14300
15154
  realpathSync: realpathSync3,
14301
- readFileSync: readFileSync5
15155
+ readFileSync: readFileSync6
14302
15156
  };
14303
15157
  function packageHarnessKind(packageName) {
14304
15158
  if (packageName === "@oh-my-pi/pi-coding-agent")
@@ -14595,12 +15449,6 @@ var DREAMER_ACTION_AGENTS = new Set([
14595
15449
  "dreamer",
14596
15450
  "magic-context-dreamer"
14597
15451
  ]);
14598
- var HISTORIAN_AGENTS = new Set([
14599
- "magic-context-historian",
14600
- "historian",
14601
- "historian-recomp",
14602
- "historian-editor"
14603
- ]);
14604
15452
  var SEARCH_ONLY_SUBAGENT_TOOL_AGENTS = new Set([
14605
15453
  "dreamer-retrospective",
14606
15454
  "dreamer-primer-investigator"
@@ -14790,7 +15638,15 @@ class PiSubagentRunner {
14790
15638
  async runOnce(options, runMode, modelRefOverride) {
14791
15639
  const startTime = Date.now();
14792
15640
  let recordedAccounting = false;
15641
+ const cap = subagentStepCap(options.agent);
15642
+ const budget = options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined;
15643
+ const telemetry = new SubagentTelemetry(options.systemPrompt, (line) => sessionLog(options.accountingSessionId ?? "subagent", line));
15644
+ let loggedTelemetry = false;
14793
15645
  const recordAccounting = (result, messages = []) => {
15646
+ if (!loggedTelemetry) {
15647
+ loggedTelemetry = true;
15648
+ telemetry.finish(options.agent, result.ok ? "completed" : result.reason, cap);
15649
+ }
14794
15650
  if (!options.accountingSessionId || recordedAccounting)
14795
15651
  return;
14796
15652
  recordedAccounting = true;
@@ -14846,7 +15702,9 @@ class PiSubagentRunner {
14846
15702
  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.`);
14847
15703
  }
14848
15704
  const promptBytes = Buffer.byteLength(options.userMessage, "utf8");
14849
- const deliverViaStdin = promptBytes > PROMPT_ARGV_MAX_BYTES || this.platform === "win32";
15705
+ const budgetToolGuardAvailable = SUBAGENT_ENTRY_PATH !== undefined || this.subagentExtensions?.some((extension) => basename(extension) === "subagent-entry.js") === true;
15706
+ const rpcBudget = options.tokenBudget !== undefined && this.invocation.targetHarness === "pi" && budgetToolGuardAvailable;
15707
+ const deliverViaStdin = !rpcBudget && (promptBytes > PROMPT_ARGV_MAX_BYTES || this.platform === "win32");
14850
15708
  let systemPromptTempDir;
14851
15709
  let systemPromptPath;
14852
15710
  const cleanupSystemPromptFile = () => {
@@ -14869,13 +15727,30 @@ class PiSubagentRunner {
14869
15727
  return failBeforeSpawn("spawn_failed", `failed to prepare pi system prompt file: ${error instanceof Error ? error.message : String(error)}`);
14870
15728
  }
14871
15729
  }
15730
+ if (rpcBudget && !systemPromptTempDir) {
15731
+ try {
15732
+ systemPromptTempDir = mkdtempSync2(join5(tmpdir2(), "mc-pi-prompt-"));
15733
+ } catch (error) {
15734
+ return failBeforeSpawn("spawn_failed", `failed to prepare pi budget guard: ${error instanceof Error ? error.message : String(error)}`);
15735
+ }
15736
+ }
15737
+ const budgetGuardFile = rpcBudget && systemPromptTempDir ? join5(systemPromptTempDir, "budget-guard") : undefined;
15738
+ if (budgetGuardFile) {
15739
+ try {
15740
+ writeFileSync5(budgetGuardFile, "", "utf8");
15741
+ } catch (error) {
15742
+ cleanupSystemPromptFile();
15743
+ return failBeforeSpawn("spawn_failed", `failed to write pi budget guard: ${error instanceof Error ? error.message : String(error)}`);
15744
+ }
15745
+ }
14872
15746
  const args = buildArgs(options, {
14873
15747
  targetHarness: this.invocation.targetHarness,
14874
15748
  disableDiscoveredExtensions: runMode.disableDiscoveredExtensions,
14875
15749
  subagentExtensions: this.subagentExtensions,
14876
- omitPositionalMessage: deliverViaStdin,
15750
+ omitPositionalMessage: deliverViaStdin || rpcBudget,
14877
15751
  systemPromptPath,
14878
- modelRef: modelRefOverride
15752
+ modelRef: modelRefOverride,
15753
+ rpc: rpcBudget
14879
15754
  });
14880
15755
  return new Promise((resolve) => {
14881
15756
  let accountingMessages = [];
@@ -14884,13 +15759,26 @@ class PiSubagentRunner {
14884
15759
  if (settled)
14885
15760
  return;
14886
15761
  settled = true;
15762
+ if (accountingMessages.length > 0 && /historian|dreamer|classify/i.test(options.agent)) {
15763
+ const last = [...accountingMessages].reverse().find((message) => message !== null && typeof message === "object" && message.role === "assistant");
15764
+ const responseChars = result.ok ? result.assistantText.length : last?.content?.filter((part) => typeof part === "object" && part !== null && part.type === "text" && typeof part.text === "string").reduce((sum, part) => sum + part.text.length, 0) ?? 0;
15765
+ const tokenDetails = formatRunTokenLog(runTokenLog(last?.usage, options.maxOutputTokens, last?.stopReason));
15766
+ if (!result.ok && result.reason === "truncated") {
15767
+ result = { ...result, error: `${result.error}; ${tokenDetails}` };
15768
+ }
15769
+ sessionLog(options.accountingSessionId ?? "subagent", `${options.agent} response_chars=${responseChars} ${tokenDetails}`);
15770
+ }
15771
+ const outcome = budget ? {
15772
+ ...result,
15773
+ meta: { ...result.meta, tokenBudget: budget.snapshot() }
15774
+ } : result;
14887
15775
  cleanupSystemPromptFile();
14888
15776
  try {
14889
- recordAccounting(result, accountingMessages);
15777
+ recordAccounting(outcome, accountingMessages);
14890
15778
  } catch (err) {
14891
15779
  sessionLog(options.accountingSessionId ?? "subagent", `subagent accounting failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
14892
15780
  }
14893
- resolve(result);
15781
+ resolve(outcome);
14894
15782
  };
14895
15783
  const emitProgress = (event) => {
14896
15784
  if (!options.onProgress)
@@ -14902,10 +15790,14 @@ class PiSubagentRunner {
14902
15790
  let child;
14903
15791
  try {
14904
15792
  child = this.spawnImpl(this.invocation.command, [...this.invocation.prefixArgs, ...this.extraArgs, ...args], {
15793
+ windowsHide: true,
14905
15794
  cwd: options.cwd,
14906
15795
  env: {
14907
15796
  ...process.env,
14908
15797
  [MAGIC_CONTEXT_PI_SUBAGENT_ENV]: "1",
15798
+ MAGIC_CONTEXT_SUBAGENT_PROMPT_FILE: systemPromptPath,
15799
+ MAGIC_CONTEXT_SUBAGENT_BUDGET_GUARD_FILE: budgetGuardFile,
15800
+ MAGIC_CONTEXT_SUBAGENT_PROVENANCE_ONLY: /^(magic-context-)?historian(?:-recomp|-editor)?$/.test(options.agent) ? undefined : "1",
14909
15801
  ...options.temperature !== undefined ? {
14910
15802
  MAGIC_CONTEXT_HISTORIAN_TEMPERATURE: String(options.temperature)
14911
15803
  } : {},
@@ -14913,7 +15805,11 @@ class PiSubagentRunner {
14913
15805
  MAGIC_CONTEXT_HISTORIAN_MAX_OUTPUT_TOKENS: String(options.maxOutputTokens)
14914
15806
  } : {}
14915
15807
  },
14916
- stdio: [deliverViaStdin ? "pipe" : "ignore", "pipe", "pipe"]
15808
+ stdio: [
15809
+ deliverViaStdin || rpcBudget ? "pipe" : "ignore",
15810
+ "pipe",
15811
+ "pipe"
15812
+ ]
14917
15813
  });
14918
15814
  } catch (error) {
14919
15815
  cleanupSystemPromptFile();
@@ -14936,15 +15832,43 @@ class PiSubagentRunner {
14936
15832
  return;
14937
15833
  }
14938
15834
  emitProgress({ type: "spawned", argv: args, pid: child.pid });
14939
- if (deliverViaStdin && child.stdin) {
15835
+ if (rpcBudget && child.stdin) {
15836
+ child.stdin.on("error", (error) => {
15837
+ terminateChild(child);
15838
+ settle({
15839
+ ok: false,
15840
+ reason: budget?.snapshot().finalizeFired ? "token_budget" : "spawn_failed",
15841
+ error: `Pi RPC input failed: ${error.message}`,
15842
+ durationMs: Date.now() - startTime
15843
+ });
15844
+ });
15845
+ child.stdin.write(`${JSON.stringify({
15846
+ type: "prompt",
15847
+ message: options.userMessage,
15848
+ id: "dreamer-initial"
15849
+ })}
15850
+ `);
15851
+ } else if (deliverViaStdin && child.stdin) {
14940
15852
  child.stdin.on("error", () => {});
14941
15853
  try {
14942
15854
  child.stdin.end(options.userMessage, "utf8");
14943
15855
  } catch {}
14944
15856
  }
14945
15857
  let stderr = "";
15858
+ let stderrLine = "";
14946
15859
  child.stderr?.on("data", (chunk) => {
14947
15860
  const text = chunk.toString("utf8");
15861
+ stderrLine += text;
15862
+ const lines = stderrLine.split(`
15863
+ `);
15864
+ stderrLine = (lines.pop() ?? "").slice(-4096);
15865
+ for (const line of lines) {
15866
+ if (settled || !line.startsWith('{"type":"mc_system_prompt"'))
15867
+ continue;
15868
+ const parsed = parsePiEventLine(line);
15869
+ if (parsed.ok)
15870
+ telemetry.observe(parsed.event);
15871
+ }
14948
15872
  stderr += text;
14949
15873
  if (stderr.length > 16000) {
14950
15874
  stderr = `…[truncated]${stderr.slice(-16000)}`;
@@ -14980,7 +15904,7 @@ class PiSubagentRunner {
14980
15904
  const accumulatedMessages = [];
14981
15905
  accountingMessages = accumulatedMessages;
14982
15906
  rl.on("line", (line) => {
14983
- if (line.length === 0)
15907
+ if (settled || line.length === 0)
14984
15908
  return;
14985
15909
  const parsed = parsePiEventLine(line);
14986
15910
  if (!parsed.ok) {
@@ -14993,6 +15917,95 @@ class PiSubagentRunner {
14993
15917
  if (typeof event !== "object" || event === null)
14994
15918
  return;
14995
15919
  const e = event;
15920
+ if (rpcBudget && e.type === "response" && event.success === false) {
15921
+ const reply = event;
15922
+ if (reply.command === "prompt" || reply.command === "steer" && !sawAgentEnd) {
15923
+ terminateChild(child);
15924
+ settle({
15925
+ ok: false,
15926
+ reason: reply.command === "steer" ? "token_budget" : "model_failed",
15927
+ error: reply.error ?? `Pi RPC ${reply.command} failed`,
15928
+ durationMs: Date.now() - startTime
15929
+ });
15930
+ return;
15931
+ }
15932
+ }
15933
+ telemetry.observe(event);
15934
+ if (budget && e.type === "message_end") {
15935
+ const message = e.message;
15936
+ if (message?.role === "assistant") {
15937
+ const usage = message.usage;
15938
+ const finished = message.stopReason === "stop" && !(Array.isArray(message.content) && message.content.some((part) => typeof part === "object" && part !== null && part.type === "toolCall"));
15939
+ const decision = budget.charge(usage?.input ?? 0, usage?.cacheRead ?? 0, usage?.cacheWrite ?? 0, finished, rpcBudget);
15940
+ if (decision === "stop" || decision === "finalize" && !rpcBudget) {
15941
+ terminateChild(child);
15942
+ settle({
15943
+ ok: false,
15944
+ reason: "token_budget",
15945
+ error: "MC-D11: Pi dreamer exceeded its prompt-token budget",
15946
+ durationMs: Date.now() - startTime,
15947
+ meta: { tokenBudget: budget.snapshot() }
15948
+ });
15949
+ return;
15950
+ }
15951
+ if (decision === "finalize" && rpcBudget) {
15952
+ try {
15953
+ if (!budgetGuardFile)
15954
+ throw new Error("missing budget guard file");
15955
+ writeFileSync5(budgetGuardFile, "finalize", "utf8");
15956
+ } catch {
15957
+ terminateChild(child);
15958
+ settle({
15959
+ ok: false,
15960
+ reason: "token_budget",
15961
+ error: "MC-D11: Pi could not activate its tool guard",
15962
+ durationMs: Date.now() - startTime
15963
+ });
15964
+ return;
15965
+ }
15966
+ child.stdin?.write(`${JSON.stringify({
15967
+ type: "steer",
15968
+ message: TOKEN_BUDGET_FINALIZE_MESSAGE,
15969
+ id: "dreamer-finalize"
15970
+ })}
15971
+ `);
15972
+ }
15973
+ }
15974
+ }
15975
+ const resultParts = event.result?.content;
15976
+ if (budget?.snapshot().finalizeFired && e.type === "tool_execution_end" && resultParts?.some((part) => part.text === TOKEN_BUDGET_TOOL_REFUSAL)) {
15977
+ const refusal = budget.refuseTool();
15978
+ if (refusal?.hardStopped) {
15979
+ terminateChild(child);
15980
+ settle({
15981
+ ok: false,
15982
+ reason: "token_budget",
15983
+ error: "MC-D11: Pi dreamer made two calls after finalize",
15984
+ durationMs: Date.now() - startTime,
15985
+ meta: { tokenBudget: budget.snapshot() }
15986
+ });
15987
+ return;
15988
+ }
15989
+ }
15990
+ if (cap !== undefined && telemetry.steps > cap) {
15991
+ if (e.type === "message_end")
15992
+ accumulatedMessages.push(e.message);
15993
+ if (e.type === "agent_end" && Array.isArray(e.messages))
15994
+ accountingMessages = e.messages;
15995
+ if (timeoutHandle)
15996
+ clearTimeout(timeoutHandle);
15997
+ if (drainTimerHandle)
15998
+ clearTimeout(drainTimerHandle);
15999
+ terminateChild(child);
16000
+ settle({
16001
+ ok: false,
16002
+ reason: "step_limit",
16003
+ error: `MC-D10: Pi subagent ${options.agent} exceeded its ${cap}-step limit`,
16004
+ durationMs: Date.now() - startTime,
16005
+ meta: { cap, steps: telemetry.steps }
16006
+ });
16007
+ return;
16008
+ }
14996
16009
  const isFirstEvent = eventCount === 0;
14997
16010
  eventCount += 1;
14998
16011
  lastEventTimestamp = Date.now();
@@ -15306,9 +16319,9 @@ function buildArgs(options, opts) {
15306
16319
  const targetHarness = opts?.targetHarness ?? resolvePiHarnessKind();
15307
16320
  const ompTarget = targetHarness === "omp";
15308
16321
  const args = [
15309
- "--print",
16322
+ ...opts?.rpc ? [] : ["--print"],
15310
16323
  "--mode",
15311
- "json",
16324
+ opts?.rpc ? "rpc" : "json",
15312
16325
  "--no-session",
15313
16326
  "--no-skills",
15314
16327
  ...ompTarget ? ["--no-rules"] : ["--no-prompt-templates", "--no-context-files"]
@@ -15322,7 +16335,7 @@ function buildArgs(options, opts) {
15322
16335
  }
15323
16336
  }
15324
16337
  const subagentEntryPath = opts?.subagentEntryPath ?? SUBAGENT_ENTRY_PATH;
15325
- const shouldLoadSubagentExtension = subagentEntryPath && (SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
16338
+ const shouldLoadSubagentExtension = subagentEntryPath && (opts?.rpc || SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
15326
16339
  if (shouldLoadSubagentExtension) {
15327
16340
  args.push("--extension", subagentEntryPath);
15328
16341
  if (DREAMER_ACTION_AGENTS.has(options.agent)) {
@@ -15330,7 +16343,7 @@ function buildArgs(options, opts) {
15330
16343
  }
15331
16344
  }
15332
16345
  const historianCalibrationEntryPath = opts?.historianCalibrationEntryPath === undefined ? HISTORIAN_CALIBRATION_ENTRY_PATH : opts.historianCalibrationEntryPath;
15333
- if (HISTORIAN_AGENTS.has(options.agent) && historianCalibrationEntryPath) {
16346
+ if (subagentStepCap(options.agent) !== undefined && historianCalibrationEntryPath) {
15334
16347
  args.push("--extension", historianCalibrationEntryPath);
15335
16348
  }
15336
16349
  const strictTools = STRICT_TOOL_ALLOWLIST.get(options.agent);
@@ -15559,7 +16572,7 @@ function* sessionEntries(path) {
15559
16572
  }
15560
16573
 
15561
16574
  // src/dreamer/pi-session-api.ts
15562
- import { existsSync as existsSync8, readFileSync as readFileSync6, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
16575
+ import { existsSync as existsSync8, readFileSync as readFileSync7, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
15563
16576
  import {
15564
16577
  basename as basename2,
15565
16578
  dirname as dirname2,
@@ -15588,7 +16601,7 @@ function isScriptEntry(filePath) {
15588
16601
  }
15589
16602
  function readManifest(pkgJsonPath) {
15590
16603
  try {
15591
- const pkg = JSON.parse(readFileSync6(pkgJsonPath, "utf8"));
16604
+ const pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
15592
16605
  return pkg && typeof pkg === "object" ? pkg : null;
15593
16606
  } catch {
15594
16607
  return null;
@@ -15752,7 +16765,7 @@ async function loadDefaultPiSessionApi(loaders) {
15752
16765
  throw new Error("Pi session APIs unavailable: expected SessionManager.listAll on pi-coding-agent");
15753
16766
  }
15754
16767
  const loadEntriesFromFile = mod.loadEntriesFromFile ?? ((filePath) => {
15755
- const content = readFileSync6(filePath, "utf8");
16768
+ const content = readFileSync7(filePath, "utf8");
15756
16769
  return mod.parseSessionEntries?.(content) ?? [];
15757
16770
  });
15758
16771
  return {
@@ -15900,11 +16913,14 @@ class PiRetrospectiveRawProvider {
15900
16913
  continue;
15901
16914
  if (typeof info.cwd !== "string" || resolve2(info.cwd) !== projectCwd)
15902
16915
  continue;
16916
+ const activity = this.deps.contextDb ? readSessionActivity(this.deps.contextDb, info.id) : typeof info.modified === "number" ? info.modified : undefined;
16917
+ if (this.deps.contextDb && activity === undefined)
16918
+ continue;
15903
16919
  this.sessionPathById.set(info.id, info.path);
15904
16920
  result.push({
15905
16921
  sessionId: info.id,
15906
16922
  path: info.path,
15907
- updatedAt: typeof info.modified === "number" ? info.modified : undefined
16923
+ updatedAt: activity
15908
16924
  });
15909
16925
  }
15910
16926
  return result.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0));
@@ -16051,6 +17067,12 @@ var inFlightDreams = (() => {
16051
17067
  globals[PI_DREAMER_IN_FLIGHT] = dreams;
16052
17068
  return dreams;
16053
17069
  })();
17070
+ var dreamAbortControllers = new Map;
17071
+ function abortInFlightDreamers(registrationOwner) {
17072
+ for (const controller of dreamAbortControllers.get(registrationOwner) ?? []) {
17073
+ controller.abort();
17074
+ }
17075
+ }
16054
17076
  var sessionCounter = 0;
16055
17077
  var piSubagentRunnerFactory = () => new PiSubagentRunner;
16056
17078
  var startDreamScheduleTimerFn = startDreamScheduleTimer;
@@ -16079,7 +17101,7 @@ function registerPiDreamerProject(opts) {
16079
17101
  const client = createPiDreamerClient(opts, notifyOwnersOfAdjunctRefresh, () => {
16080
17102
  const current = registeredProjects2.get(opts.projectIdentity);
16081
17103
  return current?.generation === generation && current.owners.get(opts.registrationOwner)?.projectDir === opts.projectDir;
16082
- }, () => opts.sampleDreamRun ? opts.sampleDreamRun().dreamerConfig?.maxTokens : opts.config.maxTokens);
17104
+ }, () => opts.sampleDreamRun ? opts.sampleDreamRun().dreamerConfig?.maxTokens : opts.config.maxTokens, () => opts.sampleDreamRun?.().dreamerConfig ?? opts.config);
16083
17105
  let cleanup;
16084
17106
  let cancelled = false;
16085
17107
  const modelRegistry = opts.modelRegistry;
@@ -16098,7 +17120,10 @@ function registerPiDreamerProject(opts) {
16098
17120
  retinaHandoff: opts.retinaHandoff,
16099
17121
  mural: opts.mural,
16100
17122
  ensureRegistered: ensureProjectRegisteredFromPiDirectory,
16101
- retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({ projectCwd: opts.projectDir }),
17123
+ retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({
17124
+ projectCwd: opts.projectDir,
17125
+ contextDb: opts.db
17126
+ }),
16102
17127
  primerRawProviderFactory: createPiPrimerRawProviderFactory()
16103
17128
  }).then((timerCleanup) => {
16104
17129
  if (cancelled) {
@@ -16117,7 +17142,7 @@ function registerPiDreamerProject(opts) {
16117
17142
  const sampled = manualOpts.sampleDreamRun?.();
16118
17143
  const dreamerConfig = sampled?.dreamerConfig ?? manualOpts.config;
16119
17144
  const mural = sampled?.mural ?? manualOpts.mural;
16120
- const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir, () => dreamerConfig.maxTokens);
17145
+ const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir, () => dreamerConfig.maxTokens, () => dreamerConfig);
16121
17146
  const manualRun = runManualDream({
16122
17147
  db: manualOpts.db,
16123
17148
  projectIdentity: manualOpts.projectIdentity,
@@ -16127,7 +17152,8 @@ function registerPiDreamerProject(opts) {
16127
17152
  sessionDirectory: manualOpts.projectDir,
16128
17153
  openOpenCodeDb,
16129
17154
  retrospectiveRawProvider: new PiRetrospectiveRawProvider({
16130
- projectCwd: manualOpts.projectDir
17155
+ projectCwd: manualOpts.projectDir,
17156
+ contextDb: manualOpts.db
16131
17157
  }),
16132
17158
  primerRawProviderFactory: createPiPrimerRawProviderFactory(),
16133
17159
  userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
@@ -16197,8 +17223,9 @@ async function awaitInFlightDreamers(registrationOwner) {
16197
17223
  return;
16198
17224
  await Promise.allSettled(runs);
16199
17225
  }
16200
- function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true, getMaxOutputTokens = () => opts.config.maxTokens) {
17226
+ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true, getMaxOutputTokens = () => opts.config.maxTokens, getDreamerConfig = () => opts.config) {
16201
17227
  const runner = piSubagentRunnerFactory();
17228
+ const budgetStates = new Map;
16202
17229
  const assertRegistrationOwnerActive = () => {
16203
17230
  if (!isRegistrationOwnerActive()) {
16204
17231
  throw new Error(`Pi dreamer registration is no longer active for project ${opts.projectIdentity}`);
@@ -16228,6 +17255,13 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16228
17255
  const systemPrompt = extractSystemPrompt(args);
16229
17256
  const perTaskModel = extractBodyModel(args);
16230
17257
  const requestedAgent = extractBodyAgent(args) ?? "magic-context-dreamer";
17258
+ const controller = new AbortController;
17259
+ let controllers = dreamAbortControllers.get(opts.registrationOwner);
17260
+ if (!controllers) {
17261
+ controllers = new Set;
17262
+ dreamAbortControllers.set(opts.registrationOwner, controllers);
17263
+ }
17264
+ controllers.add(controller);
16231
17265
  const runPromise = runner.run({
16232
17266
  agent: requestedAgent,
16233
17267
  systemPrompt,
@@ -16236,18 +17270,39 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16236
17270
  fallbackModels: undefined,
16237
17271
  timeoutMs: 30 * 60 * 1000,
16238
17272
  cwd: dreamSession.directory,
16239
- signal: args.signal ?? undefined,
17273
+ signal: args.signal ? AbortSignal.any([args.signal, controller.signal]) : controller.signal,
16240
17274
  thinkingLevel: extractBodyVariant(args),
16241
17275
  maxOutputTokens: getMaxOutputTokens(),
16242
17276
  accountingSessionId: opts.projectIdentity,
16243
17277
  accountingSubagent: "dreamer",
16244
- accountingTask: accountingTaskFromTitle(dreamSession.title)
17278
+ accountingTask: accountingTaskFromTitle(dreamSession.title),
17279
+ tokenBudget: (() => {
17280
+ const task = accountingTaskFromTitle(dreamSession.title);
17281
+ if (!task)
17282
+ return;
17283
+ return buildDreamTaskRuntimeConfigs(getDreamerConfig(), opts.harness).find((entry) => entry.task === task)?.tokenBudget;
17284
+ })()
16245
17285
  });
16246
17286
  inFlightDreams.set(runPromise, opts.registrationOwner);
16247
17287
  try {
16248
17288
  const result = await runPromise;
17289
+ const budget = result.meta?.tokenBudget;
17290
+ const budgetTask = accountingTaskFromTitle(dreamSession.title);
17291
+ if (budgetTask && budget && typeof budget.spent === "number") {
17292
+ const prior = budgetStates.get(budgetTask);
17293
+ budgetStates.set(budgetTask, {
17294
+ spent: (prior?.spent ?? 0) + budget.spent,
17295
+ finalizeFired: (prior?.finalizeFired ?? false) || budget.finalizeFired === true
17296
+ });
17297
+ }
16249
17298
  assertRegistrationOwnerActive();
16250
17299
  if (!result.ok) {
17300
+ if (result.reason === "token_budget") {
17301
+ throw new DreamTokenBudgetExceeded(dreamSession.id, Number(result.meta?.tokenBudget?.spent ?? 0));
17302
+ }
17303
+ if (result.reason === "step_limit") {
17304
+ throw new HiddenAgentStepLimit("pi-dreamer", Number(result.meta?.cap));
17305
+ }
16251
17306
  const error = new Error(`Pi dreamer subagent failed (${result.reason}): ${result.error}`);
16252
17307
  if (result.transient) {
16253
17308
  error.transient = true;
@@ -16264,6 +17319,9 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16264
17319
  onAdjunctsRefreshNeeded?.(opts.projectIdentity);
16265
17320
  } finally {
16266
17321
  inFlightDreams.delete(runPromise);
17322
+ controllers.delete(controller);
17323
+ if (controllers.size === 0)
17324
+ dreamAbortControllers.delete(opts.registrationOwner);
16267
17325
  }
16268
17326
  },
16269
17327
  messages: async (args) => {
@@ -16276,7 +17334,11 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16276
17334
  return {};
16277
17335
  }
16278
17336
  };
16279
- return { session };
17337
+ return {
17338
+ session,
17339
+ readTokenBudget: (task) => budgetStates.get(task),
17340
+ resetDreamTokenBudget: (task) => budgetStates.delete(task)
17341
+ };
16280
17342
  }
16281
17343
  function readDirectory(args) {
16282
17344
  const query = args.query;
@@ -16585,6 +17647,7 @@ Unknown task "${requested}".`,
16585
17647
  try {
16586
17648
  await deps.ensureRegistered?.(ctx);
16587
17649
  const result = await runPiDreamForProject(project.projectIdentity, task, deps.registrationOwner);
17650
+ const backlogAfter = getDreamTaskBacklogs(deps.db, project.projectIdentity, backlogTasks);
16588
17651
  const lines = [];
16589
17652
  if (result.ran.length > 0)
16590
17653
  lines.push(`Ran: ${result.ran.join(", ")}`);
@@ -16600,8 +17663,8 @@ Unknown task "${requested}".`,
16600
17663
  lines.push(`Skipped (no work): ${result.skippedNoWork.join(", ")}`);
16601
17664
  if (result.deferredBusy.length > 0)
16602
17665
  lines.push(`Busy: ${result.deferredBusy.join(", ")} — another dream task holds this domain's lease; retry in a minute`);
16603
- if (Object.keys(result.backlogAfter ?? {}).length > 0) {
16604
- lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(result.backlogAfter));
17666
+ if (Object.keys(backlogAfter).length > 0) {
17667
+ lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(backlogAfter, backlogTasks));
16605
17668
  }
16606
17669
  if (lines.length === 0)
16607
17670
  lines.push("No enabled dream tasks to run.");
@@ -16630,26 +17693,6 @@ Unknown task "${requested}".`,
16630
17693
  });
16631
17694
  }
16632
17695
 
16633
- // ../plugin/src/hooks/magic-context/embed-session-state.ts
16634
- var embedPauseBySession = new Set;
16635
- var embedRunStateBySession = new Map;
16636
- var autoEmbedAttemptedBySession = new Set;
16637
- function getEmbedDrainUiStatus(sessionId, progress) {
16638
- if (embedPauseBySession.has(sessionId)) {
16639
- return { status: "paused" };
16640
- }
16641
- const activeRun = embedRunStateBySession.get(sessionId);
16642
- if (activeRun && !activeRun.signal.aborted || progress?.kind === "embed" && progress.phase === "recomp") {
16643
- return { status: "running" };
16644
- }
16645
- if (progress?.kind === "embed" && (progress.phase === "failed" || progress.phase === "skipped") && progress.message) {
16646
- if (/provider/i.test(progress.message)) {
16647
- return { status: "stopped", detail: progress.message };
16648
- }
16649
- }
16650
- return { status: "idle" };
16651
- }
16652
-
16653
17696
  // ../plugin/src/hooks/magic-context/format-embed-failure.ts
16654
17697
  function formatEmbedFailureSummary(embedded, remaining, failure, style = "markdown") {
16655
17698
  const historyBlocks = `history block${embedded === 1 ? "" : "s"}`;
@@ -16713,7 +17756,7 @@ function clearPiEmbedSessionState(sessionId) {
16713
17756
  ctrl.abort();
16714
17757
  embedRunStateBySession.delete(sessionId);
16715
17758
  }
16716
- autoEmbedAttemptedBySession.delete(sessionId);
17759
+ invalidateAutoEmbedSession(sessionId);
16717
17760
  }
16718
17761
  async function runEmbedDrain(db, projectIdentity, sessionId, options = {}) {
16719
17762
  const activeCtrl = embedRunStateBySession.get(sessionId);
@@ -16895,28 +17938,34 @@ ${statusText}`,
16895
17938
  function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity) {
16896
17939
  if (!isUsableProjectIdentity(projectIdentity))
16897
17940
  return;
17941
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
17942
+ const embedIdentity = snapshot ? JSON.stringify([
17943
+ snapshot.providerIdentity,
17944
+ snapshot.chunkModelId,
17945
+ snapshot.runtimeFingerprint
17946
+ ]) : "off";
17947
+ if (autoEmbedIdentityBySession.get(sessionId) !== embedIdentity)
17948
+ invalidateAutoEmbedSession(sessionId);
16898
17949
  if (autoEmbedAttemptedBySession.has(sessionId))
16899
17950
  return;
16900
17951
  if (embedPauseBySession.has(sessionId))
16901
17952
  return;
16902
17953
  autoEmbedAttemptedBySession.add(sessionId);
17954
+ autoEmbedIdentityBySession.set(sessionId, embedIdentity);
16903
17955
  (async () => {
16904
- let drainReachedTerminal = false;
16905
17956
  try {
16906
17957
  await new Promise((resolve) => setTimeout(resolve, 0));
16907
17958
  await ensureProjectRegisteredFromPiDirectory(projectDir, deps.db);
16908
- const coverage = getEmbeddingCoverageStatus(deps.db, projectIdentity, sessionId);
16909
- if (!coverage.enabled)
17959
+ const coverage = await getAutoEmbeddingSessionCoverage(deps.db, projectIdentity, sessionId);
17960
+ if (!coverage.enabled) {
17961
+ invalidateAutoEmbedSession(sessionId);
16910
17962
  return;
16911
- const remaining = coverage.session.total - coverage.session.embedded;
17963
+ }
17964
+ const remaining = coverage.total - coverage.embedded;
16912
17965
  if (remaining <= 0)
16913
17966
  return;
16914
17967
  await runEmbedDrain(deps.db, projectIdentity, sessionId);
16915
- drainReachedTerminal = true;
16916
- } catch {} finally {
16917
- if (!drainReachedTerminal)
16918
- autoEmbedAttemptedBySession.delete(sessionId);
16919
- }
17968
+ } catch {}
16920
17969
  })();
16921
17970
  }
16922
17971
 
@@ -19772,10 +20821,10 @@ function advanceToolReclaimWatermarkToCurrentMax(db, sessionId) {
19772
20821
  }
19773
20822
 
19774
20823
  // ../plugin/src/shared/tag-transcript.ts
19775
- import { createHash as createHash11 } from "node:crypto";
20824
+ import { createHash as createHash12 } from "node:crypto";
19776
20825
  var TEXT_TAG_IDENTITY_MARKER = ":mc-text-v1:";
19777
20826
  function textIdentityDigest(value) {
19778
- return createHash11("sha256").update(value).digest("hex");
20827
+ return createHash12("sha256").update(value).digest("hex");
19779
20828
  }
19780
20829
  function buildContentDerivedTextIds(messageId, parts) {
19781
20830
  const sources = parts.filter((part) => part.kind === "text").map((part) => stripTagPrefix(part.getText() ?? ""));
@@ -23039,8 +24088,10 @@ async function runHistorianSubagentWithTransientRetriesGuarded(args) {
23039
24088
  try {
23040
24089
  const key = piModelRefToCanonical(args.options.model ?? "");
23041
24090
  const window = args.resolveContextLimit?.(key) ?? resolveKnownHistorianContextLimit(key);
23042
- const reserve = historianProducerReserve(window, args.options.maxOutputTokens, args.resolveOutputLimit?.(key));
23043
- if (window !== undefined && producerInputTokenLimit(window, reserve) === undefined && !loggedInconsistentWindows.has(key)) {
24091
+ const limits = args.resolveContextLimit ? { context: window, input: undefined } : resolveHistorianProducerLimits(key);
24092
+ const context = limits.context ?? (limits.input === undefined ? window : undefined);
24093
+ const reserve = historianProducerReserve(context, args.options.maxOutputTokens, args.resolveOutputLimit?.(key));
24094
+ if (window !== undefined && producerInputTokenLimit(context, reserve, limits.input) === undefined && !loggedInconsistentWindows.has(key)) {
23044
24095
  loggedInconsistentWindows.add(key);
23045
24096
  sessionLog(args.sessionId, `producer window inconsistent for ${key}: window=${window} reserve=${reserve}; sending unguarded`);
23046
24097
  }
@@ -23049,7 +24100,8 @@ async function runHistorianSubagentWithTransientRetriesGuarded(args) {
23049
24100
  systemLocal: estimateTokens(args.options.systemPrompt),
23050
24101
  toolsLocal: 0,
23051
24102
  modelKey: key,
23052
- contextLimitTokens: window,
24103
+ contextLimitTokens: context,
24104
+ inputLimitTokens: limits.input,
23053
24105
  maxOutputTokens: reserve
23054
24106
  });
23055
24107
  if (failure)
@@ -25659,7 +26711,7 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), m
25659
26711
  };
25660
26712
  return { docs, markers, compartments, memories, userProfile, workspace };
25661
26713
  });
25662
- return read();
26714
+ return read.deferred();
25663
26715
  }
25664
26716
  function renderFreshM0PiNonPersisted(state, db) {
25665
26717
  const docs = readProjectDocsForPiM0(state);
@@ -26786,162 +27838,6 @@ function registerPiGuardedContext(pi, handler) {
26786
27838
  });
26787
27839
  }
26788
27840
 
26789
- // ../plugin/src/hooks/magic-context/lkg-persist.ts
26790
- function parseStringArray(value) {
26791
- if (typeof value !== "string")
26792
- return null;
26793
- let parsed;
26794
- try {
26795
- parsed = JSON.parse(value);
26796
- } catch {
26797
- return null;
26798
- }
26799
- if (!Array.isArray(parsed))
26800
- return null;
26801
- const result = [];
26802
- for (const entry of parsed) {
26803
- if (typeof entry !== "string" || entry.length === 0)
26804
- return null;
26805
- result.push(entry);
26806
- }
26807
- return result;
26808
- }
26809
- function parseNullableString(value) {
26810
- if (value === null || value === undefined)
26811
- return null;
26812
- return typeof value === "string" ? value : undefined;
26813
- }
26814
- function parseNullableInteger(value) {
26815
- if (value === null || value === undefined)
26816
- return;
26817
- return typeof value === "number" && Number.isSafeInteger(value) ? value : undefined;
26818
- }
26819
- function parsePersistedLkgSlot(row) {
26820
- if (!row || typeof row !== "object")
26821
- return;
26822
- const record = row;
26823
- const jsonPrefix = record.json_prefix;
26824
- const lastInputMessageId = record.last_input_message_id;
26825
- const capturedAt = record.captured_at;
26826
- let inputIdsRaw = record.input_id_seq;
26827
- let piOutputEntryIds;
26828
- if (typeof inputIdsRaw === "string") {
26829
- try {
26830
- const metadata = JSON.parse(inputIdsRaw);
26831
- if (metadata && !Array.isArray(metadata) && metadata.version === 1 && Array.isArray(metadata.piOutputEntryIds)) {
26832
- if (!metadata.piOutputEntryIds.every((id) => id === null || typeof id === "string" && id.length > 0))
26833
- return;
26834
- piOutputEntryIds = metadata.piOutputEntryIds;
26835
- inputIdsRaw = JSON.stringify(metadata.inputIds);
26836
- }
26837
- } catch {
26838
- return;
26839
- }
26840
- }
26841
- const inputIdSeq = parseStringArray(inputIdsRaw);
26842
- const inputContentDigests = parseStringArray(record.input_content_digests);
26843
- const modelKey = parseNullableString(record.model_key);
26844
- const providerKey = parseNullableString(record.provider_key);
26845
- if (typeof jsonPrefix !== "string" || typeof lastInputMessageId !== "string" || lastInputMessageId.length === 0 || typeof capturedAt !== "number" || !Number.isFinite(capturedAt) || inputIdSeq === null || inputContentDigests === null || inputContentDigests.length !== inputIdSeq.length || modelKey === undefined || providerKey === undefined) {
26846
- return;
26847
- }
26848
- if (piOutputEntryIds) {
26849
- try {
26850
- const output = JSON.parse(jsonPrefix);
26851
- const inputs = new Set(inputIdSeq);
26852
- if (!Array.isArray(output) || output.length !== piOutputEntryIds.length || piOutputEntryIds.some((id) => id !== null && !inputs.has(id)))
26853
- return;
26854
- } catch {
26855
- return;
26856
- }
26857
- }
26858
- let inputContentSignatures;
26859
- if (record.input_content_signatures !== null && record.input_content_signatures !== undefined) {
26860
- const parsed = parseStringArray(record.input_content_signatures);
26861
- if (parsed === null || parsed.length !== inputIdSeq.length)
26862
- return;
26863
- inputContentSignatures = parsed;
26864
- }
26865
- const slot = {
26866
- jsonPrefix,
26867
- inputIdSeq,
26868
- inputContentDigests,
26869
- lastInputMessageId,
26870
- modelKey,
26871
- providerKey,
26872
- capturedAt
26873
- };
26874
- if (inputContentSignatures)
26875
- slot.inputContentSignatures = inputContentSignatures;
26876
- if (piOutputEntryIds)
26877
- slot.piOutputEntryIds = piOutputEntryIds;
26878
- const rowVersion = parseNullableInteger(record.row_version);
26879
- if (rowVersion !== undefined)
26880
- slot.rowVersion = rowVersion;
26881
- const captureSequence = parseNullableInteger(record.capture_sequence);
26882
- if (captureSequence !== undefined)
26883
- slot.captureSequence = captureSequence;
26884
- return slot;
26885
- }
26886
- function saveLkgSlotToDb(db, sessionId, slot) {
26887
- try {
26888
- db.prepare(`INSERT INTO lkg_slots (
26889
- session_id, json_prefix, input_id_seq, input_content_digests,
26890
- input_content_signatures, last_input_message_id, model_key, provider_key,
26891
- captured_at, row_version, capture_sequence
26892
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
26893
- ON CONFLICT(session_id) DO UPDATE SET
26894
- json_prefix = excluded.json_prefix,
26895
- input_id_seq = excluded.input_id_seq,
26896
- input_content_digests = excluded.input_content_digests,
26897
- input_content_signatures = excluded.input_content_signatures,
26898
- last_input_message_id = excluded.last_input_message_id,
26899
- model_key = excluded.model_key,
26900
- provider_key = excluded.provider_key,
26901
- captured_at = excluded.captured_at,
26902
- row_version = excluded.row_version,
26903
- capture_sequence = excluded.capture_sequence`).run(sessionId, slot.jsonPrefix, JSON.stringify(slot.piOutputEntryIds ? {
26904
- version: 1,
26905
- inputIds: slot.inputIdSeq,
26906
- piOutputEntryIds: slot.piOutputEntryIds
26907
- } : slot.inputIdSeq), JSON.stringify(slot.inputContentDigests), slot.inputContentSignatures ? JSON.stringify(slot.inputContentSignatures) : null, slot.lastInputMessageId, slot.modelKey, slot.providerKey, slot.capturedAt, slot.rowVersion ?? null, slot.captureSequence ?? null);
26908
- return true;
26909
- } catch (error) {
26910
- sessionLog(sessionId, "LKG snapshot persistence failed (in-memory slot retained):", error);
26911
- return false;
26912
- }
26913
- }
26914
- function clearPersistedLkgSlot(db, sessionId) {
26915
- try {
26916
- db.prepare("DELETE FROM lkg_slots WHERE session_id = ?").run(sessionId);
26917
- } catch (error) {
26918
- sessionLog(sessionId, "LKG snapshot durable clear failed:", error);
26919
- }
26920
- }
26921
- function loadPersistedLkgSlot(db, sessionId) {
26922
- let row;
26923
- try {
26924
- row = db.prepare("SELECT * FROM lkg_slots WHERE session_id = ?").get(sessionId);
26925
- } catch (error) {
26926
- sessionLog(sessionId, "LKG snapshot durable load failed:", error);
26927
- return;
26928
- }
26929
- if (!row)
26930
- return;
26931
- const slot = parsePersistedLkgSlot(row);
26932
- if (!slot) {
26933
- clearPersistedLkgSlot(db, sessionId);
26934
- return;
26935
- }
26936
- return slot;
26937
- }
26938
- function createDbLkgPersistence(db) {
26939
- return {
26940
- load: (sessionId) => loadPersistedLkgSlot(db, sessionId),
26941
- clear: (sessionId) => clearPersistedLkgSlot(db, sessionId)
26942
- };
26943
- }
26944
-
26945
27841
  // ../plugin/src/hooks/magic-context/openai-compat-adjacency.ts
26946
27842
  function assertOpenAiCompatAdjacency(messages) {
26947
27843
  const violations = [];
@@ -27136,19 +28032,22 @@ function validateAnthropicReasoningRuns(messages) {
27136
28032
  index += 1;
27137
28033
  continue;
27138
28034
  }
27139
- let thinkingBlocks = 0;
28035
+ let firstMessageInRun = null;
27140
28036
  let sawOtherContent = false;
27141
28037
  while (index < messages.length && messageRole(messages[index]) === "assistant") {
27142
28038
  for (const part of messageParts(messages[index])) {
27143
- if (partIsAnthropicThinking(part)) {
27144
- thinkingBlocks += 1;
27145
- if (thinkingBlocks > 1 || sawOtherContent)
27146
- return false;
27147
- } else if (partEndsAnthropicAssistantRun(part)) {
27148
- thinkingBlocks = 0;
28039
+ if (partEndsAnthropicAssistantRun(part)) {
28040
+ firstMessageInRun = null;
27149
28041
  sawOtherContent = false;
27150
28042
  } else if (!partIsOpenCodeStepMetadata(part)) {
27151
- sawOtherContent = true;
28043
+ if (firstMessageInRun === null)
28044
+ firstMessageInRun = index;
28045
+ if (partIsAnthropicThinking(part)) {
28046
+ if (sawOtherContent || index !== firstMessageInRun)
28047
+ return false;
28048
+ } else {
28049
+ sawOtherContent = true;
28050
+ }
27152
28051
  }
27153
28052
  }
27154
28053
  index += 1;
@@ -27256,6 +28155,8 @@ function replayLkg(args) {
27256
28155
  dropSlot(args.sessionId, "lkg_seam_invalid");
27257
28156
  return { ok: false, reason: "lkg_seam_invalid" };
27258
28157
  }
28158
+ const replayed = [...prefix, ...entry.pristineTail];
28159
+ args.prepareReplay?.(replayed);
27259
28160
  if (!args.skipSeamValidation) {
27260
28161
  if (!validateLkgSeamBoundary(prefix, entry.pristineTail)) {
27261
28162
  dropSlot(args.sessionId, "lkg_unsafe_seam");
@@ -27266,7 +28167,6 @@ function replayLkg(args) {
27266
28167
  return { ok: false, reason: "lkg_seam_invalid" };
27267
28168
  }
27268
28169
  }
27269
- const replayed = [...prefix, ...entry.pristineTail];
27270
28170
  if (requestedModelKeys.providerKey === "anthropic" && !validateAnthropicReasoningRuns(replayed)) {
27271
28171
  dropSlot(args.sessionId, "lkg_anthropic_reasoning_run_invalid");
27272
28172
  return { ok: false, reason: "lkg_anthropic_reasoning_run_invalid" };
@@ -28573,7 +29473,7 @@ function replayStrippedInlineThinkingPi(args) {
28573
29473
  }
28574
29474
 
28575
29475
  // src/served-array-ledger.ts
28576
- import { createHash as createHash12 } from "node:crypto";
29476
+ import { createHash as createHash13 } from "node:crypto";
28577
29477
  import * as fs2 from "node:fs";
28578
29478
  import * as path6 from "node:path";
28579
29479
  var PI_SERVED_ARRAY_TAIL_MESSAGES = 40;
@@ -28588,7 +29488,7 @@ var flushTimer = null;
28588
29488
  var swallowedWriteCount = 0;
28589
29489
  var lastWriteError = null;
28590
29490
  function sha256(value) {
28591
- return createHash12("sha256").update(value).digest("hex");
29491
+ return createHash13("sha256").update(value).digest("hex");
28592
29492
  }
28593
29493
  function safeSessionFileStem(sessionId) {
28594
29494
  const readable = sessionId.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 96);
@@ -28731,13 +29631,10 @@ function capturePiServedArray(sessionId, messages, options = {}) {
28731
29631
  process.once("beforeExit", flushPiServedArrayLedger);
28732
29632
 
28733
29633
  // src/strip-placeholders-pi.ts
28734
- var DROPPED_SEGMENT_PATTERN = /^\[dropped(?: §[^§]+§)?\]$/;
28735
- function isDroppedOnlyText(text) {
29634
+ var MARKER_ONLY_PATTERN = /^(?:(?:§\d+§|\[dropped(?: §\d+§)?\]|\[cleared\])\s*)+$/;
29635
+ function isPiMarkerOnlyText(text) {
28736
29636
  const trimmed = text.trim();
28737
- if (trimmed.length === 0)
28738
- return true;
28739
- const segments = trimmed.split(/(?=\[dropped(?: §[^§]+§)?\])/).map((segment) => segment.trim()).filter((segment) => segment.length > 0);
28740
- return segments.length > 0 && segments.every((s) => DROPPED_SEGMENT_PATTERN.test(s));
29637
+ return trimmed.length > 0 && MARKER_ONLY_PATTERN.test(trimmed);
28741
29638
  }
28742
29639
  function messageIsPlaceholderOnly(message) {
28743
29640
  if (!message || typeof message !== "object")
@@ -28746,7 +29643,7 @@ function messageIsPlaceholderOnly(message) {
28746
29643
  if (msg.role !== "assistant")
28747
29644
  return false;
28748
29645
  if (typeof msg.content === "string")
28749
- return isDroppedOnlyText(msg.content);
29646
+ return msg.content.trim().length === 0 || isPiMarkerOnlyText(msg.content);
28750
29647
  if (!Array.isArray(msg.content))
28751
29648
  return false;
28752
29649
  if (msg.content.length === 0)
@@ -28756,12 +29653,11 @@ function messageIsPlaceholderOnly(message) {
28756
29653
  if (!part || typeof part !== "object")
28757
29654
  return false;
28758
29655
  const p = part;
28759
- if (p.type !== "text")
28760
- return false;
28761
- if (typeof p.text !== "string")
29656
+ const text = p.type === "text" ? p.text : p.type === "thinking" ? p.thinking : null;
29657
+ if (typeof text !== "string")
28762
29658
  return false;
28763
29659
  sawVisibleContent = true;
28764
- if (!isDroppedOnlyText(p.text))
29660
+ if (text.trim().length > 0 && !isPiMarkerOnlyText(text))
28765
29661
  return false;
28766
29662
  }
28767
29663
  return sawVisibleContent;
@@ -28900,7 +29796,7 @@ function stripPiProcessedImages(args) {
28900
29796
  }
28901
29797
 
28902
29798
  // src/system-prompt.ts
28903
- import { createHash as createHash13 } from "node:crypto";
29799
+ import { createHash as createHash14 } from "node:crypto";
28904
29800
  var MAGIC_CONTEXT_MARKER = "## Magic Context";
28905
29801
  var stickyDateBySession = new Map;
28906
29802
  function buildMagicContextBlock(opts) {
@@ -28931,7 +29827,7 @@ function processSystemPromptForCache(args) {
28931
29827
  const liveDate = dateMatch ? dateMatch[0] : null;
28932
29828
  const stickyDate = stickyDateBySession.get(sessionId);
28933
29829
  const stableCandidate = liveDate && stickyDate && liveDate !== stickyDate ? systemPrompt.replace(DATE_PATTERN, stickyDate) : systemPrompt;
28934
- const stableCandidateHash = createHash13("md5").update(promptSurfaceHashMaterial(stableCandidate, args.promptSurfacePreset)).digest("hex");
29830
+ const stableCandidateHash = createHash14("md5").update(promptSurfaceHashMaterial(stableCandidate, args.promptSurfacePreset)).digest("hex");
28935
29831
  const contentOrPresetChanged = !isFirstHash && stableCandidateHash !== previousHash;
28936
29832
  const dateMayAdvance = isCacheBusting || contentOrPresetChanged;
28937
29833
  if (liveDate && !stickyDate) {
@@ -28945,7 +29841,7 @@ function processSystemPromptForCache(args) {
28945
29841
  sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
28946
29842
  }
28947
29843
  }
28948
- const currentHash = createHash13("md5").update(promptSurfaceHashMaterial(frozenPrompt, args.promptSurfacePreset)).digest("hex");
29844
+ const currentHash = createHash14("md5").update(promptSurfaceHashMaterial(frozenPrompt, args.promptSurfacePreset)).digest("hex");
28949
29845
  const hashChanged = !isFirstHash && currentHash !== previousHash;
28950
29846
  if (hashChanged) {
28951
29847
  sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
@@ -29651,6 +30547,7 @@ var deferredHistoryRefreshSessions = new Set;
29651
30547
  var deferredMaterializationSessions = new Set;
29652
30548
  var sessionsByProject = new Map;
29653
30549
  var lastSeenProjectIdentityBySession = new Map;
30550
+ var persistedProjectIdentityBySession = new Map;
29654
30551
  var rawMessageProviderUnregistersBySession = new Map;
29655
30552
  var activeContextHandlerSessions = new Set;
29656
30553
  var lastHeuristicsTurnIdBySession = new Map;
@@ -29872,9 +30769,10 @@ function updateSessionProjectTracking(sessionId, projectIdentity, db) {
29872
30769
  sessionsByProject.delete(prev);
29873
30770
  clearPiSystemPromptSession(sessionId);
29874
30771
  }
29875
- if (db && prev !== projectIdentity) {
30772
+ if (db && persistedProjectIdentityBySession.get(sessionId) !== projectIdentity) {
29876
30773
  try {
29877
30774
  recordSessionProjectIdentity(db, sessionId, projectIdentity);
30775
+ persistedProjectIdentityBySession.set(sessionId, projectIdentity);
29878
30776
  } catch {}
29879
30777
  }
29880
30778
  trackSessionForProject(projectIdentity, sessionId);
@@ -31233,6 +32131,10 @@ function registerPiContextHandler(pi, baseOptions) {
31233
32131
  log("[magic-context][pi] registered context handler (tagging + drops + nudges)");
31234
32132
  }
31235
32133
  var inFlightHistorian = new Map;
32134
+ var historianAbortControllers = new Map;
32135
+ function abortInFlightHistorians(sessionId) {
32136
+ historianAbortControllers.get(sessionId)?.abort();
32137
+ }
31236
32138
  async function awaitInFlightHistorians(sessionId) {
31237
32139
  const runs = sessionId ? [inFlightHistorian.get(sessionId)].filter((run) => run !== undefined) : [...inFlightHistorian.values()];
31238
32140
  if (runs.length === 0)
@@ -31345,7 +32247,11 @@ function spawnPiHistorianRun(args) {
31345
32247
  fallbackModelId
31346
32248
  } = args;
31347
32249
  const holderId = crypto3.randomUUID();
32250
+ const controller = new AbortController;
32251
+ historianAbortControllers.set(sessionId, controller);
31348
32252
  const runPromise = (async () => {
32253
+ if (controller.signal.aborted)
32254
+ return;
31349
32255
  const lease = acquireCompartmentLease(db, sessionId, holderId);
31350
32256
  if (!lease) {
31351
32257
  sessionLog(sessionId, "historian skipped: compartment lease held by another process");
@@ -31366,6 +32272,7 @@ function spawnPiHistorianRun(args) {
31366
32272
  appendCompaction: resolvePiAppendCompaction(ctx),
31367
32273
  readBranchEntries: resolvePiReadBranchEntries(ctx),
31368
32274
  runner: historian.runner,
32275
+ signal: controller.signal,
31369
32276
  historianModel: historian.model,
31370
32277
  fallbackModels: historian.fallbackModels,
31371
32278
  fallbackModelId,
@@ -31434,6 +32341,8 @@ function spawnPiHistorianRun(args) {
31434
32341
  }).finally(() => {
31435
32342
  try {
31436
32343
  inFlightHistorian.delete(sessionId);
32344
+ if (historianAbortControllers.get(sessionId) === controller)
32345
+ historianAbortControllers.delete(sessionId);
31437
32346
  unregister();
31438
32347
  if (isContextHandlerSessionActive(sessionId)) {
31439
32348
  historian.onStatusChange?.(ctx, sessionId);
@@ -31636,6 +32545,8 @@ Historian previously failed ${failureCount} time(s), so Magic Context is retryin
31636
32545
  }
31637
32546
  return;
31638
32547
  }
32548
+ if (!ctx.hasUI)
32549
+ return;
31639
32550
  triggered = true;
31640
32551
  sessionLog(sessionId, `historian trigger fired (reason=${trigger.reason ?? "unknown"}) usage=${usage.percentage.toFixed(1)}% — spawning subagent`);
31641
32552
  spawnPiHistorianRun({
@@ -32831,6 +33742,7 @@ function clearContextHandlerSession(sessionId) {
32831
33742
  lastHeuristicsTurnIdBySession.delete(sessionId);
32832
33743
  routinePressureAppliedBySession.delete(sessionId);
32833
33744
  lastSeenProjectIdentityBySession.delete(sessionId);
33745
+ persistedProjectIdentityBySession.delete(sessionId);
32834
33746
  for (const [projectIdentity, sessions] of sessionsByProject) {
32835
33747
  sessions.delete(sessionId);
32836
33748
  if (sessions.size === 0)
@@ -32985,6 +33897,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
32985
33897
  finished = true;
32986
33898
  return null;
32987
33899
  }
33900
+ deleteChunkEmbedBackoffForSession(db, sessionId);
32988
33901
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
32989
33902
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
32990
33903
  insertRecompCompartmentRows(db, sessionId, staging.compartments, now);
@@ -32999,6 +33912,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
32999
33912
  clearCachedM0M1(db, sessionId);
33000
33913
  db.exec("COMMIT");
33001
33914
  finished = true;
33915
+ invalidateAutoEmbedSession(sessionId);
33002
33916
  logSlowWriteTransaction("historian-publish:recomp", transactionStartedAt);
33003
33917
  return { compartments: staging.compartments, facts: staging.facts };
33004
33918
  } finally {
@@ -35025,7 +35939,7 @@ function buildStatusView(source, options) {
35025
35939
  // package.json
35026
35940
  var package_default = {
35027
35941
  name: "@cortexkit/pi-magic-context",
35028
- version: "0.44.1",
35942
+ version: "0.44.3",
35029
35943
  type: "module",
35030
35944
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
35031
35945
  main: "dist/index.js",
@@ -35073,7 +35987,6 @@ var package_default = {
35073
35987
  "comment-json": "^5.0.0",
35074
35988
  "onnxruntime-web": "1.26.0-dev.20260416-b7804b056c",
35075
35989
  "quickjs-emscripten": "^0.32.0",
35076
- typebox: "^1.3.1",
35077
35990
  zod: "^4.1.8"
35078
35991
  },
35079
35992
  optionalDependencies: {
@@ -35093,11 +36006,13 @@ var package_default = {
35093
36006
  "pi-ai-086": "npm:@earendil-works/pi-ai@0.86.0",
35094
36007
  "pi-coding-agent-086": "npm:@earendil-works/pi-coding-agent@0.86.0",
35095
36008
  "pi-coding-agent-087": "npm:@earendil-works/pi-coding-agent@0.87.1",
36009
+ typebox: "^1.3.1",
35096
36010
  typescript: "^5.8.0"
35097
36011
  },
35098
36012
  peerDependencies: {
35099
- "@earendil-works/pi-coding-agent": ">=0.80.2",
35100
- "@earendil-works/pi-tui": ">=0.80.2"
36013
+ "@earendil-works/pi-coding-agent": "*",
36014
+ "@earendil-works/pi-tui": "*",
36015
+ typebox: "*"
35101
36016
  },
35102
36017
  peerDependenciesMeta: {
35103
36018
  "@earendil-works/pi-coding-agent": {
@@ -35105,6 +36020,9 @@ var package_default = {
35105
36020
  },
35106
36021
  "@earendil-works/pi-tui": {
35107
36022
  optional: true
36023
+ },
36024
+ typebox: {
36025
+ optional: true
35108
36026
  }
35109
36027
  },
35110
36028
  exports: {
@@ -35143,6 +36061,11 @@ async function showStatusDialog(pi, ctx, deps) {
35143
36061
  overlayOptions: { anchor: "center", width: 78 }
35144
36062
  });
35145
36063
  }
36064
+ var openStatusDialogs = new Set;
36065
+ function stopStatusDialogRefresh() {
36066
+ for (const dialog of openStatusDialogs)
36067
+ dialog.close();
36068
+ }
35146
36069
 
35147
36070
  class StatusDialogComponent {
35148
36071
  props;
@@ -35152,6 +36075,7 @@ class StatusDialogComponent {
35152
36075
  constructor(props) {
35153
36076
  this.props = props;
35154
36077
  this.detail = buildPiStatusDetail(props.pi, props.ctx, props.deps, props.sessionId);
36078
+ openStatusDialogs.add(this);
35155
36079
  this.refreshTimer = setInterval(() => {
35156
36080
  if (this.closed)
35157
36081
  return;
@@ -35169,11 +36093,7 @@ class StatusDialogComponent {
35169
36093
  close() {
35170
36094
  if (this.closed)
35171
36095
  return;
35172
- this.closed = true;
35173
- if (this.refreshTimer) {
35174
- clearInterval(this.refreshTimer);
35175
- this.refreshTimer = null;
35176
- }
36096
+ this.dispose();
35177
36097
  this.props.done(undefined);
35178
36098
  }
35179
36099
  invalidate() {}
@@ -35183,6 +36103,8 @@ class StatusDialogComponent {
35183
36103
  return drawBorder(inner, width, this.props.theme);
35184
36104
  }
35185
36105
  dispose() {
36106
+ this.closed = true;
36107
+ openStatusDialogs.delete(this);
35186
36108
  if (this.refreshTimer) {
35187
36109
  clearInterval(this.refreshTimer);
35188
36110
  this.refreshTimer = null;
@@ -35726,7 +36648,10 @@ No active Pi session is available.`,
35726
36648
  });
35727
36649
  return;
35728
36650
  }
35729
- const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
36651
+ const currentDeps = {
36652
+ ...deps.resolveRuntimeDeps?.(ctx) ?? deps,
36653
+ shutdownSignal: deps.shutdownSignal
36654
+ };
35730
36655
  if (currentDeps.compactionOff) {
35731
36656
  sendStatus({
35732
36657
  title: "/ctx-wrapup",
@@ -35859,6 +36784,8 @@ No runnable wrapup boundary is available yet; wrapped up 0 messages into 0 compa
35859
36784
  console.warn(`[magic-context][pi] /ctx-wrapup marker renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
35860
36785
  }
35861
36786
  }, 60000);
36787
+ const stopRenewal = () => clearInterval(renewal);
36788
+ deps.shutdownSignal?.addEventListener("abort", stopRenewal, { once: true });
35862
36789
  try {
35863
36790
  sendStatus({
35864
36791
  title: "/ctx-wrapup",
@@ -35925,7 +36852,7 @@ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleS
35925
36852
  Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1} (~${plan.snapshot.trueRawEligibleTokens.toLocaleString()} eligible tokens remain).`,
35926
36853
  level: "info"
35927
36854
  });
35928
- const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps));
36855
+ const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps), deps.shutdownSignal);
35929
36856
  if (!leaseResult.ok) {
35930
36857
  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.";
35931
36858
  break;
@@ -35938,6 +36865,10 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
35938
36865
  console.warn(`[magic-context][pi] /ctx-wrapup compartment lease renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
35939
36866
  }
35940
36867
  }, COMPARTMENT_LEASE_RENEWAL_MS);
36868
+ const stopLeaseRenewal = () => clearInterval(leaseRenewal);
36869
+ deps.shutdownSignal?.addEventListener("abort", stopLeaseRenewal, {
36870
+ once: true
36871
+ });
35941
36872
  try {
35942
36873
  const runHistorian = deps.runPiHistorianForWrapup ?? runPiHistorian;
35943
36874
  await runHistorian({
@@ -35946,6 +36877,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
35946
36877
  directory: ctx.cwd,
35947
36878
  provider,
35948
36879
  runner: deps.runner,
36880
+ signal: deps.shutdownSignal,
35949
36881
  historianModel: deps.historianModel,
35950
36882
  fallbackModels: deps.historianFallbacks,
35951
36883
  fallbackModelId: modelKey,
@@ -35989,6 +36921,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
35989
36921
  });
35990
36922
  } finally {
35991
36923
  clearInterval(leaseRenewal);
36924
+ deps.shutdownSignal?.removeEventListener("abort", stopLeaseRenewal);
35992
36925
  releaseCompartmentLeaseBestEffort(deps.db, sessionId, leaseHolder, sessionLog);
35993
36926
  }
35994
36927
  const afterEnd = getLastCompartmentEndMessage(deps.db, sessionId);
@@ -36018,6 +36951,7 @@ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments;
36018
36951
  Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments. The compacted history is queued and materializes on your next message.`;
36019
36952
  } finally {
36020
36953
  clearInterval(renewal);
36954
+ deps.shutdownSignal?.removeEventListener("abort", stopRenewal);
36021
36955
  releaseWrapupInProgress(deps.db, sessionId, holderId);
36022
36956
  }
36023
36957
  } finally {
@@ -36039,10 +36973,12 @@ function resolvePiContextLimit(ctx, db, sessionId) {
36039
36973
  detectedContextLimit
36040
36974
  }) ?? 128000;
36041
36975
  }
36042
- async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs) {
36976
+ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs, signal) {
36043
36977
  const waitStartedAt = Date.now();
36044
36978
  const remainingMs = () => Math.max(0, waitStartedAt + maxWaitMs - Date.now());
36045
36979
  for (;; ) {
36980
+ if (signal?.aborted)
36981
+ return { ok: false, reason: "ownership_lost" };
36046
36982
  if (remainingMs() <= 0)
36047
36983
  return { ok: false, reason: "timeout" };
36048
36984
  const holderId = crypto4.randomUUID();
@@ -36051,7 +36987,17 @@ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarke
36051
36987
  return { ok: true, holderId };
36052
36988
  if (!renewWrapupMarker({}))
36053
36989
  return { ok: false, reason: "ownership_lost" };
36054
- await new Promise((resolve) => setTimeout(resolve, Math.min(LEASE_WAIT_MS, remainingMs())));
36990
+ await new Promise((resolve) => {
36991
+ const finish = () => {
36992
+ clearTimeout(timer);
36993
+ signal?.removeEventListener("abort", finish);
36994
+ resolve();
36995
+ };
36996
+ const timer = setTimeout(finish, Math.min(LEASE_WAIT_MS, remainingMs()));
36997
+ signal?.addEventListener("abort", finish, { once: true });
36998
+ if (signal?.aborted)
36999
+ finish();
37000
+ });
36055
37001
  }
36056
37002
  }
36057
37003
  function estimateChunks(tokens, chunkTokens) {
@@ -36075,6 +37021,32 @@ function readBranchEntries(ctx) {
36075
37021
  return Array.isArray(branch?.entries) ? branch.entries : [];
36076
37022
  }
36077
37023
 
37024
+ // src/dreamer/session-activity-pi.ts
37025
+ function observePiMessageActivity(db, sessionId, timestamp) {
37026
+ observeSessionActivity(db, sessionId, timestamp);
37027
+ }
37028
+ function latestPiMessageTime(path) {
37029
+ let latest = 0;
37030
+ for (const entry of sessionEntries(path)) {
37031
+ const row = entry;
37032
+ if (row.type === "message" && typeof row.message?.timestamp === "number")
37033
+ latest = Math.max(latest, row.message.timestamp);
37034
+ }
37035
+ return latest || undefined;
37036
+ }
37037
+ async function backfillPiSessionActivity(db, harness, paths) {
37038
+ await backfillSessionActivity(db, harness, (sessionId) => {
37039
+ const path = paths.get(sessionId);
37040
+ if (!path)
37041
+ return;
37042
+ try {
37043
+ return latestPiMessageTime(path);
37044
+ } catch {
37045
+ return;
37046
+ }
37047
+ });
37048
+ }
37049
+
36078
37050
  // src/dropped-input-guard-pi.ts
36079
37051
  function createPiDroppedInputGuard(options = {}) {
36080
37052
  const guard = createDroppedInputGuard({
@@ -36273,6 +37245,8 @@ function stripTagPrefixFromAssistantMessage(message) {
36273
37245
  var PI_HARNESS_DETECTION = await resolvePiHarnessDetection();
36274
37246
  var PI_HARNESS_KIND = PI_HARNESS_DETECTION.kind;
36275
37247
  var PREFIX2 = `[magic-context][${PI_HARNESS_KIND}]`;
37248
+ var databaseExitHookRegistered = false;
37249
+ var activePiRuntimes = new Set;
36276
37250
  var PI_BOOT_DEADLINE_MS = 15000;
36277
37251
  var PI_CHILD_INIT_CONTEXT = Symbol.for("magic-context.pi.child-init-context");
36278
37252
  var SUBAGENT_CHILD_SESSION_CREATED = "subagents:child:session-created";
@@ -36710,6 +37684,7 @@ async function src_default(pi) {
36710
37684
  const bootProjectDir = process.cwd();
36711
37685
  ensureConfigLocationsMigrated(bootProjectDir);
36712
37686
  const bootConfig = loadPiConfig({ cwd: bootProjectDir });
37687
+ setHomeProjectPermission(bootConfig.config.allow_home_project);
36713
37688
  if (!bootConfig.config.enabled) {
36714
37689
  info("plugin DISABLED via config (enabled: false) — skipping registration");
36715
37690
  return;
@@ -36720,6 +37695,10 @@ async function src_default(pi) {
36720
37695
  cacheSizeMb: bootConfig.config.sqlite.cache_size_mb,
36721
37696
  mmapSizeMb: bootConfig.config.sqlite.mmap_size_mb
36722
37697
  });
37698
+ if (!databaseExitHookRegistered) {
37699
+ process.once("exit", closeDatabase);
37700
+ databaseExitHookRegistered = true;
37701
+ }
36723
37702
  const storageDir = getMagicContextStorageDir();
36724
37703
  const dbPath = join11(storageDir, "context.db");
36725
37704
  let openFailureCause = null;
@@ -36793,6 +37772,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36793
37772
  const offset = afterSessionId === null ? 0 : sessions.findIndex((session) => session.sessionId === afterSessionId) + 1;
36794
37773
  return sessions.slice(offset, offset + limit);
36795
37774
  });
37775
+ const api = await loadDefaultPiSessionApi();
37776
+ const paths = new Map;
37777
+ for (const session of await api.listSessions()) {
37778
+ if (typeof session.id === "string" && typeof session.path === "string")
37779
+ paths.set(session.id, session.path);
37780
+ }
37781
+ await backfillPiSessionActivity(database, PI_HARNESS_KIND, paths);
36796
37782
  } catch (err) {
36797
37783
  warn(`[session-projects] background runner failed: ${err}`);
36798
37784
  }
@@ -36802,6 +37788,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36802
37788
  const projectDir = process.cwd();
36803
37789
  const seenDreamerProjectIdentities = new Set;
36804
37790
  const dreamerRegistrationOwner = {};
37791
+ activePiRuntimes.add(dreamerRegistrationOwner);
36805
37792
  const commandLifecycleController = new AbortController;
36806
37793
  registerCtxStatusLifecycleSignal(pi, commandLifecycleController.signal);
36807
37794
  let sessionShuttingDown = false;
@@ -37055,7 +38042,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37055
38042
  projectDepsByDir.delete(ctx.cwd);
37056
38043
  const current = resolveCurrentProjectDeps(ctx);
37057
38044
  activeModelRegistry = ctx.modelRegistry;
37058
- syncDreamerProjectRegistration(current, ctx.modelRegistry);
38045
+ if (ctx.hasUI)
38046
+ syncDreamerProjectRegistration(current, ctx.modelRegistry);
37059
38047
  syncCtxMemoryToolEnabled(pi, current.config.memory.enabled);
37060
38048
  const failuresToShow = claimConfigParseFailuresOnce("pi", current.configParseFailures);
37061
38049
  if (ctx.hasUI && failuresToShow.length > 0) {
@@ -37190,6 +38178,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37190
38178
  });
37191
38179
  info("registered /ctx-recomp");
37192
38180
  registerCtxWrapupCommand(pi, {
38181
+ shutdownSignal: commandLifecycleController.signal,
37193
38182
  db,
37194
38183
  runner: wrapupRunner,
37195
38184
  historianModel: bootProjectDeps.historianConfig?.model,
@@ -37241,7 +38230,10 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37241
38230
  dreamerEnabled: bootProjectDeps.dreamerEnabled,
37242
38231
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
37243
38232
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity),
37244
- ensureRegistered: (ctx) => syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry),
38233
+ ensureRegistered: (ctx) => {
38234
+ if (ctx.hasUI)
38235
+ syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry);
38236
+ },
37245
38237
  registrationOwner: dreamerRegistrationOwner
37246
38238
  });
37247
38239
  info("registered /ctx-dream");
@@ -37260,7 +38252,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37260
38252
  info("registered /ctx-embed");
37261
38253
  const dreamerConfig = bootProjectDeps.dreamerConfig;
37262
38254
  if (dreamerConfig) {
37263
- syncDreamerProjectRegistration(bootProjectDeps);
37264
38255
  info(`registered dreamer (${summarizeDreamSchedule(dreamerConfig)})`);
37265
38256
  } else {
37266
38257
  info(bootProjectDeps.dreamerEnabled ? "registered dreamer: DISABLED (no dreamer config)" : "registered dreamer: DISABLED (dreamer.disable=true or no dreamer config)");
@@ -37293,7 +38284,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37293
38284
  const effectiveConfig = effectiveProjectDeps.config;
37294
38285
  try {
37295
38286
  activeModelRegistry = ctx.modelRegistry;
37296
- syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
38287
+ if (ctx.hasUI)
38288
+ syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
37297
38289
  } catch (err) {
37298
38290
  warn("before_agent_start: dreamer registration sync threw:", err);
37299
38291
  }
@@ -37389,6 +38381,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37389
38381
  } catch (err) {
37390
38382
  log(`agent_end: channel2 delivery skipped: ${String(err)}`);
37391
38383
  }
38384
+ if (!ctx.hasUI)
38385
+ flushLogger();
37392
38386
  });
37393
38387
  pi.on("tool_execution_start", async (event, ctx) => {
37394
38388
  try {
@@ -37469,6 +38463,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37469
38463
  if (typeof sessionId !== "string" || sessionId.length === 0)
37470
38464
  return;
37471
38465
  const endedMsg = event.message;
38466
+ observePiMessageActivity(db, sessionId, endedMsg?.timestamp);
37472
38467
  if (endedMsg?.role === "assistant" && typeof endedMsg.id === "string" && endedMsg.id.length > 0) {
37473
38468
  const messageId = endedMsg.id;
37474
38469
  scheduleIncrementalIndex(db, sessionId, messageId, () => {
@@ -37549,6 +38544,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37549
38544
  pi.on("session_shutdown", async (_event, ctx) => {
37550
38545
  sessionShuttingDown = true;
37551
38546
  commandLifecycleController.abort();
38547
+ stopStatusDialogRefresh();
37552
38548
  const SHUTDOWN_DRAIN_MS = 5000;
37553
38549
  const sessionId = resolveSessionId(ctx);
37554
38550
  try {
@@ -37562,6 +38558,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37562
38558
  warn("shutdown: unregisterPiDreamerProject threw:", err);
37563
38559
  }
37564
38560
  if (sessionId) {
38561
+ abortInFlightHistorians(sessionId);
38562
+ abortInFlightRecomps(sessionId);
37565
38563
  try {
37566
38564
  await withTimeout(awaitInFlightHistorians(sessionId), SHUTDOWN_DRAIN_MS);
37567
38565
  } catch (err) {
@@ -37574,6 +38572,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37574
38572
  }
37575
38573
  abortInFlightRecomps(sessionId);
37576
38574
  }
38575
+ abortInFlightDreamers(dreamerRegistrationOwner);
37577
38576
  try {
37578
38577
  await withTimeout(awaitInFlightDreamers(dreamerRegistrationOwner), SHUTDOWN_DRAIN_MS);
37579
38578
  } catch (err) {
@@ -37588,6 +38587,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37588
38587
  clearContextHandlerSession(sessionId);
37589
38588
  }
37590
38589
  } catch {}
38590
+ activePiRuntimes.delete(dreamerRegistrationOwner);
38591
+ if (activePiRuntimes.size === 0)
38592
+ unregisterPiProjectEmbeddings(db);
37591
38593
  });
37592
38594
  pi.on("session_before_switch", (_event, ctx) => {
37593
38595
  try {