@cortexkit/pi-magic-context 0.44.1 → 0.44.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ import {
49
49
  nextDueAtMs,
50
50
  resolveModelConfigValue,
51
51
  DEFAULT_HISTORIAN_TIMEOUT_MS,
52
+ DREAM_TASK_PROMOTION_DEFAULTS,
52
53
  LIVE_RELOAD_CONFIG_PATHS,
53
54
  getProtectedTokensTierOverrides,
54
55
  FAIL_CLOSED_DOCTOR_COMMAND,
@@ -59,6 +60,7 @@ import {
59
60
  createFailClosedController,
60
61
  ProjectIdentityError,
61
62
  resolveProjectIdentityStrict,
63
+ isUserHomeDirectory,
62
64
  resolveProjectIdentity,
63
65
  isUsableProjectIdentity,
64
66
  resolveProjectIdentityForSession,
@@ -66,6 +68,17 @@ import {
66
68
  scheduleAfterBootQuiet,
67
69
  clearCompressionDepth,
68
70
  clearCompressionDepthRange,
71
+ formatSynapseLaneDescriptor,
72
+ buildCanonicalChunkTextFromFts,
73
+ buildCompartmentSummaryFallbackText,
74
+ canonicalizeInMemoryChunkTextForEmbedding,
75
+ chunkCanonicalText,
76
+ chunkEmbeddingWindowsAreCurrent,
77
+ replaceCompartmentChunkEmbeddings,
78
+ deleteChunkEmbedBackoffForSession,
79
+ readSessionActivity,
80
+ observeSessionActivity,
81
+ backfillSessionActivity,
69
82
  getMessageIndexSourceIdentity,
70
83
  isMessageIndexSourceCurrent,
71
84
  getLastIndexedOrdinal,
@@ -168,6 +181,12 @@ import {
168
181
  retryPendingSessionCleanups,
169
182
  retryPendingRustSessionCleanupsForProject,
170
183
  recordSessionProjectIdentity,
184
+ embedPauseBySession,
185
+ embedRunStateBySession,
186
+ autoEmbedAttemptedBySession,
187
+ autoEmbedIdentityBySession,
188
+ invalidateAutoEmbedSession,
189
+ getEmbedDrainUiStatus,
171
190
  piHarnessKindFromExecutable,
172
191
  COMPARTMENT_LEASE_RENEWAL_MS,
173
192
  acquireCompartmentLease,
@@ -231,6 +250,7 @@ import {
231
250
  runSqliteOptimize,
232
251
  openDatabase,
233
252
  openDatabaseAsync,
253
+ closeDatabase,
234
254
  queueM0Mutation,
235
255
  getMaxM0MutationId,
236
256
  queueMemoryMutation,
@@ -325,13 +345,6 @@ import {
325
345
  peekCurateCategoryScope,
326
346
  beginCurateCategoryRun,
327
347
  curateTaskStateAfterSuccess,
328
- formatSynapseLaneDescriptor,
329
- buildCanonicalChunkTextFromFts,
330
- buildCompartmentSummaryFallbackText,
331
- canonicalizeInMemoryChunkTextForEmbedding,
332
- chunkCanonicalText,
333
- chunkEmbeddingWindowsAreCurrent,
334
- replaceCompartmentChunkEmbeddings,
335
348
  cosineSimilarity,
336
349
  GIT_SWEEP_LEASE_RENEWAL_MS,
337
350
  acquireGitSweepLease,
@@ -353,6 +366,7 @@ import {
353
366
  drainCommitBacklogForProject,
354
367
  embedSessionCompartmentChunks,
355
368
  getEmbeddingCoverageStatus,
369
+ getAutoEmbeddingSessionCoverage,
356
370
  promoteSessionFactsDurable,
357
371
  embedPromotedFacts,
358
372
  recordMemoryMapping,
@@ -373,6 +387,7 @@ import {
373
387
  embedUnembeddedCommits,
374
388
  loadPiConfig,
375
389
  loadPiConfigDetailed,
390
+ unregisterPiProjectEmbeddings,
376
391
  ensureProjectRegisteredFromPiDirectory,
377
392
  resolvePiHarnessDetection,
378
393
  resolvePiHarnessKind,
@@ -408,7 +423,7 @@ import {
408
423
  registerTodoStateLifecycle,
409
424
  syncCtxMemoryToolEnabled,
410
425
  registerMagicContextTools
411
- } from "./index-mwpk0wn9.js";
426
+ } from "./index-esnhck8v.js";
412
427
  import {
413
428
  CTX_REDUCE_KEEP,
414
429
  newestCtxReduceTagNumbers,
@@ -496,6 +511,12 @@ import {
496
511
  buildTrueRawTokenIndex,
497
512
  computeRawRangeFingerprint,
498
513
  invalidateTrueRawTokenCache,
514
+ DreamTokenBudgetExceeded,
515
+ TOKEN_BUDGET_FINALIZE_MESSAGE,
516
+ TOKEN_BUDGET_TOOL_REFUSAL,
517
+ registerBudgetFinalizeChild,
518
+ releaseBudgetFinalizeChild,
519
+ createDreamTokenBudget,
499
520
  droppedInputMarker,
500
521
  createDroppedInputGuard,
501
522
  isEditTool,
@@ -522,10 +543,14 @@ import {
522
543
  getRawSessionTagKeysThrough2,
523
544
  getLegacyProtectedTailStartOrdinal2,
524
545
  readSessionChunk2
525
- } from "./index-nm3c0sn8.js";
546
+ } from "./index-gsff513e.js";
526
547
  import {
527
548
  pushNotification2
528
549
  } from "./index-b3eqj1g6.js";
550
+ import {
551
+ subagentStepCap,
552
+ SubagentTelemetry
553
+ } from "./index-n8kg008j.js";
529
554
  import"./index-5fw91enm.js";
530
555
  import {
531
556
  logSlowWriteTransaction
@@ -822,6 +847,15 @@ var CANONICAL_DREAM_TASKS = [
822
847
  "promote-primers",
823
848
  "refresh-primers"
824
849
  ];
850
+ var DREAM_TOOL_LOOP_TOKEN_BUDGETS = {
851
+ "map-memories": 1500000,
852
+ verify: 1700000,
853
+ "verify-broad": 1700000,
854
+ curate: 1500000,
855
+ retrospective: 300000,
856
+ "maintain-docs": 1600000,
857
+ "refresh-primers": 350000
858
+ };
825
859
  var DREAM_TASK_CAPABILITIES = {
826
860
  "map-memories": {
827
861
  requiresTools: true,
@@ -934,6 +968,7 @@ function buildDreamTaskRuntimeConfigs(dreamer, harness, language, muralModel) {
934
968
  thinkingLevel: harness === "pi" ? resolved.primary?.qualifier : undefined,
935
969
  language,
936
970
  timeoutMinutes: resolved.timeoutMinutes ?? 20,
971
+ tokenBudget: task in DREAM_TOOL_LOOP_TOKEN_BUDGETS ? dreamer?.tasks?.[task]?.token_budget ?? DREAM_TOOL_LOOP_TOKEN_BUDGETS[task] : undefined,
937
972
  promotionThreshold: resolved.promotionThreshold,
938
973
  docsMaxTokens: task === "maintain-docs" ? dreamer?.tasks?.["maintain-docs"]?.max_tokens ?? 12000 : undefined,
939
974
  retrospectiveRecencyDays: task === "retrospective" ? dreamer?.tasks?.retrospective?.recency_days ?? 30 : undefined
@@ -1527,7 +1562,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1527
1562
  const startedAt = performance.now();
1528
1563
  const harness = getHarness();
1529
1564
  const now = options.now ?? Date.now;
1530
- const resolveIdentity = options.resolveIdentity ?? resolveProjectIdentity;
1565
+ const resolveIdentity = options.resolveIdentity ?? ((directory) => resolveProjectIdentityForSession(directory, options.allowHomeProject));
1531
1566
  const yieldFn = options.yieldFn ?? defaultYieldFn;
1532
1567
  const holderId = options.holderId ?? randomUUID();
1533
1568
  const readPage = createPageReader(source);
@@ -1541,7 +1576,8 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1541
1576
  skippedEmptyDirectories: 0,
1542
1577
  durationMs: 0
1543
1578
  };
1544
- const leaseStatus = acquireBackfillLease(db, harness, holderId, now());
1579
+ const leaseKey = options.leaseKey ?? harness;
1580
+ const leaseStatus = acquireBackfillLease(db, leaseKey, holderId, now());
1545
1581
  if (leaseStatus !== "acquired") {
1546
1582
  result.status = leaseStatus;
1547
1583
  result.durationMs = performance.now() - startedAt;
@@ -1553,10 +1589,10 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1553
1589
  return result;
1554
1590
  };
1555
1591
  const yieldAndRenew = async () => {
1556
- if (!renewBackfillLease(db, harness, holderId, now()))
1592
+ if (!renewBackfillLease(db, leaseKey, holderId, now()))
1557
1593
  return false;
1558
1594
  await yieldFn();
1559
- return renewBackfillLease(db, harness, holderId, now());
1595
+ return renewBackfillLease(db, leaseKey, holderId, now());
1560
1596
  };
1561
1597
  const existenceCache = new Map;
1562
1598
  const identityCache = new Map;
@@ -1570,7 +1606,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1570
1606
  sourcePage = await readPage(afterSessionId, SESSION_PAGE_SIZE);
1571
1607
  } catch (error) {
1572
1608
  try {
1573
- if (!markBackfillRetryPending(db, harness, holderId, now())) {
1609
+ if (!markBackfillRetryPending(db, leaseKey, holderId, now())) {
1574
1610
  log("[session-projects] backfill lease changed before failure cleanup");
1575
1611
  }
1576
1612
  } catch (releaseError) {
@@ -1595,7 +1631,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1595
1631
  result.alreadyMappedSessions += 1;
1596
1632
  } else {
1597
1633
  result.unmappedSessions += 1;
1598
- if (!session.directory) {
1634
+ if (!session.directory || !options.allowHomeProject && isUserHomeDirectory(session.directory)) {
1599
1635
  result.skippedEmptyDirectories += 1;
1600
1636
  } else {
1601
1637
  const stillExists = existenceCache.get(session.directory) ?? existsSync(session.directory);
@@ -1613,13 +1649,18 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1613
1649
  }
1614
1650
  identity = await resolveIdentity(session.directory);
1615
1651
  identityResolutionsSinceYield += 1;
1616
- if (!renewBackfillLease(db, harness, holderId, now())) {
1652
+ if (!renewBackfillLease(db, leaseKey, holderId, now())) {
1617
1653
  return finishWithLostLease();
1618
1654
  }
1619
- identityCache.set(session.directory, identity);
1655
+ if (identity)
1656
+ identityCache.set(session.directory, identity);
1657
+ }
1658
+ if (identity) {
1659
+ recordSessionProjectIdentity(db, session.sessionId, identity);
1660
+ result.backfilledSessions += 1;
1661
+ } else {
1662
+ result.skippedEmptyDirectories += 1;
1620
1663
  }
1621
- recordSessionProjectIdentity(db, session.sessionId, identity);
1622
- result.backfilledSessions += 1;
1623
1664
  }
1624
1665
  }
1625
1666
  }
@@ -1635,11 +1676,11 @@ async function runSessionProjectBackfill(db, source, options = {}) {
1635
1676
  }
1636
1677
  const hasSkippedSessions = result.skippedDeadDirectories > 0 || result.skippedEmptyDirectories > 0;
1637
1678
  if (hasSkippedSessions) {
1638
- if (!markBackfillRetryPending(db, harness, holderId, now())) {
1679
+ if (!markBackfillRetryPending(db, leaseKey, holderId, now())) {
1639
1680
  return finishWithLostLease();
1640
1681
  }
1641
1682
  result.status = "retry_pending";
1642
- } else if (!markBackfillCompleted(db, harness, holderId, now())) {
1683
+ } else if (!markBackfillCompleted(db, leaseKey, holderId, now())) {
1643
1684
  return finishWithLostLease();
1644
1685
  }
1645
1686
  result.durationMs = performance.now() - startedAt;
@@ -1709,7 +1750,7 @@ function countFailures(db) {
1709
1750
  function classifyBackfillError(error) {
1710
1751
  if (error instanceof ProjectIdentityError) {
1711
1752
  return {
1712
- errorClass: error.errorClass === "dubious_ownership" ? "unknown" : error.errorClass,
1753
+ errorClass: error.errorClass === "dubious_ownership" || error.errorClass === "home_project_disabled" || error.errorClass === "git_identity_unavailable" ? "unknown" : error.errorClass,
1713
1754
  errorMessage: error.message
1714
1755
  };
1715
1756
  }
@@ -2313,10 +2354,12 @@ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
2313
2354
  class SmartNoteNetworkError extends Error {
2314
2355
  isSmartNoteNetworkError = true;
2315
2356
  terminal;
2357
+ persistent;
2316
2358
  constructor(message, options = {}) {
2317
2359
  super(message);
2318
2360
  this.name = "SmartNoteNetworkError";
2319
2361
  this.terminal = options.terminal ?? false;
2362
+ this.persistent = options.persistent ?? false;
2320
2363
  }
2321
2364
  }
2322
2365
 
@@ -2438,6 +2481,7 @@ function storeCompiledSmartNoteCheck(db, args) {
2438
2481
  check_failure_count = 0,
2439
2482
  check_network_failure_count = 0,
2440
2483
  check_quarantined_until = NULL,
2484
+ ready_reason = NULL,
2441
2485
  check_next_due_at = ?,
2442
2486
  check_compiled_at = ?,
2443
2487
  check_false_since_at = COALESCE(check_false_since_at, ?),
@@ -2486,15 +2530,17 @@ function markSmartNoteLivenessChecked(db, noteId, now) {
2486
2530
  function markSmartNoteCheckStatus(db, noteId, status, now) {
2487
2531
  db.prepare(`UPDATE notes SET check_status = ?, updated_at = ? WHERE id = ? AND type = 'smart'`).run(status, now, noteId);
2488
2532
  }
2489
- function markSmartNoteCompilationFailure(db, noteId, now, maxFailures) {
2533
+ function markSmartNoteCompilationFailure(db, noteId, now, maxFailures, error, persistent) {
2490
2534
  const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
2491
- const status = failureCount >= maxFailures ? "fallback" : "uncompiled";
2535
+ const status = persistent ? "uncompiled" : failureCount >= maxFailures ? "fallback" : "uncompiled";
2536
+ const nextDueAt = now + (persistent ? 7 * 24 * 60 * 60 * 1000 : backoffMs(failureCount));
2492
2537
  db.prepare(`UPDATE notes
2493
2538
  SET check_failure_count = ?,
2494
2539
  check_status = ?,
2495
2540
  check_next_due_at = ?,
2541
+ ready_reason = ?,
2496
2542
  updated_at = ?
2497
- WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
2543
+ WHERE id = ? AND type = 'smart'`).run(failureCount, status, nextDueAt, persistent ? `Condition can't be checked: ${error}; rewrite it` : null, now, noteId);
2498
2544
  }
2499
2545
  function readFailureCount(db, noteId, column) {
2500
2546
  if (column !== "check_failure_count" && column !== "check_network_failure_count")
@@ -2542,7 +2588,10 @@ function countCompartmentsSince(db, projectPath, since) {
2542
2588
  return row?.cnt ?? 0;
2543
2589
  }
2544
2590
  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);
2591
+ const row = since === null ? db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ?").get(projectPath) : db.prepare(`SELECT COUNT(*) AS cnt FROM session_projects sp
2592
+ JOIN schema_migrations_meta activity
2593
+ ON activity.key = 'retrospective_activity:' || sp.session_id
2594
+ WHERE sp.project_path = ? AND CAST(activity.value AS INTEGER) > ?`).get(projectPath, since);
2546
2595
  return row?.cnt ?? 0;
2547
2596
  }
2548
2597
  function countMappedMemories(db, projectPath) {
@@ -2651,12 +2700,14 @@ function getDreamTaskBacklog(db, projectPath, task, options = {}) {
2651
2700
  };
2652
2701
  }
2653
2702
  case "retrospective": {
2654
- const pending = countProjectSessionsSince(db, projectPath, options.retrospectiveWatermarkMs ?? null);
2703
+ const watermark = options.retrospectiveWatermarkMs !== undefined ? options.retrospectiveWatermarkMs : getTaskScheduleState(db, projectPath, task)?.retrospectiveWatermarkMs;
2704
+ const pending = countProjectSessionsSince(db, projectPath, watermark ?? null);
2655
2705
  return { pending, total: pending };
2656
2706
  }
2657
2707
  case "maintain-docs": {
2658
2708
  const total = countCompartmentsSince(db, projectPath, 0);
2659
- const pending = countCompartmentsSince(db, projectPath, options.lastRunAt ?? 0);
2709
+ const lastRunAt = options.lastRunAt !== undefined ? options.lastRunAt : getTaskScheduleState(db, projectPath, task)?.lastRunAt;
2710
+ const pending = countCompartmentsSince(db, projectPath, lastRunAt ?? 0);
2660
2711
  return { pending, total };
2661
2712
  }
2662
2713
  case "evaluate-smart-notes": {
@@ -2711,7 +2762,7 @@ function evaluateTaskGate(task, ctx) {
2711
2762
  case "review-user-memories":
2712
2763
  return getUserMemoryCandidates(db).length >= ctx.promotionThreshold;
2713
2764
  case "promote-primers":
2714
- return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? 2);
2765
+ return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"]);
2715
2766
  case "refresh-primers":
2716
2767
  return getActivePrimers(db, project).some((primer) => !primer.answer.trim() || primer.answerRefreshedAt == null || (primer.lastObservedAt ?? 0) > primer.answerRefreshedAt);
2717
2768
  default: {
@@ -2758,6 +2809,11 @@ var USER_FACING_FAILURES = {
2758
2809
  sentence: "Memory maintenance stopped at its hidden agent step limit.",
2759
2810
  action: "This task needs less work per run; changing the model connection will not help."
2760
2811
  },
2812
+ dream_token_budget: {
2813
+ code: "MC-D11",
2814
+ sentence: "Memory maintenance reached its prompt-token budget.",
2815
+ action: "The unfinished items will be retried on the next run."
2816
+ },
2761
2817
  dream_empty_completion: {
2762
2818
  code: "MC-D03",
2763
2819
  sentence: "Memory maintenance received no usable response.",
@@ -2975,6 +3031,7 @@ var DREAM_FAILURE_KEYS = {
2975
3031
  provider_timeout: "dream_provider_timeout",
2976
3032
  provider_error: "dream_provider_error",
2977
3033
  step_limit: "dream_step_limit",
3034
+ token_budget: "dream_token_budget",
2978
3035
  empty_completion: "dream_empty_completion",
2979
3036
  no_models: "dream_no_models",
2980
3037
  child_aborted: "dream_child_aborted",
@@ -3042,7 +3099,7 @@ function openOpenCodeDb() {
3042
3099
 
3043
3100
  // ../plugin/src/features/magic-context/dreamer/task-executor.ts
3044
3101
  import { createHash as createHash10 } from "node:crypto";
3045
- import { existsSync as existsSync6 } from "node:fs";
3102
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
3046
3103
 
3047
3104
  // ../plugin/src/agents/dreamer.ts
3048
3105
  var DREAMER_AGENT = "dreamer";
@@ -3175,7 +3232,7 @@ var READ_TOOLS = ["read", "grep", "glob"];
3175
3232
  var AGENT_TOOLS = {
3176
3233
  [HIDDEN_HISTORIAN_AGENT]: [],
3177
3234
  [HIDDEN_DREAMER_AGENT]: [],
3178
- [HIDDEN_CURATE_AGENT]: ["ctx_memory", "ctx_memory_list"],
3235
+ [HIDDEN_CURATE_AGENT]: ["ctx_memory"],
3179
3236
  [DREAMER_MEMORY_MAPPER_AGENT]: READ_TOOLS,
3180
3237
  [DREAMER_PRIMER_INVESTIGATOR_AGENT]: [...READ_TOOLS, "ctx_search"],
3181
3238
  [DREAMER_RETROSPECTIVE_AGENT]: ["ctx_search"]
@@ -3424,7 +3481,7 @@ async function abortChildRun(client, sessionId) {
3424
3481
  }
3425
3482
  }
3426
3483
  function isNonRetryable(error, externalSignal) {
3427
- if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit)
3484
+ if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit || error instanceof DreamTokenBudgetExceeded)
3428
3485
  return true;
3429
3486
  if (error instanceof Error) {
3430
3487
  if (error.name === "AbortError")
@@ -3450,6 +3507,8 @@ function classifyPromptFailure(error, phase, externalSignal) {
3450
3507
  const message = extractMessage(error);
3451
3508
  if (error instanceof HiddenAgentStepLimit)
3452
3509
  return "step_limit";
3510
+ if (error instanceof DreamTokenBudgetExceeded)
3511
+ return "token_budget";
3453
3512
  if (externalSignal?.aborted || message === "prompt aborted by external signal") {
3454
3513
  return "child_aborted";
3455
3514
  }
@@ -3700,10 +3759,115 @@ function normalizeSDKResponse(response, fallback, options) {
3700
3759
  }
3701
3760
  return fallback;
3702
3761
  }
3762
+ // ../plugin/src/features/magic-context/subagent-token-capture.ts
3763
+ function asNumber(value) {
3764
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
3765
+ }
3766
+ function tokenObjectFromMessage(message) {
3767
+ const info = message.info;
3768
+ if (info && typeof info === "object") {
3769
+ const tokens = info.tokens;
3770
+ if (tokens && typeof tokens === "object")
3771
+ return tokens;
3772
+ }
3773
+ const tokens = message.tokens;
3774
+ if (tokens && typeof tokens === "object")
3775
+ return tokens;
3776
+ const usage = message.usage;
3777
+ if (usage && typeof usage === "object")
3778
+ return usage;
3779
+ return null;
3780
+ }
3781
+ function isAssistantMessage(message) {
3782
+ if (!message || typeof message !== "object")
3783
+ return false;
3784
+ const record = message;
3785
+ const info = record.info;
3786
+ if (info && typeof info === "object") {
3787
+ return info.role === "assistant";
3788
+ }
3789
+ return record.role === "assistant";
3790
+ }
3791
+ function modelFromMessage(message) {
3792
+ const info = message.info;
3793
+ const source = info && typeof info === "object" ? info : message;
3794
+ return {
3795
+ providerId: typeof source.providerID === "string" ? source.providerID : typeof source.providerId === "string" ? source.providerId : null,
3796
+ modelId: typeof source.modelID === "string" ? source.modelID : typeof source.modelId === "string" ? source.modelId : null
3797
+ };
3798
+ }
3799
+ function failedInvocationStatus(error) {
3800
+ const message = describeError(error).brief;
3801
+ if (/timed out after \d+ms|prompt timed out/i.test(message))
3802
+ return "timed_out";
3803
+ if (error !== null && typeof error === "object" && error.name === "TimeoutError") {
3804
+ return "timed_out";
3805
+ }
3806
+ if (/length-capped|no (?:assistant )?output|empty (?:assistant )?(?:output|text)/i.test(message)) {
3807
+ return "empty";
3808
+ }
3809
+ return "failed";
3810
+ }
3811
+ function emptyTokenTotals() {
3812
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
3813
+ }
3814
+ function sumTokensFromChildMessages(messages) {
3815
+ const totals = emptyTokenTotals();
3816
+ for (const message of messages) {
3817
+ if (!isAssistantMessage(message))
3818
+ continue;
3819
+ const tokens = tokenObjectFromMessage(message);
3820
+ if (!tokens)
3821
+ continue;
3822
+ const cache = tokens.cache && typeof tokens.cache === "object" ? tokens.cache : {};
3823
+ totals.input += asNumber(tokens.input);
3824
+ totals.output += asNumber(tokens.output);
3825
+ totals.cacheRead += asNumber(cache.read ?? tokens.cacheRead ?? tokens.cache_read);
3826
+ totals.cacheWrite += asNumber(cache.write ?? tokens.cacheWrite ?? tokens.cache_write);
3827
+ }
3828
+ return totals;
3829
+ }
3830
+ function findLastAssistantModel(messages) {
3831
+ for (let index = messages.length - 1;index >= 0; index -= 1) {
3832
+ const message = messages[index];
3833
+ if (isAssistantMessage(message))
3834
+ return modelFromMessage(message);
3835
+ }
3836
+ return { providerId: null, modelId: null };
3837
+ }
3838
+ function recordChildInvocation(input) {
3839
+ if (!input.db)
3840
+ return null;
3841
+ const tokens = input.tokens ?? sumTokensFromChildMessages(input.messages ?? []);
3842
+ const model = input.providerId !== undefined || input.modelId !== undefined ? { providerId: input.providerId ?? null, modelId: input.modelId ?? null } : findLastAssistantModel(input.messages ?? []);
3843
+ try {
3844
+ return recordSubagentInvocation(input.db, {
3845
+ sessionId: input.parentSessionId,
3846
+ harness: input.harness,
3847
+ subagent: input.subagent,
3848
+ task: input.task ?? null,
3849
+ providerId: model.providerId,
3850
+ modelId: model.modelId,
3851
+ startedAt: input.startedAt,
3852
+ endedAt: input.endedAt ?? Date.now(),
3853
+ status: input.status,
3854
+ inputTokens: tokens.input,
3855
+ outputTokens: tokens.output,
3856
+ cacheReadTokens: tokens.cacheRead,
3857
+ cacheWriteTokens: tokens.cacheWrite,
3858
+ error: input.error ? describeError(input.error).brief : null,
3859
+ parentInvocationId: input.parentInvocationId ?? null
3860
+ });
3861
+ } catch (error) {
3862
+ sessionLog(input.parentSessionId, "subagent token accounting failed:", describeError(error).brief);
3863
+ return null;
3864
+ }
3865
+ }
3866
+
3703
3867
  // ../plugin/src/shared/prompt-async-transport.ts
3704
3868
  var DEFAULT_POLL_INTERVAL_MS = 1000;
3705
3869
  var DEFAULT_START_GRACE_MS = 30000;
3706
- var MESSAGE_WINDOW = 20;
3870
+ var MESSAGE_WINDOW = 100;
3707
3871
  function sessionApi(client) {
3708
3872
  return client?.session;
3709
3873
  }
@@ -3714,7 +3878,8 @@ function supportsPromptAsync(client) {
3714
3878
  function createPromptAsyncTransport(client, childSessionId, options = {}) {
3715
3879
  if (!supportsPromptAsync(client))
3716
3880
  return;
3717
- return Object.assign((request) => promptAsyncAndWaitForIdle(client, request, options), { childSessionId });
3881
+ const budgetGuard = options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined;
3882
+ return Object.assign((request) => promptAsyncAndWaitForIdle(client, request, { ...options, budgetGuard }), { childSessionId });
3718
3883
  }
3719
3884
  function infoOf(message) {
3720
3885
  const info = message?.info;
@@ -3730,6 +3895,10 @@ function isSettledAssistant(message) {
3730
3895
  return false;
3731
3896
  return info.time?.completed != null || info.error != null || info.finish != null;
3732
3897
  }
3898
+ function isTerminalAssistant(message) {
3899
+ const info = infoOf(message);
3900
+ return isSettledAssistant(message) && info.finish !== "tool-calls" && info.finish !== "tool_use" && !(message && typeof message === "object" && Array.isArray(message.parts) && message.parts.some((part) => part.type === "tool"));
3901
+ }
3733
3902
  function unwrapData(response) {
3734
3903
  if (response && typeof response === "object" && "data" in response) {
3735
3904
  return response.data;
@@ -3807,40 +3976,106 @@ async function promptAsyncAndWaitForIdle(client, request, options = {}) {
3807
3976
  const signal = request.signal;
3808
3977
  const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
3809
3978
  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));
3979
+ const budget = options.budgetGuard ?? (options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined);
3980
+ if (budget?.snapshot().finalizeFired)
3981
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
3982
+ const seenUsage = new Set;
3983
+ let finalizing = false;
3984
+ let completionBaseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((id) => id !== null));
3985
+ const initialBaseline = new Set(completionBaseline);
3811
3986
  const sent = await session.promptAsync?.(request);
3812
3987
  if (sent && typeof sent === "object" && "error" in sent) {
3813
3988
  const rejection = sent.error;
3814
3989
  if (rejection)
3815
3990
  throw new Error(`prompt_async was rejected: ${describeRejection(rejection)}`);
3816
3991
  }
3817
- const sentAt = Date.now();
3992
+ let sentAt = Date.now();
3818
3993
  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)
3994
+ try {
3995
+ for (;; ) {
3996
+ await sleep(pollIntervalMs, signal);
3997
+ const status = await readStatus(session, sessionId, dir, signal);
3998
+ const messages = await readMessages(session, sessionId, dir, signal);
3999
+ if (budget) {
4000
+ for (const message of messages) {
4001
+ const id = messageId(message);
4002
+ if (!id || seenUsage.has(id) || infoOf(message).role !== "assistant")
4003
+ continue;
4004
+ if (initialBaseline.has(id))
4005
+ continue;
4006
+ const tokens = sumTokensFromChildMessages([message]);
4007
+ if (tokens.input + tokens.cacheRead + tokens.cacheWrite === 0)
4008
+ continue;
4009
+ seenUsage.add(id);
4010
+ const decision = budget.charge(tokens.input, tokens.cacheRead, tokens.cacheWrite, isTerminalAssistant(message));
4011
+ options.onBudgetUpdate?.({ ...budget.snapshot(), sessionId });
4012
+ if (decision === "stop") {
4013
+ await session.abort?.({ path: { id: sessionId } });
4014
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4015
+ }
4016
+ if (decision === "finalize") {
4017
+ finalizing = true;
4018
+ registerBudgetFinalizeChild(sessionId, budget);
4019
+ if (!session.abort)
4020
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4021
+ await session.abort({ path: { id: sessionId } });
4022
+ const abortDeadline = Date.now() + startGraceMs;
4023
+ while (Date.now() < abortDeadline) {
4024
+ const state = await readStatus(session, sessionId, dir, signal);
4025
+ if (state === "idle")
4026
+ break;
4027
+ await sleep(pollIntervalMs, signal);
4028
+ }
4029
+ if (await readStatus(session, sessionId, dir, signal) !== "idle") {
4030
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4031
+ }
4032
+ completionBaseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((value) => value !== null));
4033
+ const finalizeResponse = await session.promptAsync?.({
4034
+ ...request,
4035
+ body: {
4036
+ ...request.body,
4037
+ parts: [{ type: "text", text: TOKEN_BUDGET_FINALIZE_MESSAGE }]
4038
+ }
4039
+ });
4040
+ if (finalizeResponse && typeof finalizeResponse === "object" && "error" in finalizeResponse && finalizeResponse.error) {
4041
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4042
+ }
4043
+ sentAt = Date.now();
4044
+ sawBusy = false;
4045
+ break;
4046
+ }
4047
+ }
4048
+ if (budget.snapshot().hardStopped) {
4049
+ await session.abort?.({ path: { id: sessionId } });
4050
+ throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
4051
+ }
4052
+ }
4053
+ if (status === "busy" || status === "retry") {
4054
+ sawBusy = true;
4055
+ continue;
4056
+ }
4057
+ if (status === null)
4058
+ continue;
4059
+ const current = finalizing ? await readMessages(session, sessionId, dir, signal) : messages;
4060
+ const last = current.at(-1);
4061
+ const lastId = messageId(last);
4062
+ if (last && lastId !== null && !completionBaseline.has(lastId) && isSettledAssistant(last))
3841
4063
  return;
3842
- throw new Error(`prompt_async did not start a run in child session ${sessionId} within ${startGraceMs}ms`);
4064
+ const fresh = current.some((message) => {
4065
+ const id = messageId(message);
4066
+ return id !== null && !completionBaseline.has(id);
4067
+ });
4068
+ if (sawBusy && fresh)
4069
+ return;
4070
+ if (Date.now() - sentAt >= startGraceMs) {
4071
+ if (fresh)
4072
+ return;
4073
+ throw new Error(`prompt_async did not start a run in child session ${sessionId} within ${startGraceMs}ms`);
4074
+ }
3843
4075
  }
4076
+ } finally {
4077
+ if (budget)
4078
+ releaseBudgetFinalizeChild(sessionId);
3844
4079
  }
3845
4080
  }
3846
4081
  // ../plugin/src/shared/summarize-child-stderr.ts
@@ -3950,111 +4185,6 @@ function forgetHistorianOutputCap(sessionId) {
3950
4185
  historianCaps.delete(sessionId);
3951
4186
  }
3952
4187
 
3953
- // ../plugin/src/features/magic-context/subagent-token-capture.ts
3954
- function asNumber(value) {
3955
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
3956
- }
3957
- function tokenObjectFromMessage(message) {
3958
- const info = message.info;
3959
- if (info && typeof info === "object") {
3960
- const tokens = info.tokens;
3961
- if (tokens && typeof tokens === "object")
3962
- return tokens;
3963
- }
3964
- const tokens = message.tokens;
3965
- if (tokens && typeof tokens === "object")
3966
- return tokens;
3967
- const usage = message.usage;
3968
- if (usage && typeof usage === "object")
3969
- return usage;
3970
- return null;
3971
- }
3972
- function isAssistantMessage(message) {
3973
- if (!message || typeof message !== "object")
3974
- return false;
3975
- const record = message;
3976
- const info = record.info;
3977
- if (info && typeof info === "object") {
3978
- return info.role === "assistant";
3979
- }
3980
- return record.role === "assistant";
3981
- }
3982
- function modelFromMessage(message) {
3983
- const info = message.info;
3984
- const source = info && typeof info === "object" ? info : message;
3985
- return {
3986
- providerId: typeof source.providerID === "string" ? source.providerID : typeof source.providerId === "string" ? source.providerId : null,
3987
- modelId: typeof source.modelID === "string" ? source.modelID : typeof source.modelId === "string" ? source.modelId : null
3988
- };
3989
- }
3990
- function failedInvocationStatus(error) {
3991
- const message = describeError(error).brief;
3992
- if (/timed out after \d+ms|prompt timed out/i.test(message))
3993
- return "timed_out";
3994
- if (error !== null && typeof error === "object" && error.name === "TimeoutError") {
3995
- return "timed_out";
3996
- }
3997
- if (/length-capped|no (?:assistant )?output|empty (?:assistant )?(?:output|text)/i.test(message)) {
3998
- return "empty";
3999
- }
4000
- return "failed";
4001
- }
4002
- function emptyTokenTotals() {
4003
- return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
4004
- }
4005
- function sumTokensFromChildMessages(messages) {
4006
- const totals = emptyTokenTotals();
4007
- for (const message of messages) {
4008
- if (!isAssistantMessage(message))
4009
- continue;
4010
- const tokens = tokenObjectFromMessage(message);
4011
- if (!tokens)
4012
- continue;
4013
- const cache = tokens.cache && typeof tokens.cache === "object" ? tokens.cache : {};
4014
- totals.input += asNumber(tokens.input);
4015
- totals.output += asNumber(tokens.output);
4016
- totals.cacheRead += asNumber(cache.read ?? tokens.cacheRead ?? tokens.cache_read);
4017
- totals.cacheWrite += asNumber(cache.write ?? tokens.cacheWrite ?? tokens.cache_write);
4018
- }
4019
- return totals;
4020
- }
4021
- function findLastAssistantModel(messages) {
4022
- for (let index = messages.length - 1;index >= 0; index -= 1) {
4023
- const message = messages[index];
4024
- if (isAssistantMessage(message))
4025
- return modelFromMessage(message);
4026
- }
4027
- return { providerId: null, modelId: null };
4028
- }
4029
- function recordChildInvocation(input) {
4030
- if (!input.db)
4031
- return null;
4032
- const tokens = input.tokens ?? sumTokensFromChildMessages(input.messages ?? []);
4033
- const model = input.providerId !== undefined || input.modelId !== undefined ? { providerId: input.providerId ?? null, modelId: input.modelId ?? null } : findLastAssistantModel(input.messages ?? []);
4034
- try {
4035
- return recordSubagentInvocation(input.db, {
4036
- sessionId: input.parentSessionId,
4037
- harness: input.harness,
4038
- subagent: input.subagent,
4039
- task: input.task ?? null,
4040
- providerId: model.providerId,
4041
- modelId: model.modelId,
4042
- startedAt: input.startedAt,
4043
- endedAt: input.endedAt ?? Date.now(),
4044
- status: input.status,
4045
- inputTokens: tokens.input,
4046
- outputTokens: tokens.output,
4047
- cacheReadTokens: tokens.cacheRead,
4048
- cacheWriteTokens: tokens.cacheWrite,
4049
- error: input.error ? describeError(input.error).brief : null,
4050
- parentInvocationId: input.parentInvocationId ?? null
4051
- });
4052
- } catch (error) {
4053
- sessionLog(input.parentSessionId, "subagent token accounting failed:", describeError(error).brief);
4054
- return null;
4055
- }
4056
- }
4057
-
4058
4188
  // ../plugin/src/hooks/magic-context/historian-prompt.generated.ts
4059
4189
  var COMPARTMENT_AGENT_SYSTEM_PROMPT = `# Historian
4060
4190
 
@@ -6783,7 +6913,7 @@ Project memory uses exactly 5 categories. Every memory belongs to one:
6783
6913
  var CURATE_SYSTEM_PROMPT = `You are a memory-pool curator for the magic-context system. You run during a scheduled dream window to keep a project's cross-session memory store lean and well-formed.
6784
6914
 
6785
6915
  ## Memory operations
6786
- - \`ctx_memory_list(category="...")\` — browse active memories, optionally filter by category
6916
+ - The scoped category snapshot arrives in the first message. Do not list or get it again; track mutation deltas.
6787
6917
  - \`action="merge", ids=[N,M,...], content="...", category="..."\` — consolidate duplicates into one canonical memory
6788
6918
  - \`action="update", ids=[N], content="...", superseded_by=M\` — rewrite content; name where removed detail survives when cutting more than half
6789
6919
  - \`action="write", category="...", content="..."\` — create a memory (SPLITS ONLY — never mint new facts)
@@ -6813,7 +6943,7 @@ var PRIMER_INVESTIGATOR_SYSTEM_PROMPT = `You are a read-only code investigator f
6813
6943
  function renderMemoryList(memories) {
6814
6944
  return memories.map((memory) => {
6815
6945
  const files = memory.mappedFiles.length ? memory.mappedFiles.join(", ") : "(none mapped yet)";
6816
- return `[${memory.id}] ${memory.category}
6946
+ return `[${memory.id}] ${memory.category} importance=${memory.importance} retrieval_count=${memory.retrievalCount} seen_count=${memory.seenCount}
6817
6947
  Content: ${memory.content}
6818
6948
  Mapped files: ${files}${memory.hasNoFileSentinel ? " (file-independent)" : ""}`;
6819
6949
  }).join(`
@@ -6841,9 +6971,41 @@ Rewrite narrative/historical → operational present tense ("X uses Y because Z"
6841
6971
  Archive a redundant memory only when a better ACTIVE memory in the same project and category preserves its information; name that survivor with \`superseded_by\`. A bare "redundant" verdict is deletion and will be refused. Leave standalone low-value or stale entries unchanged for a human to review. The global user profile describes the operator and is never a substitute for project knowledge, so it cannot justify an archive.
6842
6972
  KEEP (overrides archive): constraint/rule language (must/never/always) · explains WHY (because/so that/to prevent) · EXTERNAL-system limit (CONSTRAINTS: archive only if word-for-word duplicated) · path/config WITH context · retrieval_count>0 · priority/philosophy.
6843
6973
 
6844
- ### Memory pool
6974
+ ### Cross-chunk duplicate candidates (normalized content matches)
6975
+ ${args.crossChunkCandidates?.join(`
6976
+ `) || "(none)"}
6977
+
6978
+ ### Category snapshot (do not re-enumerate)
6845
6979
  ${renderMemoryList(args.memories)}`;
6846
6980
  }
6981
+ function chunkCurateMemories(memories, maxCharacters) {
6982
+ const chunks = [];
6983
+ let current = [];
6984
+ let size = 0;
6985
+ for (const memory of memories) {
6986
+ const length = renderMemoryList([memory]).length + 2;
6987
+ if (current.length && size + length > maxCharacters) {
6988
+ chunks.push(current);
6989
+ current = [];
6990
+ size = 0;
6991
+ }
6992
+ current.push(memory);
6993
+ size += length;
6994
+ }
6995
+ if (current.length)
6996
+ chunks.push(current);
6997
+ const keys = new Map;
6998
+ chunks.forEach((chunk, index) => {
6999
+ chunk.forEach((memory) => {
7000
+ const key = memory.content.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
7001
+ keys.set(key, [...keys.get(key) ?? [], { id: memory.id, chunk: index }]);
7002
+ });
7003
+ });
7004
+ return chunks.map((chunk, index) => ({
7005
+ memories: chunk,
7006
+ crossChunkCandidates: [...keys.values()].filter((matches) => matches.some((match) => match.chunk === index) && matches.some((match) => match.chunk !== index)).map((matches) => `IDs ${matches.map((match) => match.id).join(", ")}`)
7007
+ }));
7008
+ }
6847
7009
  var RETROSPECTIVE_SYSTEM_PROMPT = `You are a retrospective learning agent for Magic Context.
6848
7010
 
6849
7011
  You learn only from recurring user-friction moments where the user had to correct, re-explain, or recover from the assistant's repeated behavior. You receive a pre-rendered friction window from the host and may use ctx_search to look for corroborating prior patterns.
@@ -6902,17 +7064,23 @@ Return only XML in this exact shape:
6902
7064
  <learning route="observation">one recurring user preference</learning>
6903
7065
  </learnings>`;
6904
7066
  }
6905
- function buildMaintainDocsPrompt(projectPath, changeSet, existingDocs, budget = 12000, currentTokens = 0) {
7067
+ function buildMaintainDocsPrompt(projectPath, changeSet, existingDocs, budget = 12000, currentTokens = 0, currentDocs) {
6906
7068
  return `## Task: Propose documentation corrections
6907
7069
 
6908
7070
  Project: ${projectPath}
6909
7071
  Existing docs: ARCHITECTURE.md ${existingDocs.architecture ? "exists" : "missing"}; STRUCTURE.md ${existingDocs.structure ? "exists" : "missing"}.
6910
7072
  Current combined token count: ${currentTokens}. Combined budget: ${budget} tokens.
6911
7073
 
6912
- Host-collected code changes (commit subjects and changed files, not documentation history):
7074
+ Host-collected code changes (diff stat and hunks, or file/line ranges when oversized):
6913
7075
  ${changeSet}
6914
7076
 
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.
7077
+ Current ARCHITECTURE.md:
7078
+ ${currentDocs?.architecture ?? "(read from project)"}
7079
+
7080
+ Current STRUCTURE.md:
7081
+ ${currentDocs?.structure ?? "(read from project)"}
7082
+
7083
+ Find claims in these sections that the diff makes wrong or incomplete. Propose one focused change per claim or section. Do not investigate unrelated areas. Read the docs and relevant source with read-only tools. These files are short maps read by every agent in every session. Describe how the system works now, never what changed or when: no change log, dates, or commit lists. One short paragraph per subsystem at most. Mechanism detail belongs in docs/architecture/; you may name a docs page for it but never move that detail into these two files. Propose a change only when code contradicts the docs or a major piece is missing. Prefer rewriting a stale sentence to adding one. Propose nothing when nothing is wrong. Keep both files within the combined budget. Never touch protected regions (<!-- mc:protected START ... --> through <!-- mc:protected END -->); preserve their bytes.
6916
7084
 
6917
7085
  Return ONLY a JSON array (or []), with each entry {"file":"ARCHITECTURE.md"|"STRUCTURE.md","action":"replace"|"add"|"remove","heading":"## Exact section heading","text":"## Full replacement section including heading and body (empty for remove)","reason":"one-line reason"}. A replacement includes the entire section, including its heading. Additions are appended at the end of the file. Do not include any unmodified sections. No file writes.`;
6918
7086
  }
@@ -6922,10 +7090,11 @@ function buildDreamTaskPrompt(task, args) {
6922
7090
  return buildCuratePrompt({
6923
7091
  projectPath: args.projectPath,
6924
7092
  category: args.curate?.category ?? "PROJECT_RULES",
6925
- memories: args.curate?.memories ?? []
7093
+ memories: args.curate?.memories ?? [],
7094
+ crossChunkCandidates: args.curate?.crossChunkCandidates
6926
7095
  });
6927
7096
  case "maintain-docs":
6928
- return buildMaintainDocsPrompt(args.projectPath, args.docsChangeSet ?? "", args.existingDocs ?? { architecture: false, structure: false }, args.docsBudget, args.docsCurrentTokens);
7097
+ return buildMaintainDocsPrompt(args.projectPath, args.docsChangeSet ?? "", args.existingDocs ?? { architecture: false, structure: false }, args.docsBudget, args.docsCurrentTokens, args.docsContents);
6929
7098
  }
6930
7099
  }
6931
7100
 
@@ -7858,6 +8027,7 @@ function hasCurrentDocsProposal(projectDir) {
7858
8027
  }
7859
8028
  function git(projectDir, args) {
7860
8029
  return execFileSync("git", args, {
8030
+ windowsHide: true,
7861
8031
  cwd: projectDir,
7862
8032
  encoding: "utf8",
7863
8033
  timeout: 1e4,
@@ -7869,11 +8039,80 @@ function docsChangeSet(projectDir, storedAnchor) {
7869
8039
  if (realpathSync2(git(projectDir, ["rev-parse", "--show-toplevel"])) !== realpathSync2(projectDir))
7870
8040
  return null;
7871
8041
  const head = git(projectDir, ["rev-parse", "HEAD"]);
8042
+ const recorded = Boolean(storedAnchor);
7872
8043
  let anchor = storedAnchor;
7873
- if (!anchor || !/^[a-f0-9]{40}$/.test(anchor) || !git(projectDir, ["cat-file", "-t", anchor]).includes("commit"))
8044
+ if (anchor && (!/^[a-f0-9]{40}$/.test(anchor) || git(projectDir, ["cat-file", "-t", anchor]) !== "commit"))
8045
+ throw new Error("maintain-docs checkpoint is not a git commit");
8046
+ if (!anchor)
7874
8047
  anchor = git(projectDir, ["log", "-1", "--format=%H", "--", ...FILES]);
7875
8048
  if (!anchor)
7876
8049
  return null;
8050
+ if (recorded && anchor === head)
8051
+ return { head, text: "", unchanged: true, relevant: false };
8052
+ if (recorded) {
8053
+ const changed = git(projectDir, [
8054
+ "diff",
8055
+ "-M",
8056
+ "--name-status",
8057
+ `${anchor}..HEAD`,
8058
+ "--",
8059
+ "."
8060
+ ]).split(`
8061
+ `).filter(Boolean).map((line) => line.split("\t").slice(1));
8062
+ const files = [
8063
+ ...new Set(changed.flat().filter((file) => file !== "ARCHITECTURE.md" && file !== "STRUCTURE.md" && !/(?:^|[./-])(test|spec)(?:[./-]|$)/.test(file) && !file.endsWith(".lock")))
8064
+ ];
8065
+ const docs = FILES.map((file) => existsSync3(join3(projectDir, file)) ? readFileSync3(join3(projectDir, file), "utf8") : "").join(`
8066
+ `);
8067
+ const relevant = files.some((file) => {
8068
+ const directory = file.split("/").slice(0, -1).join("/");
8069
+ return docs.includes(file) || directory.length > 2 && docs.includes(directory);
8070
+ });
8071
+ if (!relevant)
8072
+ return { head, text: "", unchanged: false, relevant: false };
8073
+ const stat = git(projectDir, [
8074
+ "diff",
8075
+ "-M",
8076
+ "--stat",
8077
+ `${anchor}..HEAD`,
8078
+ "--",
8079
+ ...files
8080
+ ]);
8081
+ const patch = git(projectDir, [
8082
+ "diff",
8083
+ "-M",
8084
+ "--unified=3",
8085
+ `${anchor}..HEAD`,
8086
+ "--",
8087
+ ...files
8088
+ ]);
8089
+ const budget = 32000;
8090
+ const text = `Changed files:
8091
+ ${files.join(`
8092
+ `)}
8093
+
8094
+ Stat:
8095
+ ${stat}
8096
+
8097
+ Hunks:
8098
+ ${patch}`;
8099
+ if (text.length <= budget)
8100
+ return { head, text, unchanged: false, relevant: true };
8101
+ const ranges = [...patch.matchAll(/^\+\+\+ b\/(.+)$|^@@ .* \+(\d+)(?:,(\d+))? @@/gm)];
8102
+ return {
8103
+ head,
8104
+ unchanged: false,
8105
+ relevant: true,
8106
+ text: `Changed files and ranges (diff exceeds prompt budget):
8107
+ ${files.join(`
8108
+ `)}
8109
+ ${ranges.map((match) => match[1] ? `file: ${match[1]}` : `line: ${match[2]} +${match[3] ?? 1}`).join(`
8110
+ `).slice(0, budget)}
8111
+
8112
+ Stat:
8113
+ ${stat}`
8114
+ };
8115
+ }
7877
8116
  const excluded = [
7878
8117
  "ARCHITECTURE.md",
7879
8118
  "STRUCTURE.md",
@@ -7903,11 +8142,15 @@ function docsChangeSet(projectDir, storedAnchor) {
7903
8142
  const text = raw.slice(0, 24000);
7904
8143
  return {
7905
8144
  head,
8145
+ unchanged: false,
8146
+ relevant: true,
7906
8147
  text: `${text}${raw.length > text.length ? `
7907
8148
  [cut ${raw.length - text.length} bytes]` : ""}${commits.length > 201 ? `
7908
8149
  [cut commits beyond newest 200]` : ""}`
7909
8150
  };
7910
- } catch {
8151
+ } catch (error) {
8152
+ if (storedAnchor)
8153
+ throw error;
7911
8154
  return null;
7912
8155
  }
7913
8156
  }
@@ -7941,7 +8184,7 @@ function diff(name, before, after) {
7941
8184
  writeFileSync3(join3(dir, "before"), before);
7942
8185
  writeFileSync3(join3(dir, "after"), after);
7943
8186
  try {
7944
- return execFileSync("git", ["diff", "--no-index", "--", join3(dir, "before"), join3(dir, "after")], { encoding: "utf8" });
8187
+ return execFileSync("git", ["diff", "--no-index", "--", join3(dir, "before"), join3(dir, "after")], { windowsHide: true, encoding: "utf8" });
7945
8188
  } catch (error) {
7946
8189
  return error.stdout?.replaceAll(join3(dir, "before"), `a/${name}`).replaceAll(join3(dir, "after"), `b/${name}`) ?? "";
7947
8190
  }
@@ -8192,7 +8435,7 @@ function requestValidatedAddress(validation, candidate, options) {
8192
8435
  const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8193
8436
  bytes += buf.byteLength;
8194
8437
  if (bytes > options.bodyLimitBytes) {
8195
- reject(new SmartNoteNetworkError("SMART_NOTE_NETWORK: response body too large", { terminal: true }));
8438
+ reject(new SmartNoteNetworkError(`SMART_NOTE_NETWORK: response body too large at ${url.href} (received at least ${bytes} bytes; limit ${options.bodyLimitBytes})`, { terminal: true, persistent: true }));
8196
8439
  response.destroy();
8197
8440
  request2.destroy();
8198
8441
  return;
@@ -8529,6 +8772,7 @@ async function runGit(projectRoot, args, signal) {
8529
8772
  throwIfAborted2(signal);
8530
8773
  try {
8531
8774
  const result = await execFileAsync("git", ["-C", projectRoot, ...args], {
8775
+ windowsHide: true,
8532
8776
  timeout: DEFAULT_GIT_TIMEOUT_MS,
8533
8777
  maxBuffer: 128 * 1024,
8534
8778
  signal
@@ -8691,6 +8935,7 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
8691
8935
  const controller = new AbortController;
8692
8936
  let externallyCancelled = false;
8693
8937
  let executionTimedOut = false;
8938
+ let persistentNetworkFailure = false;
8694
8939
  const externalAbort = () => {
8695
8940
  externallyCancelled = true;
8696
8941
  controller.abort(options.signal?.reason);
@@ -8709,7 +8954,19 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
8709
8954
  context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
8710
8955
  context.runtime.setMaxStackSize(options.stackLimitBytes ?? DEFAULT_STACK_LIMIT_BYTES);
8711
8956
  context.runtime.setInterruptHandler(() => controller.signal.aborted || Date.now() > deadline);
8712
- installCapabilityObject(context, capabilities);
8957
+ installCapabilityObject(context, {
8958
+ ...capabilities,
8959
+ httpGet: async (url) => {
8960
+ try {
8961
+ return await capabilities.httpGet(url);
8962
+ } catch (error) {
8963
+ if (error instanceof SmartNoteNetworkError && error.persistent) {
8964
+ persistentNetworkFailure = true;
8965
+ }
8966
+ throw error;
8967
+ }
8968
+ }
8969
+ });
8713
8970
  disableAmbientDynamicCode(context);
8714
8971
  const result = await evalCheck(context, options.compiledCheck);
8715
8972
  const checkResult = result;
@@ -8723,14 +8980,14 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
8723
8980
  } catch (error) {
8724
8981
  if (externallyCancelled && !executionTimedOut)
8725
8982
  return cancelledResult(error);
8726
- return failureResult(formatSandboxError(error), isSmartNoteNetworkError(error));
8983
+ return failureResult(formatSandboxError(error), isSmartNoteNetworkError(error), persistentNetworkFailure);
8727
8984
  } finally {
8728
8985
  clearTimeout(timer);
8729
8986
  options.signal?.removeEventListener("abort", externalAbort);
8730
8987
  }
8731
8988
  }
8732
- function failureResult(error, network) {
8733
- return { ok: false, cancelled: false, error: truncate(error), network };
8989
+ function failureResult(error, network, persistent = false) {
8990
+ return { ok: false, cancelled: false, error: truncate(error), network, persistent };
8734
8991
  }
8735
8992
  function cancelledResult(reason) {
8736
8993
  return {
@@ -8828,7 +9085,12 @@ var MAX_CRON_CHARS = 256;
8828
9085
  var MAX_COMPILER_ERROR_CHARS = 2 * 1024;
8829
9086
  async function compileSmartNoteCheck(args) {
8830
9087
  if (!args.note.surfaceCondition) {
8831
- return { ok: false, cancelled: false, error: "note has no surface condition" };
9088
+ return {
9089
+ ok: false,
9090
+ cancelled: false,
9091
+ error: "note has no surface condition",
9092
+ persistent: false
9093
+ };
8832
9094
  }
8833
9095
  const prompt = `Compile this smart note condition into a sandbox check.
8834
9096
 
@@ -8935,15 +9197,13 @@ Remember: output only the JSON object described by the system prompt.`;
8935
9197
  const compiledCheck = normalizeCompiledCheck(response.compiled_check);
8936
9198
  const manifest = normalizeManifest(response.manifest);
8937
9199
  const checkCron = normalizeCron(response.check_cron);
8938
- for (const warning of manifestAdvisoryWarnings(compiledCheck, manifest)) {
9200
+ const dryRun = await dryRunSmartNoteCheck(compiledCheck, args.capabilityFactory, args.signal);
9201
+ for (const warning of [
9202
+ ...manifestAdvisoryWarnings(compiledCheck, manifest),
9203
+ ...dryRun.advisories
9204
+ ]) {
8939
9205
  log(`[dreamer] smart note #${args.note.id}: manifest advisory — ${warning}`);
8940
9206
  }
8941
- const dryRun = await runCompiledSmartNoteCheck({
8942
- compiledCheck,
8943
- capabilityFactory: args.capabilityFactory,
8944
- signal: args.signal,
8945
- timeoutMs: 2000
8946
- });
8947
9207
  if (!dryRun.ok) {
8948
9208
  const error = boundedError(`dry-run failed: ${dryRun.error}`);
8949
9209
  recordInvocation({
@@ -8951,7 +9211,12 @@ Remember: output only the JSON object described by the system prompt.`;
8951
9211
  messages: outputMessages,
8952
9212
  error
8953
9213
  });
8954
- return { ok: false, cancelled: dryRun.cancelled, error };
9214
+ return {
9215
+ ok: false,
9216
+ cancelled: dryRun.cancelled,
9217
+ error,
9218
+ persistent: !dryRun.cancelled && dryRun.persistent
9219
+ };
8955
9220
  }
8956
9221
  recordInvocation({ status: "completed", messages: outputMessages });
8957
9222
  return {
@@ -8966,7 +9231,7 @@ Remember: output only the JSON object described by the system prompt.`;
8966
9231
  const cancelled = args.signal.aborted;
8967
9232
  const message = boundedError(error instanceof Error ? error.message : String(error));
8968
9233
  recordInvocation({ status: cancelled ? "aborted" : "failed", error: message });
8969
- return { ok: false, cancelled, error: message };
9234
+ return { ok: false, cancelled, error: message, persistent: false };
8970
9235
  } finally {
8971
9236
  if (args.client) {
8972
9237
  await teardownChildSession({
@@ -8981,6 +9246,29 @@ Remember: output only the JSON object described by the system prompt.`;
8981
9246
  }
8982
9247
  }
8983
9248
  }
9249
+ async function dryRunSmartNoteCheck(compiledCheck, capabilityFactory, signal) {
9250
+ const responses = [];
9251
+ const dryRun = await runCompiledSmartNoteCheck({
9252
+ compiledCheck,
9253
+ capabilityFactory: (runSignal) => {
9254
+ const capabilities = capabilityFactory(runSignal);
9255
+ return {
9256
+ ...capabilities,
9257
+ httpGet: async (url) => {
9258
+ const response = await capabilities.httpGet(url);
9259
+ responses.push({ url, status: response.status });
9260
+ return response;
9261
+ }
9262
+ };
9263
+ },
9264
+ signal,
9265
+ timeoutMs: 2000
9266
+ });
9267
+ const advisories = dryRun.ok && responses.length > 0 && responses.every(({ status }) => status === 401 || status === 403 || status === 404) ? [
9268
+ `all HTTP sources currently return 401/403/404: ${responses.map(({ url, status }) => `${url} (HTTP ${status})`).join(", ")}`
9269
+ ] : [];
9270
+ return { ...dryRun, advisories };
9271
+ }
8984
9272
  function parseCompilerOutput(output) {
8985
9273
  if (!output)
8986
9274
  throw new Error("smart-note compiler returned no output");
@@ -9435,7 +9723,7 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld,
9435
9723
  expected: sourceRevisionExpectation(note),
9436
9724
  leaseHeld,
9437
9725
  write: () => {
9438
- markSmartNoteCompilationFailure(args.db, note.id, now, MAX_COMPILATION_FAILURES);
9726
+ markSmartNoteCompilationFailure(args.db, note.id, now, MAX_COMPILATION_FAILURES, result.error, result.persistent);
9439
9727
  }
9440
9728
  });
9441
9729
  return false;
@@ -9806,7 +10094,7 @@ var MAP_MEMORIES_SYSTEM_PROMPT = `You are a memory mapper for the magic-context
9806
10094
 
9807
10095
  A memory's BACKING FILES are the file(s) whose code the memory makes a claim about — the files you would open to check whether the memory is accurate. You do NOT judge accuracy, rewrite, or remove anything. You only LOCATE backing files.
9808
10096
 
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.
10097
+ Tools (read-only): read, grep, glob. Request line ranges rather than full files; use offset/limit for longer files. Each memory may come with "Likely files" already named in it and confirmed to exist — confirm those FIRST (cheap) instead of searching. Use search/grep to FIND code only when no likely files are given. Do not guess — confirm a file exists and genuinely backs the memory before listing it. Keep reads minimal: you do not need to read a whole file to confirm it backs a one-line claim.
9810
10098
 
9811
10099
  For each memory decide ONE of:
9812
10100
  - Backing files found → the COMPLETE set of repo-relative paths whose code the memory is about.
@@ -10063,6 +10351,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
10063
10351
  prompt,
10064
10352
  title: "magic-context-dream-map-memories",
10065
10353
  callContext: "dreamer:map-memories",
10354
+ metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
10066
10355
  model: args.model,
10067
10356
  fallbackModels: args.fallbackModels,
10068
10357
  timeoutMs: sliceMs,
@@ -10103,7 +10392,10 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
10103
10392
  parts: [{ type: "text", text: prompt, synthetic: true }]
10104
10393
  }
10105
10394
  }, {
10106
- transport: createPromptAsyncTransport(client, agentSessionId),
10395
+ transport: createPromptAsyncTransport(client, agentSessionId, {
10396
+ tokenBudget: args.tokenBudget,
10397
+ onBudgetUpdate: args.onBudgetUpdate
10398
+ }),
10107
10399
  timeoutMs: sliceMs,
10108
10400
  signal,
10109
10401
  fallbackModels: args.fallbackModels,
@@ -10143,7 +10435,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
10143
10435
  error,
10144
10436
  messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
10145
10437
  });
10146
- if (error instanceof DreamerModuleFailureError)
10438
+ if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded)
10147
10439
  throw error;
10148
10440
  if (signal.aborted)
10149
10441
  throw error;
@@ -10773,6 +11065,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
10773
11065
  prompt,
10774
11066
  title: "magic-context-dream-refresh-primers",
10775
11067
  callContext: "dreamer:refresh-primers",
11068
+ metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
10776
11069
  model: args.model,
10777
11070
  fallbackModels: args.fallbackModels,
10778
11071
  language: args.language,
@@ -10822,6 +11115,10 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
10822
11115
  signal,
10823
11116
  fallbackModels: args.fallbackModels,
10824
11117
  callContext: "dreamer:refresh-primers",
11118
+ transport: createPromptAsyncTransport(client, agentSessionId, {
11119
+ tokenBudget: args.tokenBudget,
11120
+ onBudgetUpdate: args.onBudgetUpdate
11121
+ }),
10825
11122
  fetchOutput: async () => {
10826
11123
  const messagesResponse = await client.session.messages({
10827
11124
  path: { id: agentSessionId },
@@ -11034,12 +11331,14 @@ class OpenCodeRetrospectiveRawProvider {
11034
11331
  this.openDb = deps.openOpenCodeDb ?? openOpenCodeDb;
11035
11332
  }
11036
11333
  listProjectSessions(projectIdentity) {
11037
- const rows = this.deps.contextDb.prepare(`SELECT sp.session_id, sp.updated_at
11334
+ const rows = this.deps.contextDb.prepare(`SELECT sp.session_id, CAST(activity.value AS INTEGER) AS updated_at
11038
11335
  FROM session_projects sp
11336
+ JOIN schema_migrations_meta activity
11337
+ ON activity.key = 'retrospective_activity:' || sp.session_id
11039
11338
  LEFT JOIN session_meta m ON m.session_id = sp.session_id
11040
11339
  WHERE sp.project_path = ? AND sp.harness = 'opencode'
11041
11340
  AND COALESCE(m.is_subagent, 0) = 0
11042
- ORDER BY sp.updated_at ASC, sp.session_id ASC`).all(projectIdentity);
11341
+ ORDER BY updated_at ASC, sp.session_id ASC`).all(projectIdentity);
11043
11342
  return rows.map((row) => ({
11044
11343
  sessionId: row.session_id,
11045
11344
  updatedAt: typeof row.updated_at === "number" ? row.updated_at : undefined
@@ -11058,15 +11357,13 @@ class OpenCodeRetrospectiveRawProvider {
11058
11357
  const db = this.resolveDb();
11059
11358
  if (!db)
11060
11359
  return { messages: [], truncated: false };
11061
- try {
11062
- return readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession);
11063
- } catch {
11064
- return { messages: [], truncated: false };
11065
- }
11360
+ return readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession);
11066
11361
  }
11067
11362
  readOldestMessageTimesSince(sessionIds, sinceMs) {
11068
11363
  const db = this.resolveDb();
11069
- if (!db || sessionIds.length === 0)
11364
+ if (!db)
11365
+ return null;
11366
+ if (sessionIds.length === 0)
11070
11367
  return new Map;
11071
11368
  return readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs);
11072
11369
  }
@@ -11163,6 +11460,11 @@ async function readRetrospectiveScanWindow(provider, projectIdentity, watermarkM
11163
11460
  }
11164
11461
  const droppedSince = [...budgetDropped, ...countDropped];
11165
11462
  let maxScannedTs = scanSinceMs;
11463
+ if (oldestBySession && sinceReads.every((read) => read.messages.length === 0)) {
11464
+ for (const { session } of eligibleSessions) {
11465
+ maxScannedTs = Math.max(maxScannedTs, session.updatedAt ?? scanSinceMs);
11466
+ }
11467
+ }
11166
11468
  for (const row of keptSince) {
11167
11469
  if (row.ts > maxScannedTs)
11168
11470
  maxScannedTs = row.ts;
@@ -11454,6 +11756,127 @@ function insertDreamRun(db, run) {
11454
11756
  // ../plugin/src/features/magic-context/dreamer/verify.ts
11455
11757
  import { createHash as createHash9 } from "node:crypto";
11456
11758
 
11759
+ // ../plugin/src/features/magic-context/dreamer/verify-diff.ts
11760
+ import { execFile as execFile2 } from "node:child_process";
11761
+ import { promisify as promisify2 } from "node:util";
11762
+ var exec = promisify2(execFile2);
11763
+ var VERIFY_DIFF_BUDGET_TOKENS = 12000;
11764
+ var MAX_DIFF_BYTES = 4 * 1024 * 1024;
11765
+ async function git2(root, args) {
11766
+ const { stdout } = await exec("git", args, {
11767
+ cwd: root,
11768
+ timeout: 1e4,
11769
+ maxBuffer: MAX_DIFF_BYTES,
11770
+ encoding: "utf8"
11771
+ });
11772
+ return stdout;
11773
+ }
11774
+ async function buildVerifyDiffEvidence(directory, memories) {
11775
+ const root = await resolveGitTopLevel(directory);
11776
+ const head = root && await readGitHead(root);
11777
+ if (!root || !head || memories.some((m) => !m.verifiedAt))
11778
+ return null;
11779
+ try {
11780
+ const bases = new Map;
11781
+ for (const memory of memories) {
11782
+ const at = memory.verifiedAt;
11783
+ if (!at)
11784
+ return null;
11785
+ const base = memory.verifiedCommit ?? (await git2(root, [
11786
+ "rev-list",
11787
+ "-1",
11788
+ `--before=${new Date(at).toISOString()}`,
11789
+ head
11790
+ ])).trim();
11791
+ if (!/^[0-9a-f]{40}$/i.test(base))
11792
+ return null;
11793
+ bases.set(memory.id, base);
11794
+ }
11795
+ const grouped = new Map;
11796
+ for (const memory of memories) {
11797
+ const at = memory.verifiedAt;
11798
+ const base = bases.get(memory.id);
11799
+ if (!at || !base)
11800
+ return null;
11801
+ for (const file of memory.mappedFiles) {
11802
+ if (!file || file.startsWith("/") || file.split("/").includes(".."))
11803
+ return null;
11804
+ const previous = grouped.get(file);
11805
+ if (!previous)
11806
+ grouped.set(file, {
11807
+ base,
11808
+ time: at,
11809
+ ids: [memory.id]
11810
+ });
11811
+ else {
11812
+ previous.ids.push(memory.id);
11813
+ if (at < previous.time) {
11814
+ previous.time = at;
11815
+ previous.base = base;
11816
+ }
11817
+ }
11818
+ }
11819
+ }
11820
+ const files = [];
11821
+ const namesByBase = new Map;
11822
+ for (const [file, group] of grouped) {
11823
+ let names = namesByBase.get(group.base);
11824
+ if (names === undefined) {
11825
+ names = await git2(root, ["diff", "-M", "--name-status", group.base, head]);
11826
+ namesByBase.set(group.base, names);
11827
+ }
11828
+ const rename = names.split(`
11829
+ `).map((line) => line.split("\t")).find((parts) => parts[0]?.startsWith("R") && parts[1] === file);
11830
+ const paths = rename ? [file, rename[2]] : [file];
11831
+ const committed = await git2(root, [
11832
+ "diff",
11833
+ "-M",
11834
+ "--no-ext-diff",
11835
+ group.base,
11836
+ head,
11837
+ "--",
11838
+ ...paths
11839
+ ]);
11840
+ const pending = await git2(root, ["diff", "-M", "--no-ext-diff", head, "--", ...paths]);
11841
+ const status = rename ? `RENAMED ${file} -> ${rename[2]}
11842
+ ` : names.split(`
11843
+ `).some((line) => line === `D ${file}`) ? `DELETED ${file}
11844
+ ` : "";
11845
+ files.push({
11846
+ path: file,
11847
+ memoryIds: group.ids,
11848
+ text: `${status}${committed}${pending ? `
11849
+ Uncommitted changes:
11850
+ ${pending}` : ""}` || "No changed hunks in mapped file."
11851
+ });
11852
+ }
11853
+ const size = files.reduce((sum, file) => sum + file.text.length, 0);
11854
+ if (size <= VERIFY_DIFF_BUDGET_TOKENS * 3)
11855
+ return { head, mode: "diff", files };
11856
+ return {
11857
+ head,
11858
+ mode: "ranges",
11859
+ files: files.map((file) => {
11860
+ const headers = file.text.split(`
11861
+ `).filter((line) => /^(?:@@|diff --git|rename |deleted file mode|new file mode|RENAMED|DELETED|Uncommitted changes:)/.test(line));
11862
+ const lines = file.text.split(`
11863
+ `);
11864
+ const added = lines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
11865
+ const removed = lines.filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
11866
+ return {
11867
+ ...file,
11868
+ text: `Changed lines: +${added}/-${removed}.
11869
+ ${headers.join(`
11870
+ `)}
11871
+ Read only these changed line ranges if they may affect the claim.`
11872
+ };
11873
+ })
11874
+ };
11875
+ } catch {
11876
+ return null;
11877
+ }
11878
+ }
11879
+
11457
11880
  // ../plugin/src/features/magic-context/dreamer/verify-gate.ts
11458
11881
  import path5 from "node:path";
11459
11882
  function minOf(values) {
@@ -11488,12 +11911,24 @@ async function partitionVerifyScope(args) {
11488
11911
  const active = getMemoriesByProject(args.db, args.projectIdentity);
11489
11912
  const verById = getMemoryVerifications(args.db, active.map((m) => m.id));
11490
11913
  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
- });
11914
+ const toPrompt = (m) => {
11915
+ const verifiedAt = verById.get(m.id)?.verifiedAt ?? 0;
11916
+ let verifiedCommit = null;
11917
+ try {
11918
+ const metadata = JSON.parse(m.metadataJson ?? "{}");
11919
+ if (metadata.dreamerVerifiedAt === verifiedAt && typeof metadata.dreamerVerifiedCommit === "string" && /^[0-9a-f]{40}$/i.test(metadata.dreamerVerifiedCommit)) {
11920
+ verifiedCommit = metadata.dreamerVerifiedCommit;
11921
+ }
11922
+ } catch {}
11923
+ return {
11924
+ id: m.id,
11925
+ category: m.category,
11926
+ content: m.content,
11927
+ mappedFiles: verById.get(m.id)?.files ?? [],
11928
+ verifiedAt,
11929
+ verifiedCommit
11930
+ };
11931
+ };
11497
11932
  if (args.forceBroad) {
11498
11933
  const broadCycleStartAt = ensureBroadCycleStart({
11499
11934
  db: args.db,
@@ -11573,9 +12008,9 @@ async function partitionVerifyScope(args) {
11573
12008
  // ../plugin/src/features/magic-context/dreamer/verify-prompt.ts
11574
12009
  var VERIFY_SYSTEM_PROMPT = `You are a memory verifier for the magic-context system. You verify project memories against the CURRENT code.
11575
12010
 
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.
12011
+ Each memory below comes with its backing file(s) — the code it makes a claim about. For EACH memory: use the provided git diff as evidence when present. If no diff is supplied, read its backing files (you may read more if needed) and decide whether the memory is still accurate.
11577
12012
 
11578
- Tools (read-only): read, grep, glob, aft_search, aft_outline, aft_zoom. You read code to check claims; you change nothing.
12013
+ Tools (read-only): read, grep, glob. Request line ranges and keep reads short. You read code only when the evidence requires it; you change nothing.
11579
12014
 
11580
12015
  Decide ONE of four outcomes per memory:
11581
12016
  - VERIFIED — still accurate. Keep it as-is.
@@ -11600,7 +12035,7 @@ Rules:
11600
12035
  - Every input memory id MUST appear exactly once, in exactly one of verified/update/archive/skip.
11601
12036
  - files = the COMPLETE current backing set (repo-relative, comma-separated). It may differ from the given mapping if a file moved — record what you actually verified against.
11602
12037
  - Default to VERIFIED. skip when code cannot decide the claim. update and archive are the exceptions, not the norm.`;
11603
- function buildVerifyPrompt(projectPath, memories) {
12038
+ function buildVerifyPrompt(projectPath, memories, evidence) {
11604
12039
  const list = memories.map((m) => `[${m.id}] ${m.category}
11605
12040
  Content: ${m.content}
11606
12041
  Backing files: ${m.mappedFiles.join(", ")}`).join(`
@@ -11610,11 +12045,18 @@ Backing files: ${m.mappedFiles.join(", ")}`).join(`
11610
12045
 
11611
12046
  Project: ${projectPath}
11612
12047
 
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.
12048
+ ${evidence ? "The git diff below is the evidence. If changed hunks do not touch a claim, mark it verified without opening files. Open files only when a diff suggests the claim may have changed. A rename or deletion requires an update or archive when positively supported; do not archive on uncertainty." : "Read each memory's backing files to check its claims."} Decide verified / update / archive / skip (default verified; behavioral directives only permit verified or skip), then output ONE <verify> manifest covering every id.
11614
12049
 
11615
12050
  <memories>
11616
12051
  ${list}
11617
- </memories>`;
12052
+ </memories>${evidence ? `
12053
+
12054
+ <changed-files mode="${evidence.mode}">
12055
+ ${evidence.files.map((file) => `File: ${file.path} (memories: ${file.memoryIds.join(", ")})
12056
+ ${file.text}`).join(`
12057
+
12058
+ `)}
12059
+ </changed-files>` : ""}`;
11618
12060
  }
11619
12061
  function attrOf(s, name) {
11620
12062
  const m = s.match(new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`));
@@ -11738,8 +12180,11 @@ async function runVerify(args) {
11738
12180
  return result;
11739
12181
  }
11740
12182
  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));
12183
+ const groups = !args.forceBroad && gate.mode === "incremental" ? [gate.inScope.filter((m) => m.verifiedAt), gate.inScope.filter((m) => !m.verifiedAt)] : [gate.inScope];
12184
+ for (const group of groups) {
12185
+ for (let i = 0;i < group.length; i += VERIFY_BATCH_SIZE) {
12186
+ batches.push(group.slice(i, i + VERIFY_BATCH_SIZE));
12187
+ }
11743
12188
  }
11744
12189
  const abortController = new AbortController;
11745
12190
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
@@ -11803,7 +12248,9 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11803
12248
  let promptSettled = false;
11804
12249
  const startedAt = Date.now();
11805
12250
  try {
11806
- const prompt = buildVerifyPrompt(args.projectIdentity, batch);
12251
+ const headAtPrompt = await readGitHead(args.sessionDirectory);
12252
+ const evidence = !args.forceBroad && batch.every((m) => m.verifiedAt) ? await buildVerifyDiffEvidence(args.sessionDirectory, batch) : null;
12253
+ const prompt = buildVerifyPrompt(args.projectIdentity, batch, evidence);
11807
12254
  if (args.hiddenCompletionExecutor) {
11808
12255
  const run = await runHiddenSingleShotPrompt({
11809
12256
  executor: args.hiddenCompletionExecutor,
@@ -11814,6 +12261,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11814
12261
  prompt,
11815
12262
  title: "magic-context-dream-verify",
11816
12263
  callContext: "dreamer:verify",
12264
+ metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
11817
12265
  model: args.model,
11818
12266
  fallbackModels: args.fallbackModels,
11819
12267
  language: args.language,
@@ -11830,7 +12278,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11830
12278
  status: "completed",
11831
12279
  messages: run.completion.messages ?? []
11832
12280
  });
11833
- return applyParsedVerifyManifest(args, batch, run.validated);
12281
+ return applyParsedVerifyManifest(args, batch, run.validated, evidence?.head ?? headAtPrompt);
11834
12282
  }
11835
12283
  const client = args.client;
11836
12284
  if (!client)
@@ -11858,7 +12306,10 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11858
12306
  parts: [{ type: "text", text: prompt, synthetic: true }]
11859
12307
  }
11860
12308
  }, {
11861
- transport: createPromptAsyncTransport(client, agentSessionId),
12309
+ transport: createPromptAsyncTransport(client, agentSessionId, {
12310
+ tokenBudget: args.tokenBudget,
12311
+ onBudgetUpdate: args.onBudgetUpdate
12312
+ }),
11862
12313
  timeoutMs: sliceMs,
11863
12314
  signal,
11864
12315
  fallbackModels: args.fallbackModels,
@@ -11887,7 +12338,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11887
12338
  });
11888
12339
  promptSettled = true;
11889
12340
  recordInvocation4(args, startedAt, { status: "completed", messages: run.output });
11890
- return await applyParsedVerifyManifest(args, batch, run.validated);
12341
+ return await applyParsedVerifyManifest(args, batch, run.validated, evidence?.head ?? headAtPrompt);
11891
12342
  } catch (error) {
11892
12343
  const desc = describeError(error);
11893
12344
  const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
@@ -11898,7 +12349,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11898
12349
  error,
11899
12350
  messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
11900
12351
  });
11901
- if (error instanceof DreamerModuleFailureError || signal.aborted)
12352
+ if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded || signal.aborted)
11902
12353
  throw error;
11903
12354
  if (promptFailure?.failureClass === "provider_timeout" || isPromptTimeoutError(error)) {
11904
12355
  return {
@@ -11933,7 +12384,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
11933
12384
  });
11934
12385
  }
11935
12386
  }
11936
- async function applyParsedVerifyManifest(args, batch, parsed) {
12387
+ async function applyParsedVerifyManifest(args, batch, parsed, headAtPrompt) {
11937
12388
  const batchIds = new Set(batch.map((m) => m.id));
11938
12389
  const batchById = new Map(batch.map((memory) => [memory.id, memory]));
11939
12390
  const valid = {
@@ -11960,6 +12411,7 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
11960
12411
  return { verified: 0, updated: 0, archived: 0, skipped: 0, refused: 0 };
11961
12412
  }
11962
12413
  const now = Date.now();
12414
+ const verifiedHead = headAtPrompt === undefined ? await readGitHead(args.sessionDirectory) : headAtPrompt;
11963
12415
  const writes = [];
11964
12416
  let refused = 0;
11965
12417
  const skipped = valid.skipped.length;
@@ -12071,6 +12523,8 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
12071
12523
  continue;
12072
12524
  if (w.kind === "verify") {
12073
12525
  recordMemoryVerifications(args.db, w.id, w.files, now);
12526
+ if (verifiedHead)
12527
+ recordVerifiedCommit(args.db, memory, now, verifiedHead);
12074
12528
  verified += 1;
12075
12529
  } else if (w.kind === "update") {
12076
12530
  rewriteMemoryContent(args.db, memory, w.content, w.hash);
@@ -12095,6 +12549,15 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
12095
12549
  });
12096
12550
  return { verified, updated, archived, skipped, refused };
12097
12551
  }
12552
+ function recordVerifiedCommit(db, memory, at, head) {
12553
+ let metadata = {};
12554
+ try {
12555
+ const parsed = JSON.parse(memory.metadataJson ?? "{}");
12556
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
12557
+ metadata = parsed;
12558
+ } catch {}
12559
+ db.prepare("UPDATE memories SET metadata_json = ? WHERE id = ?").run(JSON.stringify({ ...metadata, dreamerVerifiedAt: at, dreamerVerifiedCommit: head }), memory.id);
12560
+ }
12098
12561
  async function normalizeFiles(args, rawFiles) {
12099
12562
  if (rawFiles.length === 0)
12100
12563
  return [];
@@ -12152,6 +12615,16 @@ function dreamRunFailureDetail(error) {
12152
12615
  }
12153
12616
  const described = describeError(error);
12154
12617
  const message = described.brief;
12618
+ if (error instanceof DreamTokenBudgetExceeded) {
12619
+ return {
12620
+ failure_class: "token_budget",
12621
+ model_attempted: null,
12622
+ models_tried: [],
12623
+ provider_error: null,
12624
+ timeout_ms: null,
12625
+ child_session_id: error.sessionId
12626
+ };
12627
+ }
12155
12628
  if (error instanceof Error && error.name === "HiddenAgentStepLimit") {
12156
12629
  return {
12157
12630
  failure_class: "step_limit",
@@ -12196,7 +12669,10 @@ function toCuratePromptMemory(memory, verificationById) {
12196
12669
  category: memory.category,
12197
12670
  content: memory.content,
12198
12671
  mappedFiles: verification?.files ?? [],
12199
- hasNoFileSentinel: verification?.hasSentinel ?? false
12672
+ hasNoFileSentinel: verification?.hasSentinel ?? false,
12673
+ importance: memory.importance,
12674
+ retrievalCount: memory.retrievalCount,
12675
+ seenCount: memory.seenCount
12200
12676
  };
12201
12677
  }
12202
12678
  function loadActiveMemoryPromptMemories(db, projectIdentity) {
@@ -12236,7 +12712,10 @@ function inspectCurateMemoryOperations(messages) {
12236
12712
  if (!isRecord(part.state) || part.state.status !== "completed")
12237
12713
  continue;
12238
12714
  const input = isRecord(part.state.input) ? part.state.input : null;
12239
- summary.completedActions.push(typeof input?.action === "string" ? input.action : "unknown");
12715
+ const action = input?.action;
12716
+ const output = typeof part.state.output === "string" ? part.state.output : "";
12717
+ if (output === "completed" && (action === "merge" || action === "archive" || action === "update") || action === "merge" && output.startsWith("Merged memories") || action === "archive" && output.startsWith("Archived memor") || action === "update" && output.startsWith("Updated memory"))
12718
+ summary.completedActions.push(action);
12240
12719
  }
12241
12720
  }
12242
12721
  return summary;
@@ -12284,6 +12763,7 @@ function createDreamTaskExecutor(deps) {
12284
12763
  };
12285
12764
  return async (config, ctx) => {
12286
12765
  const { db, projectIdentity, holderId, leaseKey } = ctx;
12766
+ deps.client?.resetDreamTokenBudget?.(config.task);
12287
12767
  const startedAt = Date.now();
12288
12768
  const leaseAcquisition = ctx.leaseAcquisition ?? acquireLeaseWithAcquisition(db, holderId, leaseKey) ?? (() => {
12289
12769
  throw new Error("Dream lease unavailable during executor setup");
@@ -12342,8 +12822,23 @@ function createDreamTaskExecutor(deps) {
12342
12822
  if (!leaseOwnershipMatches(db, holderId, leaseAcquisition.generation, leaseKey)) {
12343
12823
  throw new Error("Dream lease lost during executor setup");
12344
12824
  }
12825
+ let budgetSpent = 0;
12826
+ let budgetFinalized = false;
12827
+ const childSpending = new Map;
12828
+ const onBudgetUpdate = (state) => {
12829
+ if (state.sessionId) {
12830
+ const previous = childSpending.get(state.sessionId) ?? 0;
12831
+ budgetSpent += Math.max(0, state.spent - previous);
12832
+ childSpending.set(state.sessionId, state.spent);
12833
+ } else
12834
+ budgetSpent = Math.max(budgetSpent, state.spent);
12835
+ budgetFinalized ||= state.finalizeFired;
12836
+ };
12345
12837
  const recordRun = (status, error, extra) => {
12346
12838
  try {
12839
+ const hostBudget = deps.readTokenBudget?.(config.task) ?? deps.client?.readTokenBudget?.(config.task);
12840
+ if (hostBudget)
12841
+ onBudgetUpdate(hostBudget);
12347
12842
  insertDreamRun(db, {
12348
12843
  projectPath: projectIdentity,
12349
12844
  startedAt,
@@ -12359,6 +12854,14 @@ function createDreamTaskExecutor(deps) {
12359
12854
  failure: extra?.failure ?? dreamRunFailureDetail(error ?? "unknown dreamer failure")
12360
12855
  } : {},
12361
12856
  ...extra?.progress ? { progress: extra.progress } : {},
12857
+ ...config.tokenBudget ? {
12858
+ tokenBudget: {
12859
+ budget: config.tokenBudget,
12860
+ spent: budgetSpent,
12861
+ finalizeFired: budgetFinalized,
12862
+ banked: extra?.banked ?? processedDreamTaskItems(backlogAtStart.pending, (extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task)).pending)
12863
+ }
12864
+ } : {},
12362
12865
  backlog: (() => {
12363
12866
  const end = extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task);
12364
12867
  const processed = processedDreamTaskItems(backlogAtStart.pending, end.pending);
@@ -12449,7 +12952,7 @@ function createDreamTaskExecutor(deps) {
12449
12952
  leaseKey,
12450
12953
  deadline,
12451
12954
  leaseAcquisition,
12452
- promotionThreshold: config.promotionThreshold ?? 3,
12955
+ promotionThreshold: config.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"],
12453
12956
  model: config.model,
12454
12957
  fallbackModels: config.fallbackModels,
12455
12958
  language: config.language ?? deps.language
@@ -12473,6 +12976,8 @@ function createDreamTaskExecutor(deps) {
12473
12976
  model: config.model,
12474
12977
  fallbackModels: config.fallbackModels,
12475
12978
  moduleRoute,
12979
+ tokenBudget: config.tokenBudget,
12980
+ onBudgetUpdate,
12476
12981
  onProgress: (processed) => reportProgress(processed)
12477
12982
  });
12478
12983
  log(`[dreamer] map-memories: committed=${result.mapped + result.independent} mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}${result.stopReason ? ` stop_reason=${result.stopReason}` : ""}`);
@@ -12513,6 +13018,8 @@ function createDreamTaskExecutor(deps) {
12513
13018
  fallbackModels: config.fallbackModels,
12514
13019
  language: config.language ?? deps.language,
12515
13020
  moduleRoute,
13021
+ tokenBudget: config.tokenBudget,
13022
+ onBudgetUpdate,
12516
13023
  onProgress: (processed, refused) => reportProgress(processed, refused)
12517
13024
  });
12518
13025
  const processed = result.verified + result.updated + result.archived + result.skipped + result.refused;
@@ -12524,7 +13031,8 @@ function createDreamTaskExecutor(deps) {
12524
13031
  recordRun("completed", null, {
12525
13032
  progress: broadProgress,
12526
13033
  memoryChanges: computeMemoryDelta(memoryBefore),
12527
- backlogAfter
13034
+ backlogAfter,
13035
+ banked: result.verified + result.updated + result.archived
12528
13036
  });
12529
13037
  return { status: "completed" };
12530
13038
  }
@@ -12533,14 +13041,16 @@ function createDreamTaskExecutor(deps) {
12533
13041
  recordRun("failed", error, {
12534
13042
  progress: verificationProgress,
12535
13043
  memoryChanges: computeMemoryDelta(memoryBefore),
12536
- backlogAfter
13044
+ backlogAfter,
13045
+ banked: result.verified + result.updated + result.archived
12537
13046
  });
12538
13047
  return { status: "failed", transient: !batchTimedOut, error };
12539
13048
  }
12540
13049
  recordRun("completed", null, {
12541
13050
  progress: verificationProgress,
12542
13051
  memoryChanges: computeMemoryDelta(memoryBefore),
12543
- backlogAfter
13052
+ backlogAfter,
13053
+ banked: result.verified + result.updated + result.archived
12544
13054
  });
12545
13055
  return { status: "completed" };
12546
13056
  }
@@ -12592,7 +13102,7 @@ function createDreamTaskExecutor(deps) {
12592
13102
  leaseKey,
12593
13103
  deadline,
12594
13104
  leaseAcquisition,
12595
- promotionThreshold: config.promotionThreshold ?? 2,
13105
+ promotionThreshold: config.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"],
12596
13106
  ensureProjectRegistered: deps.ensureProjectRegistered
12597
13107
  });
12598
13108
  recordRun("completed", null);
@@ -12615,6 +13125,8 @@ function createDreamTaskExecutor(deps) {
12615
13125
  fallbackModels: config.fallbackModels,
12616
13126
  language: config.language ?? deps.language,
12617
13127
  rawProviderFactory: deps.primerRawProviderFactory,
13128
+ tokenBudget: config.tokenBudget,
13129
+ onBudgetUpdate,
12618
13130
  onProgress: (processed) => reportProgress(processed)
12619
13131
  });
12620
13132
  recordRun("completed", null);
@@ -12645,6 +13157,7 @@ function createDreamTaskExecutor(deps) {
12645
13157
  if (config.task === "retrospective") {
12646
13158
  const memoryBefore = getMemoryCountsByStatus(db, projectIdentity);
12647
13159
  const retro = await runRetrospectiveTask(config, ctx, {
13160
+ onBudgetUpdate,
12648
13161
  deps,
12649
13162
  deadline,
12650
13163
  parent,
@@ -12652,7 +13165,10 @@ function createDreamTaskExecutor(deps) {
12652
13165
  moduleRoute
12653
13166
  });
12654
13167
  recordRun("completed", null, {
12655
- memoryChanges: computeMemoryDelta(memoryBefore)
13168
+ memoryChanges: computeMemoryDelta(memoryBefore),
13169
+ backlogAfter: retro.retrospectiveWatermarkMs == null ? undefined : getDreamTaskBacklog(db, projectIdentity, "retrospective", {
13170
+ retrospectiveWatermarkMs: retro.retrospectiveWatermarkMs
13171
+ })
12656
13172
  });
12657
13173
  return {
12658
13174
  status: "completed",
@@ -12665,6 +13181,7 @@ function createDreamTaskExecutor(deps) {
12665
13181
  };
12666
13182
  }
12667
13183
  return await runAgenticTask(config, ctx, {
13184
+ onBudgetUpdate,
12668
13185
  deps,
12669
13186
  deadline,
12670
13187
  parent,
@@ -12675,6 +13192,9 @@ function createDreamTaskExecutor(deps) {
12675
13192
  moduleRoute
12676
13193
  });
12677
13194
  } catch (error) {
13195
+ if (error instanceof DreamTokenBudgetExceeded) {
13196
+ budgetSpent = Math.max(budgetSpent, error.spent);
13197
+ }
12678
13198
  const classified = classifyFailure(error);
12679
13199
  const retrospectiveOverflow = error instanceof RetrospectivePromptOverflowError ? error : null;
12680
13200
  const transient = retrospectiveOverflow ? true : classified.transient;
@@ -12833,7 +13353,7 @@ function resolveRetrospectiveUsableInputTokens(config, deps) {
12833
13353
  }
12834
13354
  async function runRetrospectiveTask(config, ctx, helpers) {
12835
13355
  const { db, projectIdentity, holderId, leaseKey } = ctx;
12836
- const { deps, deadline, parent } = helpers;
13356
+ const { deps, deadline, parent, onBudgetUpdate } = helpers;
12837
13357
  const provider = resolveRetrospectiveProvider(deps, db, projectIdentity);
12838
13358
  if (!provider) {
12839
13359
  log("[dreamer] retrospective: no raw provider available — clean no-op");
@@ -12887,7 +13407,8 @@ async function runRetrospectiveTask(config, ctx, helpers) {
12887
13407
  ],
12888
13408
  timeoutMs: Math.max(1, deadline - Date.now()),
12889
13409
  title: "magic-context-dream-retrospective",
12890
- directory: deps.sessionDirectory
13410
+ directory: deps.sessionDirectory,
13411
+ metadata: { tokenBudget: config.tokenBudget, onBudgetUpdate }
12891
13412
  });
12892
13413
  childSessionId = hiddenHandle.id;
12893
13414
  } else {
@@ -12923,6 +13444,12 @@ async function runRetrospectiveTask(config, ctx, helpers) {
12923
13444
  signal: abortController.signal,
12924
13445
  fallbackModels: config.fallbackModels,
12925
13446
  callContext: "dreamer:retrospective",
13447
+ ...!hiddenHandle && deps.client ? {
13448
+ transport: createPromptAsyncTransport(deps.client, sessionId, {
13449
+ tokenBudget: config.tokenBudget,
13450
+ onBudgetUpdate
13451
+ })
13452
+ } : {},
12926
13453
  ...hiddenHandle && deps.hiddenCompletionExecutor ? {
12927
13454
  transport: Object.assign((request) => deps.hiddenCompletionExecutor?.attempt(hiddenHandle, request) ?? Promise.reject(new Error("Hidden transport unavailable")), { childSessionId: hiddenHandle.childSessionId })
12928
13455
  } : {},
@@ -13098,7 +13625,7 @@ async function runRetrospectiveTask(config, ctx, helpers) {
13098
13625
  }
13099
13626
  async function runAgenticTask(config, ctx, helpers) {
13100
13627
  const { db, projectIdentity, holderId, leaseKey } = ctx;
13101
- const { deps, deadline, parent } = helpers;
13628
+ const { deps, deadline, parent, onBudgetUpdate } = helpers;
13102
13629
  const task = config.task;
13103
13630
  const docsDir = deps.sessionDirectory;
13104
13631
  const invocationStartedAt = Date.now();
@@ -13152,122 +13679,178 @@ async function runAgenticTask(config, ctx, helpers) {
13152
13679
  return { status: "completed", detail: progress };
13153
13680
  }
13154
13681
  }
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}`);
13682
+ if (task === "maintain-docs" && !changes) {
13683
+ const reason = "no relevant commits or no git repository";
13158
13684
  helpers.recordRun("completed", null, { progress: reason });
13159
13685
  return { status: "completed", detail: reason };
13160
13686
  }
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
13687
+ if (task === "maintain-docs" && changes && (changes.unchanged || !changes.relevant)) {
13688
+ const reason = changes.unchanged ? "no commits since last run" : "diff does not describe a documented area";
13689
+ helpers.recordRun("completed", null, { progress: reason });
13690
+ return {
13691
+ status: "completed",
13692
+ detail: reason,
13693
+ schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) }
13694
+ };
13695
+ }
13696
+ if (task === "maintain-docs" && hasCurrentDocsProposal(docsDir)) {
13697
+ const reason = "pending proposal matches current docs";
13698
+ helpers.recordRun("completed", null, { progress: reason });
13699
+ return {
13700
+ status: "completed",
13701
+ detail: reason,
13702
+ ...changes ? { schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) } } : {}
13703
+ };
13704
+ }
13705
+ const curateChunks = curateMemories ? chunkCurateMemories(curateMemories, Math.max(4000, Math.floor(resolveRetrospectiveUsableInputTokens(config, deps) * 2))) : [];
13706
+ const batches = task === "curate" ? curateChunks : [{ memories: [], crossChunkCandidates: [] }];
13707
+ const completedActions = [];
13708
+ let curateRefused = 0;
13709
+ let run = {
13710
+ output: [],
13711
+ validated: ""
13712
+ };
13713
+ for (const batch of batches) {
13714
+ const taskPrompt = buildDreamTaskPrompt(task, {
13715
+ projectPath: projectIdentity,
13716
+ lastDreamAt: lastRunAt ? String(lastRunAt) : null,
13717
+ existingDocs,
13718
+ docsChangeSet: changes?.text,
13719
+ docsContents: task === "maintain-docs" ? {
13720
+ architecture: existingDocs?.architecture ? readFileSync5(`${docsDir}/ARCHITECTURE.md`, "utf8") : "(missing)",
13721
+ structure: existingDocs?.structure ? readFileSync5(`${docsDir}/STRUCTURE.md`, "utf8") : "(missing)"
13722
+ } : undefined,
13723
+ docsBudget: config.docsMaxTokens ?? 12000,
13724
+ docsCurrentTokens: maintainDocsSnapshot ? Math.ceil([...maintainDocsSnapshot.values()].join("").length / 3.5) : 0,
13725
+ curate: curateMemories && curateCategory ? {
13726
+ category: curateCategory,
13727
+ memories: batch.memories,
13728
+ crossChunkCandidates: batch.crossChunkCandidates
13729
+ } : undefined
13213
13730
  });
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: {
13731
+ if (deps.hiddenCompletionExecutor) {
13732
+ const hidden = await runHiddenSingleShotPrompt({
13733
+ executor: deps.hiddenCompletionExecutor,
13734
+ parentSessionId: parent ?? undefined,
13735
+ sessionDirectory: docsDir,
13223
13736
  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 }
13737
+ system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : CURATE_SYSTEM_PROMPT,
13738
+ prompt: taskPrompt,
13739
+ title: `magic-context-dream-${task}`,
13740
+ callContext: `dreamer:${task}`,
13741
+ metadata: { tokenBudget: config.tokenBudget, onBudgetUpdate },
13742
+ allowEmpty: task === "curate",
13743
+ model: config.model,
13744
+ fallbackModels: config.fallbackModels,
13745
+ language: task === "curate" ? config.language ?? deps.language : undefined,
13746
+ timeoutMs: Math.min(Math.max(0, deadline - Date.now()), config.timeoutMinutes * 60 * 1000),
13747
+ signal: abortController.signal,
13748
+ parse: (text, completion) => {
13749
+ if (task !== "curate")
13750
+ return text;
13751
+ const memoryOperations = inspectCurateMemoryOperations(completion.messages);
13752
+ if (text)
13753
+ validateCurateAssistantText(text);
13754
+ if (memoryOperations.totalCalls > 0 && memoryOperations.completedActions.length === 0)
13755
+ throw new Error("Curate returned no completed ctx_memory tool result.");
13756
+ if (!text && memoryOperations.completedActions.length === 0)
13757
+ throw new Error("Dreamer returned no assistant output.");
13758
+ return { text: text || null, memoryOperations };
13759
+ }
13760
+ });
13761
+ childSessionId = hidden.childSessionId;
13762
+ run = { output: hidden.completion.messages ?? [], validated: hidden.validated };
13763
+ promptSettled = true;
13764
+ } else {
13765
+ const createResponse = await createChildSessionWithFence({
13766
+ client: requireDreamClient(deps.client),
13767
+ db,
13768
+ parentSessionId: parent ?? undefined,
13769
+ title: `magic-context-dream-${task}`,
13770
+ directory: docsDir
13771
+ });
13772
+ const created = normalizeSDKResponse(createResponse, null, {
13773
+ preferResponseOnMissingData: true
13774
+ });
13775
+ childSessionId = typeof created?.id === "string" ? created.id : null;
13776
+ if (!childSessionId)
13777
+ throw new Error("Dreamer could not create its child session.");
13778
+ const sessionId = childSessionId;
13779
+ const remainingMs = Math.max(0, deadline - Date.now());
13780
+ run = await promptSyncWithValidatedOutputRetry(requireDreamClient(deps.client), {
13781
+ path: { id: sessionId },
13782
+ query: { directory: docsDir },
13783
+ body: {
13784
+ agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
13785
+ system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : withContentLanguageDirective(CURATE_SYSTEM_PROMPT, config.language ?? deps.language),
13786
+ ...modelBodyField(config.model),
13787
+ parts: [{ type: "text", text: taskPrompt, synthetic: true }]
13788
+ }
13789
+ }, {
13790
+ timeoutMs: Math.min(remainingMs, config.timeoutMinutes * 60 * 1000),
13791
+ signal: abortController.signal,
13792
+ fallbackModels: config.fallbackModels,
13793
+ callContext: `dreamer:${task}`,
13794
+ transport: createPromptAsyncTransport(requireDreamClient(deps.client), sessionId, { tokenBudget: config.tokenBudget, onBudgetUpdate }),
13795
+ fetchOutput: async () => {
13796
+ const messagesResponse = await requireDreamClient(deps.client).session.messages({
13797
+ path: { id: sessionId },
13798
+ query: {
13799
+ directory: docsDir,
13800
+ ...task === "curate" ? {} : { limit: 50 }
13801
+ }
13802
+ });
13803
+ return normalizeSDKResponse(messagesResponse, [], {
13804
+ preferResponseOnMissingData: true
13805
+ });
13806
+ },
13807
+ validateOutput: (messages) => {
13808
+ const text = extractLatestAssistantText(messages);
13809
+ if (task !== "curate") {
13810
+ if (!text)
13811
+ throw new Error("Dreamer returned no assistant output.");
13812
+ return text;
13813
+ }
13814
+ const memoryOperations = inspectCurateMemoryOperations(messages);
13815
+ if (text)
13816
+ validateCurateAssistantText(text);
13817
+ if (memoryOperations.completedActions.length > 0) {
13818
+ return { text, memoryOperations };
13239
13819
  }
13240
- });
13241
- return normalizeSDKResponse(messagesResponse, [], {
13242
- preferResponseOnMissingData: true
13243
- });
13244
- },
13245
- validateOutput: (messages) => {
13246
- const text = extractLatestAssistantText(messages);
13247
- if (task !== "curate") {
13248
13820
  if (!text)
13249
13821
  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) {
13822
+ if (memoryOperations.totalCalls > 0) {
13823
+ throw new Error("Curate returned no completed ctx_memory tool result.");
13824
+ }
13256
13825
  return { text, memoryOperations };
13257
13826
  }
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;
13827
+ });
13828
+ promptSettled = true;
13829
+ }
13830
+ if (task === "curate") {
13831
+ completedActions.push(...run.validated.memoryOperations.completedActions);
13832
+ curateRefused += takeCurateSafetyRefusalCount(childSessionId ?? "");
13833
+ }
13834
+ if (childSessionId && !deps.hiddenCompletionExecutor) {
13835
+ await teardownChildSession({
13836
+ client: requireDreamClient(deps.client),
13837
+ sessionId: childSessionId,
13838
+ sessionDirectory: docsDir,
13839
+ promptSettled,
13840
+ privacySensitive: true,
13841
+ context: `[dreamer] ${task}`,
13842
+ log
13843
+ });
13844
+ childSessionId = null;
13845
+ }
13267
13846
  }
13847
+ if (task === "curate")
13848
+ run.validated = {
13849
+ text: null,
13850
+ memoryOperations: { totalCalls: completedActions.length, completedActions }
13851
+ };
13268
13852
  if (leaseLost)
13269
13853
  throw new Error("Dream lease lost during task");
13270
- const curateRefused = task === "curate" ? takeCurateSafetyRefusalCount(childSessionId ?? "") : 0;
13271
13854
  if (curateRefused > 0) {
13272
13855
  helpers.reportProgress(curateRefused, curateRefused);
13273
13856
  log(`[dreamer] curate safety summary: refused=${curateRefused}`);
@@ -13288,7 +13871,11 @@ async function runAgenticTask(config, ctx, helpers) {
13288
13871
  try {
13289
13872
  if (run.validated === "[]") {
13290
13873
  helpers.recordRun("completed", null, { progress: "no corrections proposed" });
13291
- return { status: "completed", detail: "no corrections proposed" };
13874
+ return {
13875
+ status: "completed",
13876
+ detail: "no corrections proposed",
13877
+ schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) }
13878
+ };
13292
13879
  }
13293
13880
  const path = writeDocsProposal(docsDir, String(run.validated), config.docsMaxTokens ?? 12000, docsHashes, changes.head);
13294
13881
  helpers.recordRun("completed", null, { progress: `proposal: ${path}` });
@@ -13506,7 +14093,7 @@ async function runDomainGroup(deps, group, cb) {
13506
14093
  projectIdentity,
13507
14094
  lastRunAt: readLastRunAt(db, projectIdentity, due.config.task),
13508
14095
  retrospectiveWatermarkMs: readRetrospectiveWatermark(db, projectIdentity, due.config.task),
13509
- promotionThreshold: due.config.promotionThreshold ?? 3
14096
+ promotionThreshold: due.config.promotionThreshold ?? (due.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
13510
14097
  });
13511
14098
  if (!gatePass) {
13512
14099
  advanceAfterRun(db, projectIdentity, due, Date.now(), "skipped", null);
@@ -13586,7 +14173,7 @@ async function runManualDream(deps) {
13586
14173
  projectIdentity: deps.projectIdentity,
13587
14174
  lastRunAt: readLastRunAt(deps.db, deps.projectIdentity, d.config.task),
13588
14175
  retrospectiveWatermarkMs: readRetrospectiveWatermark(deps.db, deps.projectIdentity, d.config.task),
13589
- promotionThreshold: d.config.promotionThreshold ?? 3
14176
+ promotionThreshold: d.config.promotionThreshold ?? (d.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
13590
14177
  });
13591
14178
  if (pass)
13592
14179
  gated.push(d);
@@ -13642,7 +14229,7 @@ async function runDueTasksForProject(deps) {
13642
14229
  projectIdentity: deps.projectIdentity,
13643
14230
  lastRunAt: readLastRunAt(deps.db, deps.projectIdentity, d.config.task),
13644
14231
  retrospectiveWatermarkMs: readRetrospectiveWatermark(deps.db, deps.projectIdentity, d.config.task),
13645
- promotionThreshold: d.config.promotionThreshold ?? 3
14232
+ promotionThreshold: d.config.promotionThreshold ?? (d.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
13646
14233
  });
13647
14234
  if (pass) {
13648
14235
  gated.push(d);
@@ -14269,7 +14856,7 @@ import * as childProcess from "node:child_process";
14269
14856
  import {
14270
14857
  existsSync as existsSync7,
14271
14858
  mkdtempSync as mkdtempSync2,
14272
- readFileSync as readFileSync5,
14859
+ readFileSync as readFileSync6,
14273
14860
  realpathSync as realpathSync3,
14274
14861
  rmSync as rmSync2,
14275
14862
  statSync as statSync4,
@@ -14298,7 +14885,7 @@ var DEFAULT_PI_RESOLUTION_FS = {
14298
14885
  existsSync: existsSync7,
14299
14886
  statSync: statSync4,
14300
14887
  realpathSync: realpathSync3,
14301
- readFileSync: readFileSync5
14888
+ readFileSync: readFileSync6
14302
14889
  };
14303
14890
  function packageHarnessKind(packageName) {
14304
14891
  if (packageName === "@oh-my-pi/pi-coding-agent")
@@ -14595,12 +15182,6 @@ var DREAMER_ACTION_AGENTS = new Set([
14595
15182
  "dreamer",
14596
15183
  "magic-context-dreamer"
14597
15184
  ]);
14598
- var HISTORIAN_AGENTS = new Set([
14599
- "magic-context-historian",
14600
- "historian",
14601
- "historian-recomp",
14602
- "historian-editor"
14603
- ]);
14604
15185
  var SEARCH_ONLY_SUBAGENT_TOOL_AGENTS = new Set([
14605
15186
  "dreamer-retrospective",
14606
15187
  "dreamer-primer-investigator"
@@ -14790,7 +15371,15 @@ class PiSubagentRunner {
14790
15371
  async runOnce(options, runMode, modelRefOverride) {
14791
15372
  const startTime = Date.now();
14792
15373
  let recordedAccounting = false;
15374
+ const cap = subagentStepCap(options.agent);
15375
+ const budget = options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined;
15376
+ const telemetry = new SubagentTelemetry(options.systemPrompt, (line) => sessionLog(options.accountingSessionId ?? "subagent", line));
15377
+ let loggedTelemetry = false;
14793
15378
  const recordAccounting = (result, messages = []) => {
15379
+ if (!loggedTelemetry) {
15380
+ loggedTelemetry = true;
15381
+ telemetry.finish(options.agent, result.ok ? "completed" : result.reason, cap);
15382
+ }
14794
15383
  if (!options.accountingSessionId || recordedAccounting)
14795
15384
  return;
14796
15385
  recordedAccounting = true;
@@ -14846,7 +15435,9 @@ class PiSubagentRunner {
14846
15435
  return failBeforeSpawn("spawn_failed", `Magic Context: plugin build is older than its database (database=v${fence.failure.persistedVersion}, supported_fence=v${fence.failure.supportedVersion}) — restart Pi.`);
14847
15436
  }
14848
15437
  const promptBytes = Buffer.byteLength(options.userMessage, "utf8");
14849
- const deliverViaStdin = promptBytes > PROMPT_ARGV_MAX_BYTES || this.platform === "win32";
15438
+ const budgetToolGuardAvailable = SUBAGENT_ENTRY_PATH !== undefined || this.subagentExtensions?.some((extension) => basename(extension) === "subagent-entry.js") === true;
15439
+ const rpcBudget = options.tokenBudget !== undefined && this.invocation.targetHarness === "pi" && budgetToolGuardAvailable;
15440
+ const deliverViaStdin = !rpcBudget && (promptBytes > PROMPT_ARGV_MAX_BYTES || this.platform === "win32");
14850
15441
  let systemPromptTempDir;
14851
15442
  let systemPromptPath;
14852
15443
  const cleanupSystemPromptFile = () => {
@@ -14869,13 +15460,30 @@ class PiSubagentRunner {
14869
15460
  return failBeforeSpawn("spawn_failed", `failed to prepare pi system prompt file: ${error instanceof Error ? error.message : String(error)}`);
14870
15461
  }
14871
15462
  }
15463
+ if (rpcBudget && !systemPromptTempDir) {
15464
+ try {
15465
+ systemPromptTempDir = mkdtempSync2(join5(tmpdir2(), "mc-pi-prompt-"));
15466
+ } catch (error) {
15467
+ return failBeforeSpawn("spawn_failed", `failed to prepare pi budget guard: ${error instanceof Error ? error.message : String(error)}`);
15468
+ }
15469
+ }
15470
+ const budgetGuardFile = rpcBudget && systemPromptTempDir ? join5(systemPromptTempDir, "budget-guard") : undefined;
15471
+ if (budgetGuardFile) {
15472
+ try {
15473
+ writeFileSync5(budgetGuardFile, "", "utf8");
15474
+ } catch (error) {
15475
+ cleanupSystemPromptFile();
15476
+ return failBeforeSpawn("spawn_failed", `failed to write pi budget guard: ${error instanceof Error ? error.message : String(error)}`);
15477
+ }
15478
+ }
14872
15479
  const args = buildArgs(options, {
14873
15480
  targetHarness: this.invocation.targetHarness,
14874
15481
  disableDiscoveredExtensions: runMode.disableDiscoveredExtensions,
14875
15482
  subagentExtensions: this.subagentExtensions,
14876
- omitPositionalMessage: deliverViaStdin,
15483
+ omitPositionalMessage: deliverViaStdin || rpcBudget,
14877
15484
  systemPromptPath,
14878
- modelRef: modelRefOverride
15485
+ modelRef: modelRefOverride,
15486
+ rpc: rpcBudget
14879
15487
  });
14880
15488
  return new Promise((resolve) => {
14881
15489
  let accountingMessages = [];
@@ -14884,13 +15492,17 @@ class PiSubagentRunner {
14884
15492
  if (settled)
14885
15493
  return;
14886
15494
  settled = true;
15495
+ const outcome = budget ? {
15496
+ ...result,
15497
+ meta: { ...result.meta, tokenBudget: budget.snapshot() }
15498
+ } : result;
14887
15499
  cleanupSystemPromptFile();
14888
15500
  try {
14889
- recordAccounting(result, accountingMessages);
15501
+ recordAccounting(outcome, accountingMessages);
14890
15502
  } catch (err) {
14891
15503
  sessionLog(options.accountingSessionId ?? "subagent", `subagent accounting failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
14892
15504
  }
14893
- resolve(result);
15505
+ resolve(outcome);
14894
15506
  };
14895
15507
  const emitProgress = (event) => {
14896
15508
  if (!options.onProgress)
@@ -14902,10 +15514,14 @@ class PiSubagentRunner {
14902
15514
  let child;
14903
15515
  try {
14904
15516
  child = this.spawnImpl(this.invocation.command, [...this.invocation.prefixArgs, ...this.extraArgs, ...args], {
15517
+ windowsHide: true,
14905
15518
  cwd: options.cwd,
14906
15519
  env: {
14907
15520
  ...process.env,
14908
15521
  [MAGIC_CONTEXT_PI_SUBAGENT_ENV]: "1",
15522
+ MAGIC_CONTEXT_SUBAGENT_PROMPT_FILE: systemPromptPath,
15523
+ MAGIC_CONTEXT_SUBAGENT_BUDGET_GUARD_FILE: budgetGuardFile,
15524
+ MAGIC_CONTEXT_SUBAGENT_PROVENANCE_ONLY: /^(magic-context-)?historian(?:-recomp|-editor)?$/.test(options.agent) ? undefined : "1",
14909
15525
  ...options.temperature !== undefined ? {
14910
15526
  MAGIC_CONTEXT_HISTORIAN_TEMPERATURE: String(options.temperature)
14911
15527
  } : {},
@@ -14913,7 +15529,11 @@ class PiSubagentRunner {
14913
15529
  MAGIC_CONTEXT_HISTORIAN_MAX_OUTPUT_TOKENS: String(options.maxOutputTokens)
14914
15530
  } : {}
14915
15531
  },
14916
- stdio: [deliverViaStdin ? "pipe" : "ignore", "pipe", "pipe"]
15532
+ stdio: [
15533
+ deliverViaStdin || rpcBudget ? "pipe" : "ignore",
15534
+ "pipe",
15535
+ "pipe"
15536
+ ]
14917
15537
  });
14918
15538
  } catch (error) {
14919
15539
  cleanupSystemPromptFile();
@@ -14936,15 +15556,43 @@ class PiSubagentRunner {
14936
15556
  return;
14937
15557
  }
14938
15558
  emitProgress({ type: "spawned", argv: args, pid: child.pid });
14939
- if (deliverViaStdin && child.stdin) {
15559
+ if (rpcBudget && child.stdin) {
15560
+ child.stdin.on("error", (error) => {
15561
+ terminateChild(child);
15562
+ settle({
15563
+ ok: false,
15564
+ reason: budget?.snapshot().finalizeFired ? "token_budget" : "spawn_failed",
15565
+ error: `Pi RPC input failed: ${error.message}`,
15566
+ durationMs: Date.now() - startTime
15567
+ });
15568
+ });
15569
+ child.stdin.write(`${JSON.stringify({
15570
+ type: "prompt",
15571
+ message: options.userMessage,
15572
+ id: "dreamer-initial"
15573
+ })}
15574
+ `);
15575
+ } else if (deliverViaStdin && child.stdin) {
14940
15576
  child.stdin.on("error", () => {});
14941
15577
  try {
14942
15578
  child.stdin.end(options.userMessage, "utf8");
14943
15579
  } catch {}
14944
15580
  }
14945
15581
  let stderr = "";
15582
+ let stderrLine = "";
14946
15583
  child.stderr?.on("data", (chunk) => {
14947
15584
  const text = chunk.toString("utf8");
15585
+ stderrLine += text;
15586
+ const lines = stderrLine.split(`
15587
+ `);
15588
+ stderrLine = (lines.pop() ?? "").slice(-4096);
15589
+ for (const line of lines) {
15590
+ if (settled || !line.startsWith('{"type":"mc_system_prompt"'))
15591
+ continue;
15592
+ const parsed = parsePiEventLine(line);
15593
+ if (parsed.ok)
15594
+ telemetry.observe(parsed.event);
15595
+ }
14948
15596
  stderr += text;
14949
15597
  if (stderr.length > 16000) {
14950
15598
  stderr = `…[truncated]${stderr.slice(-16000)}`;
@@ -14980,7 +15628,7 @@ class PiSubagentRunner {
14980
15628
  const accumulatedMessages = [];
14981
15629
  accountingMessages = accumulatedMessages;
14982
15630
  rl.on("line", (line) => {
14983
- if (line.length === 0)
15631
+ if (settled || line.length === 0)
14984
15632
  return;
14985
15633
  const parsed = parsePiEventLine(line);
14986
15634
  if (!parsed.ok) {
@@ -14993,6 +15641,95 @@ class PiSubagentRunner {
14993
15641
  if (typeof event !== "object" || event === null)
14994
15642
  return;
14995
15643
  const e = event;
15644
+ if (rpcBudget && e.type === "response" && event.success === false) {
15645
+ const reply = event;
15646
+ if (reply.command === "prompt" || reply.command === "steer" && !sawAgentEnd) {
15647
+ terminateChild(child);
15648
+ settle({
15649
+ ok: false,
15650
+ reason: reply.command === "steer" ? "token_budget" : "model_failed",
15651
+ error: reply.error ?? `Pi RPC ${reply.command} failed`,
15652
+ durationMs: Date.now() - startTime
15653
+ });
15654
+ return;
15655
+ }
15656
+ }
15657
+ telemetry.observe(event);
15658
+ if (budget && e.type === "message_end") {
15659
+ const message = e.message;
15660
+ if (message?.role === "assistant") {
15661
+ const usage = message.usage;
15662
+ const finished = message.stopReason === "stop" && !(Array.isArray(message.content) && message.content.some((part) => typeof part === "object" && part !== null && part.type === "toolCall"));
15663
+ const decision = budget.charge(usage?.input ?? 0, usage?.cacheRead ?? 0, usage?.cacheWrite ?? 0, finished, rpcBudget);
15664
+ if (decision === "stop" || decision === "finalize" && !rpcBudget) {
15665
+ terminateChild(child);
15666
+ settle({
15667
+ ok: false,
15668
+ reason: "token_budget",
15669
+ error: "MC-D11: Pi dreamer exceeded its prompt-token budget",
15670
+ durationMs: Date.now() - startTime,
15671
+ meta: { tokenBudget: budget.snapshot() }
15672
+ });
15673
+ return;
15674
+ }
15675
+ if (decision === "finalize" && rpcBudget) {
15676
+ try {
15677
+ if (!budgetGuardFile)
15678
+ throw new Error("missing budget guard file");
15679
+ writeFileSync5(budgetGuardFile, "finalize", "utf8");
15680
+ } catch {
15681
+ terminateChild(child);
15682
+ settle({
15683
+ ok: false,
15684
+ reason: "token_budget",
15685
+ error: "MC-D11: Pi could not activate its tool guard",
15686
+ durationMs: Date.now() - startTime
15687
+ });
15688
+ return;
15689
+ }
15690
+ child.stdin?.write(`${JSON.stringify({
15691
+ type: "steer",
15692
+ message: TOKEN_BUDGET_FINALIZE_MESSAGE,
15693
+ id: "dreamer-finalize"
15694
+ })}
15695
+ `);
15696
+ }
15697
+ }
15698
+ }
15699
+ const resultParts = event.result?.content;
15700
+ if (budget?.snapshot().finalizeFired && e.type === "tool_execution_end" && resultParts?.some((part) => part.text === TOKEN_BUDGET_TOOL_REFUSAL)) {
15701
+ const refusal = budget.refuseTool();
15702
+ if (refusal?.hardStopped) {
15703
+ terminateChild(child);
15704
+ settle({
15705
+ ok: false,
15706
+ reason: "token_budget",
15707
+ error: "MC-D11: Pi dreamer made two calls after finalize",
15708
+ durationMs: Date.now() - startTime,
15709
+ meta: { tokenBudget: budget.snapshot() }
15710
+ });
15711
+ return;
15712
+ }
15713
+ }
15714
+ if (cap !== undefined && telemetry.steps > cap) {
15715
+ if (e.type === "message_end")
15716
+ accumulatedMessages.push(e.message);
15717
+ if (e.type === "agent_end" && Array.isArray(e.messages))
15718
+ accountingMessages = e.messages;
15719
+ if (timeoutHandle)
15720
+ clearTimeout(timeoutHandle);
15721
+ if (drainTimerHandle)
15722
+ clearTimeout(drainTimerHandle);
15723
+ terminateChild(child);
15724
+ settle({
15725
+ ok: false,
15726
+ reason: "step_limit",
15727
+ error: `MC-D10: Pi subagent ${options.agent} exceeded its ${cap}-step limit`,
15728
+ durationMs: Date.now() - startTime,
15729
+ meta: { cap, steps: telemetry.steps }
15730
+ });
15731
+ return;
15732
+ }
14996
15733
  const isFirstEvent = eventCount === 0;
14997
15734
  eventCount += 1;
14998
15735
  lastEventTimestamp = Date.now();
@@ -15306,9 +16043,9 @@ function buildArgs(options, opts) {
15306
16043
  const targetHarness = opts?.targetHarness ?? resolvePiHarnessKind();
15307
16044
  const ompTarget = targetHarness === "omp";
15308
16045
  const args = [
15309
- "--print",
16046
+ ...opts?.rpc ? [] : ["--print"],
15310
16047
  "--mode",
15311
- "json",
16048
+ opts?.rpc ? "rpc" : "json",
15312
16049
  "--no-session",
15313
16050
  "--no-skills",
15314
16051
  ...ompTarget ? ["--no-rules"] : ["--no-prompt-templates", "--no-context-files"]
@@ -15322,7 +16059,7 @@ function buildArgs(options, opts) {
15322
16059
  }
15323
16060
  }
15324
16061
  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));
16062
+ const shouldLoadSubagentExtension = subagentEntryPath && (opts?.rpc || SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
15326
16063
  if (shouldLoadSubagentExtension) {
15327
16064
  args.push("--extension", subagentEntryPath);
15328
16065
  if (DREAMER_ACTION_AGENTS.has(options.agent)) {
@@ -15330,7 +16067,7 @@ function buildArgs(options, opts) {
15330
16067
  }
15331
16068
  }
15332
16069
  const historianCalibrationEntryPath = opts?.historianCalibrationEntryPath === undefined ? HISTORIAN_CALIBRATION_ENTRY_PATH : opts.historianCalibrationEntryPath;
15333
- if (HISTORIAN_AGENTS.has(options.agent) && historianCalibrationEntryPath) {
16070
+ if (subagentStepCap(options.agent) !== undefined && historianCalibrationEntryPath) {
15334
16071
  args.push("--extension", historianCalibrationEntryPath);
15335
16072
  }
15336
16073
  const strictTools = STRICT_TOOL_ALLOWLIST.get(options.agent);
@@ -15559,7 +16296,7 @@ function* sessionEntries(path) {
15559
16296
  }
15560
16297
 
15561
16298
  // src/dreamer/pi-session-api.ts
15562
- import { existsSync as existsSync8, readFileSync as readFileSync6, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
16299
+ import { existsSync as existsSync8, readFileSync as readFileSync7, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
15563
16300
  import {
15564
16301
  basename as basename2,
15565
16302
  dirname as dirname2,
@@ -15588,7 +16325,7 @@ function isScriptEntry(filePath) {
15588
16325
  }
15589
16326
  function readManifest(pkgJsonPath) {
15590
16327
  try {
15591
- const pkg = JSON.parse(readFileSync6(pkgJsonPath, "utf8"));
16328
+ const pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
15592
16329
  return pkg && typeof pkg === "object" ? pkg : null;
15593
16330
  } catch {
15594
16331
  return null;
@@ -15752,7 +16489,7 @@ async function loadDefaultPiSessionApi(loaders) {
15752
16489
  throw new Error("Pi session APIs unavailable: expected SessionManager.listAll on pi-coding-agent");
15753
16490
  }
15754
16491
  const loadEntriesFromFile = mod.loadEntriesFromFile ?? ((filePath) => {
15755
- const content = readFileSync6(filePath, "utf8");
16492
+ const content = readFileSync7(filePath, "utf8");
15756
16493
  return mod.parseSessionEntries?.(content) ?? [];
15757
16494
  });
15758
16495
  return {
@@ -15900,11 +16637,14 @@ class PiRetrospectiveRawProvider {
15900
16637
  continue;
15901
16638
  if (typeof info.cwd !== "string" || resolve2(info.cwd) !== projectCwd)
15902
16639
  continue;
16640
+ const activity = this.deps.contextDb ? readSessionActivity(this.deps.contextDb, info.id) : typeof info.modified === "number" ? info.modified : undefined;
16641
+ if (this.deps.contextDb && activity === undefined)
16642
+ continue;
15903
16643
  this.sessionPathById.set(info.id, info.path);
15904
16644
  result.push({
15905
16645
  sessionId: info.id,
15906
16646
  path: info.path,
15907
- updatedAt: typeof info.modified === "number" ? info.modified : undefined
16647
+ updatedAt: activity
15908
16648
  });
15909
16649
  }
15910
16650
  return result.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0));
@@ -16051,6 +16791,12 @@ var inFlightDreams = (() => {
16051
16791
  globals[PI_DREAMER_IN_FLIGHT] = dreams;
16052
16792
  return dreams;
16053
16793
  })();
16794
+ var dreamAbortControllers = new Map;
16795
+ function abortInFlightDreamers(registrationOwner) {
16796
+ for (const controller of dreamAbortControllers.get(registrationOwner) ?? []) {
16797
+ controller.abort();
16798
+ }
16799
+ }
16054
16800
  var sessionCounter = 0;
16055
16801
  var piSubagentRunnerFactory = () => new PiSubagentRunner;
16056
16802
  var startDreamScheduleTimerFn = startDreamScheduleTimer;
@@ -16079,7 +16825,7 @@ function registerPiDreamerProject(opts) {
16079
16825
  const client = createPiDreamerClient(opts, notifyOwnersOfAdjunctRefresh, () => {
16080
16826
  const current = registeredProjects2.get(opts.projectIdentity);
16081
16827
  return current?.generation === generation && current.owners.get(opts.registrationOwner)?.projectDir === opts.projectDir;
16082
- }, () => opts.sampleDreamRun ? opts.sampleDreamRun().dreamerConfig?.maxTokens : opts.config.maxTokens);
16828
+ }, () => opts.sampleDreamRun ? opts.sampleDreamRun().dreamerConfig?.maxTokens : opts.config.maxTokens, () => opts.sampleDreamRun?.().dreamerConfig ?? opts.config);
16083
16829
  let cleanup;
16084
16830
  let cancelled = false;
16085
16831
  const modelRegistry = opts.modelRegistry;
@@ -16098,7 +16844,10 @@ function registerPiDreamerProject(opts) {
16098
16844
  retinaHandoff: opts.retinaHandoff,
16099
16845
  mural: opts.mural,
16100
16846
  ensureRegistered: ensureProjectRegisteredFromPiDirectory,
16101
- retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({ projectCwd: opts.projectDir }),
16847
+ retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({
16848
+ projectCwd: opts.projectDir,
16849
+ contextDb: opts.db
16850
+ }),
16102
16851
  primerRawProviderFactory: createPiPrimerRawProviderFactory()
16103
16852
  }).then((timerCleanup) => {
16104
16853
  if (cancelled) {
@@ -16117,7 +16866,7 @@ function registerPiDreamerProject(opts) {
16117
16866
  const sampled = manualOpts.sampleDreamRun?.();
16118
16867
  const dreamerConfig = sampled?.dreamerConfig ?? manualOpts.config;
16119
16868
  const mural = sampled?.mural ?? manualOpts.mural;
16120
- const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir, () => dreamerConfig.maxTokens);
16869
+ const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir, () => dreamerConfig.maxTokens, () => dreamerConfig);
16121
16870
  const manualRun = runManualDream({
16122
16871
  db: manualOpts.db,
16123
16872
  projectIdentity: manualOpts.projectIdentity,
@@ -16127,7 +16876,8 @@ function registerPiDreamerProject(opts) {
16127
16876
  sessionDirectory: manualOpts.projectDir,
16128
16877
  openOpenCodeDb,
16129
16878
  retrospectiveRawProvider: new PiRetrospectiveRawProvider({
16130
- projectCwd: manualOpts.projectDir
16879
+ projectCwd: manualOpts.projectDir,
16880
+ contextDb: manualOpts.db
16131
16881
  }),
16132
16882
  primerRawProviderFactory: createPiPrimerRawProviderFactory(),
16133
16883
  userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
@@ -16197,8 +16947,9 @@ async function awaitInFlightDreamers(registrationOwner) {
16197
16947
  return;
16198
16948
  await Promise.allSettled(runs);
16199
16949
  }
16200
- function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true, getMaxOutputTokens = () => opts.config.maxTokens) {
16950
+ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true, getMaxOutputTokens = () => opts.config.maxTokens, getDreamerConfig = () => opts.config) {
16201
16951
  const runner = piSubagentRunnerFactory();
16952
+ const budgetStates = new Map;
16202
16953
  const assertRegistrationOwnerActive = () => {
16203
16954
  if (!isRegistrationOwnerActive()) {
16204
16955
  throw new Error(`Pi dreamer registration is no longer active for project ${opts.projectIdentity}`);
@@ -16228,6 +16979,13 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16228
16979
  const systemPrompt = extractSystemPrompt(args);
16229
16980
  const perTaskModel = extractBodyModel(args);
16230
16981
  const requestedAgent = extractBodyAgent(args) ?? "magic-context-dreamer";
16982
+ const controller = new AbortController;
16983
+ let controllers = dreamAbortControllers.get(opts.registrationOwner);
16984
+ if (!controllers) {
16985
+ controllers = new Set;
16986
+ dreamAbortControllers.set(opts.registrationOwner, controllers);
16987
+ }
16988
+ controllers.add(controller);
16231
16989
  const runPromise = runner.run({
16232
16990
  agent: requestedAgent,
16233
16991
  systemPrompt,
@@ -16236,18 +16994,39 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16236
16994
  fallbackModels: undefined,
16237
16995
  timeoutMs: 30 * 60 * 1000,
16238
16996
  cwd: dreamSession.directory,
16239
- signal: args.signal ?? undefined,
16997
+ signal: args.signal ? AbortSignal.any([args.signal, controller.signal]) : controller.signal,
16240
16998
  thinkingLevel: extractBodyVariant(args),
16241
16999
  maxOutputTokens: getMaxOutputTokens(),
16242
17000
  accountingSessionId: opts.projectIdentity,
16243
17001
  accountingSubagent: "dreamer",
16244
- accountingTask: accountingTaskFromTitle(dreamSession.title)
17002
+ accountingTask: accountingTaskFromTitle(dreamSession.title),
17003
+ tokenBudget: (() => {
17004
+ const task = accountingTaskFromTitle(dreamSession.title);
17005
+ if (!task)
17006
+ return;
17007
+ return buildDreamTaskRuntimeConfigs(getDreamerConfig(), opts.harness).find((entry) => entry.task === task)?.tokenBudget;
17008
+ })()
16245
17009
  });
16246
17010
  inFlightDreams.set(runPromise, opts.registrationOwner);
16247
17011
  try {
16248
17012
  const result = await runPromise;
17013
+ const budget = result.meta?.tokenBudget;
17014
+ const budgetTask = accountingTaskFromTitle(dreamSession.title);
17015
+ if (budgetTask && budget && typeof budget.spent === "number") {
17016
+ const prior = budgetStates.get(budgetTask);
17017
+ budgetStates.set(budgetTask, {
17018
+ spent: (prior?.spent ?? 0) + budget.spent,
17019
+ finalizeFired: (prior?.finalizeFired ?? false) || budget.finalizeFired === true
17020
+ });
17021
+ }
16249
17022
  assertRegistrationOwnerActive();
16250
17023
  if (!result.ok) {
17024
+ if (result.reason === "token_budget") {
17025
+ throw new DreamTokenBudgetExceeded(dreamSession.id, Number(result.meta?.tokenBudget?.spent ?? 0));
17026
+ }
17027
+ if (result.reason === "step_limit") {
17028
+ throw new HiddenAgentStepLimit("pi-dreamer", Number(result.meta?.cap));
17029
+ }
16251
17030
  const error = new Error(`Pi dreamer subagent failed (${result.reason}): ${result.error}`);
16252
17031
  if (result.transient) {
16253
17032
  error.transient = true;
@@ -16264,6 +17043,9 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16264
17043
  onAdjunctsRefreshNeeded?.(opts.projectIdentity);
16265
17044
  } finally {
16266
17045
  inFlightDreams.delete(runPromise);
17046
+ controllers.delete(controller);
17047
+ if (controllers.size === 0)
17048
+ dreamAbortControllers.delete(opts.registrationOwner);
16267
17049
  }
16268
17050
  },
16269
17051
  messages: async (args) => {
@@ -16276,7 +17058,11 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16276
17058
  return {};
16277
17059
  }
16278
17060
  };
16279
- return { session };
17061
+ return {
17062
+ session,
17063
+ readTokenBudget: (task) => budgetStates.get(task),
17064
+ resetDreamTokenBudget: (task) => budgetStates.delete(task)
17065
+ };
16280
17066
  }
16281
17067
  function readDirectory(args) {
16282
17068
  const query = args.query;
@@ -16585,6 +17371,7 @@ Unknown task "${requested}".`,
16585
17371
  try {
16586
17372
  await deps.ensureRegistered?.(ctx);
16587
17373
  const result = await runPiDreamForProject(project.projectIdentity, task, deps.registrationOwner);
17374
+ const backlogAfter = getDreamTaskBacklogs(deps.db, project.projectIdentity, backlogTasks);
16588
17375
  const lines = [];
16589
17376
  if (result.ran.length > 0)
16590
17377
  lines.push(`Ran: ${result.ran.join(", ")}`);
@@ -16600,8 +17387,8 @@ Unknown task "${requested}".`,
16600
17387
  lines.push(`Skipped (no work): ${result.skippedNoWork.join(", ")}`);
16601
17388
  if (result.deferredBusy.length > 0)
16602
17389
  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));
17390
+ if (Object.keys(backlogAfter).length > 0) {
17391
+ lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(backlogAfter, backlogTasks));
16605
17392
  }
16606
17393
  if (lines.length === 0)
16607
17394
  lines.push("No enabled dream tasks to run.");
@@ -16630,26 +17417,6 @@ Unknown task "${requested}".`,
16630
17417
  });
16631
17418
  }
16632
17419
 
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
17420
  // ../plugin/src/hooks/magic-context/format-embed-failure.ts
16654
17421
  function formatEmbedFailureSummary(embedded, remaining, failure, style = "markdown") {
16655
17422
  const historyBlocks = `history block${embedded === 1 ? "" : "s"}`;
@@ -16713,7 +17480,7 @@ function clearPiEmbedSessionState(sessionId) {
16713
17480
  ctrl.abort();
16714
17481
  embedRunStateBySession.delete(sessionId);
16715
17482
  }
16716
- autoEmbedAttemptedBySession.delete(sessionId);
17483
+ invalidateAutoEmbedSession(sessionId);
16717
17484
  }
16718
17485
  async function runEmbedDrain(db, projectIdentity, sessionId, options = {}) {
16719
17486
  const activeCtrl = embedRunStateBySession.get(sessionId);
@@ -16895,28 +17662,34 @@ ${statusText}`,
16895
17662
  function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity) {
16896
17663
  if (!isUsableProjectIdentity(projectIdentity))
16897
17664
  return;
17665
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
17666
+ const embedIdentity = snapshot ? JSON.stringify([
17667
+ snapshot.providerIdentity,
17668
+ snapshot.chunkModelId,
17669
+ snapshot.runtimeFingerprint
17670
+ ]) : "off";
17671
+ if (autoEmbedIdentityBySession.get(sessionId) !== embedIdentity)
17672
+ invalidateAutoEmbedSession(sessionId);
16898
17673
  if (autoEmbedAttemptedBySession.has(sessionId))
16899
17674
  return;
16900
17675
  if (embedPauseBySession.has(sessionId))
16901
17676
  return;
16902
17677
  autoEmbedAttemptedBySession.add(sessionId);
17678
+ autoEmbedIdentityBySession.set(sessionId, embedIdentity);
16903
17679
  (async () => {
16904
- let drainReachedTerminal = false;
16905
17680
  try {
16906
17681
  await new Promise((resolve) => setTimeout(resolve, 0));
16907
17682
  await ensureProjectRegisteredFromPiDirectory(projectDir, deps.db);
16908
- const coverage = getEmbeddingCoverageStatus(deps.db, projectIdentity, sessionId);
16909
- if (!coverage.enabled)
17683
+ const coverage = await getAutoEmbeddingSessionCoverage(deps.db, projectIdentity, sessionId);
17684
+ if (!coverage.enabled) {
17685
+ invalidateAutoEmbedSession(sessionId);
16910
17686
  return;
16911
- const remaining = coverage.session.total - coverage.session.embedded;
17687
+ }
17688
+ const remaining = coverage.total - coverage.embedded;
16912
17689
  if (remaining <= 0)
16913
17690
  return;
16914
17691
  await runEmbedDrain(deps.db, projectIdentity, sessionId);
16915
- drainReachedTerminal = true;
16916
- } catch {} finally {
16917
- if (!drainReachedTerminal)
16918
- autoEmbedAttemptedBySession.delete(sessionId);
16919
- }
17692
+ } catch {}
16920
17693
  })();
16921
17694
  }
16922
17695
 
@@ -25659,7 +26432,7 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), m
25659
26432
  };
25660
26433
  return { docs, markers, compartments, memories, userProfile, workspace };
25661
26434
  });
25662
- return read();
26435
+ return read.deferred();
25663
26436
  }
25664
26437
  function renderFreshM0PiNonPersisted(state, db) {
25665
26438
  const docs = readProjectDocsForPiM0(state);
@@ -27136,19 +27909,22 @@ function validateAnthropicReasoningRuns(messages) {
27136
27909
  index += 1;
27137
27910
  continue;
27138
27911
  }
27139
- let thinkingBlocks = 0;
27912
+ let firstMessageInRun = null;
27140
27913
  let sawOtherContent = false;
27141
27914
  while (index < messages.length && messageRole(messages[index]) === "assistant") {
27142
27915
  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;
27916
+ if (partEndsAnthropicAssistantRun(part)) {
27917
+ firstMessageInRun = null;
27149
27918
  sawOtherContent = false;
27150
27919
  } else if (!partIsOpenCodeStepMetadata(part)) {
27151
- sawOtherContent = true;
27920
+ if (firstMessageInRun === null)
27921
+ firstMessageInRun = index;
27922
+ if (partIsAnthropicThinking(part)) {
27923
+ if (sawOtherContent || index !== firstMessageInRun)
27924
+ return false;
27925
+ } else {
27926
+ sawOtherContent = true;
27927
+ }
27152
27928
  }
27153
27929
  }
27154
27930
  index += 1;
@@ -27256,6 +28032,8 @@ function replayLkg(args) {
27256
28032
  dropSlot(args.sessionId, "lkg_seam_invalid");
27257
28033
  return { ok: false, reason: "lkg_seam_invalid" };
27258
28034
  }
28035
+ const replayed = [...prefix, ...entry.pristineTail];
28036
+ args.prepareReplay?.(replayed);
27259
28037
  if (!args.skipSeamValidation) {
27260
28038
  if (!validateLkgSeamBoundary(prefix, entry.pristineTail)) {
27261
28039
  dropSlot(args.sessionId, "lkg_unsafe_seam");
@@ -27266,7 +28044,6 @@ function replayLkg(args) {
27266
28044
  return { ok: false, reason: "lkg_seam_invalid" };
27267
28045
  }
27268
28046
  }
27269
- const replayed = [...prefix, ...entry.pristineTail];
27270
28047
  if (requestedModelKeys.providerKey === "anthropic" && !validateAnthropicReasoningRuns(replayed)) {
27271
28048
  dropSlot(args.sessionId, "lkg_anthropic_reasoning_run_invalid");
27272
28049
  return { ok: false, reason: "lkg_anthropic_reasoning_run_invalid" };
@@ -28731,13 +29508,10 @@ function capturePiServedArray(sessionId, messages, options = {}) {
28731
29508
  process.once("beforeExit", flushPiServedArrayLedger);
28732
29509
 
28733
29510
  // src/strip-placeholders-pi.ts
28734
- var DROPPED_SEGMENT_PATTERN = /^\[dropped(?: §[^§]+§)?\]$/;
28735
- function isDroppedOnlyText(text) {
29511
+ var MARKER_ONLY_PATTERN = /^(?:(?:§\d+§|\[dropped(?: §\d+§)?\]|\[cleared\])\s*)+$/;
29512
+ function isPiMarkerOnlyText(text) {
28736
29513
  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));
29514
+ return trimmed.length > 0 && MARKER_ONLY_PATTERN.test(trimmed);
28741
29515
  }
28742
29516
  function messageIsPlaceholderOnly(message) {
28743
29517
  if (!message || typeof message !== "object")
@@ -28746,7 +29520,7 @@ function messageIsPlaceholderOnly(message) {
28746
29520
  if (msg.role !== "assistant")
28747
29521
  return false;
28748
29522
  if (typeof msg.content === "string")
28749
- return isDroppedOnlyText(msg.content);
29523
+ return msg.content.trim().length === 0 || isPiMarkerOnlyText(msg.content);
28750
29524
  if (!Array.isArray(msg.content))
28751
29525
  return false;
28752
29526
  if (msg.content.length === 0)
@@ -28756,12 +29530,11 @@ function messageIsPlaceholderOnly(message) {
28756
29530
  if (!part || typeof part !== "object")
28757
29531
  return false;
28758
29532
  const p = part;
28759
- if (p.type !== "text")
28760
- return false;
28761
- if (typeof p.text !== "string")
29533
+ const text = p.type === "text" ? p.text : p.type === "thinking" ? p.thinking : null;
29534
+ if (typeof text !== "string")
28762
29535
  return false;
28763
29536
  sawVisibleContent = true;
28764
- if (!isDroppedOnlyText(p.text))
29537
+ if (text.trim().length > 0 && !isPiMarkerOnlyText(text))
28765
29538
  return false;
28766
29539
  }
28767
29540
  return sawVisibleContent;
@@ -29651,6 +30424,7 @@ var deferredHistoryRefreshSessions = new Set;
29651
30424
  var deferredMaterializationSessions = new Set;
29652
30425
  var sessionsByProject = new Map;
29653
30426
  var lastSeenProjectIdentityBySession = new Map;
30427
+ var persistedProjectIdentityBySession = new Map;
29654
30428
  var rawMessageProviderUnregistersBySession = new Map;
29655
30429
  var activeContextHandlerSessions = new Set;
29656
30430
  var lastHeuristicsTurnIdBySession = new Map;
@@ -29872,9 +30646,10 @@ function updateSessionProjectTracking(sessionId, projectIdentity, db) {
29872
30646
  sessionsByProject.delete(prev);
29873
30647
  clearPiSystemPromptSession(sessionId);
29874
30648
  }
29875
- if (db && prev !== projectIdentity) {
30649
+ if (db && persistedProjectIdentityBySession.get(sessionId) !== projectIdentity) {
29876
30650
  try {
29877
30651
  recordSessionProjectIdentity(db, sessionId, projectIdentity);
30652
+ persistedProjectIdentityBySession.set(sessionId, projectIdentity);
29878
30653
  } catch {}
29879
30654
  }
29880
30655
  trackSessionForProject(projectIdentity, sessionId);
@@ -31233,6 +32008,10 @@ function registerPiContextHandler(pi, baseOptions) {
31233
32008
  log("[magic-context][pi] registered context handler (tagging + drops + nudges)");
31234
32009
  }
31235
32010
  var inFlightHistorian = new Map;
32011
+ var historianAbortControllers = new Map;
32012
+ function abortInFlightHistorians(sessionId) {
32013
+ historianAbortControllers.get(sessionId)?.abort();
32014
+ }
31236
32015
  async function awaitInFlightHistorians(sessionId) {
31237
32016
  const runs = sessionId ? [inFlightHistorian.get(sessionId)].filter((run) => run !== undefined) : [...inFlightHistorian.values()];
31238
32017
  if (runs.length === 0)
@@ -31345,7 +32124,11 @@ function spawnPiHistorianRun(args) {
31345
32124
  fallbackModelId
31346
32125
  } = args;
31347
32126
  const holderId = crypto3.randomUUID();
32127
+ const controller = new AbortController;
32128
+ historianAbortControllers.set(sessionId, controller);
31348
32129
  const runPromise = (async () => {
32130
+ if (controller.signal.aborted)
32131
+ return;
31349
32132
  const lease = acquireCompartmentLease(db, sessionId, holderId);
31350
32133
  if (!lease) {
31351
32134
  sessionLog(sessionId, "historian skipped: compartment lease held by another process");
@@ -31366,6 +32149,7 @@ function spawnPiHistorianRun(args) {
31366
32149
  appendCompaction: resolvePiAppendCompaction(ctx),
31367
32150
  readBranchEntries: resolvePiReadBranchEntries(ctx),
31368
32151
  runner: historian.runner,
32152
+ signal: controller.signal,
31369
32153
  historianModel: historian.model,
31370
32154
  fallbackModels: historian.fallbackModels,
31371
32155
  fallbackModelId,
@@ -31434,6 +32218,8 @@ function spawnPiHistorianRun(args) {
31434
32218
  }).finally(() => {
31435
32219
  try {
31436
32220
  inFlightHistorian.delete(sessionId);
32221
+ if (historianAbortControllers.get(sessionId) === controller)
32222
+ historianAbortControllers.delete(sessionId);
31437
32223
  unregister();
31438
32224
  if (isContextHandlerSessionActive(sessionId)) {
31439
32225
  historian.onStatusChange?.(ctx, sessionId);
@@ -31636,6 +32422,8 @@ Historian previously failed ${failureCount} time(s), so Magic Context is retryin
31636
32422
  }
31637
32423
  return;
31638
32424
  }
32425
+ if (!ctx.hasUI)
32426
+ return;
31639
32427
  triggered = true;
31640
32428
  sessionLog(sessionId, `historian trigger fired (reason=${trigger.reason ?? "unknown"}) usage=${usage.percentage.toFixed(1)}% — spawning subagent`);
31641
32429
  spawnPiHistorianRun({
@@ -32831,6 +33619,7 @@ function clearContextHandlerSession(sessionId) {
32831
33619
  lastHeuristicsTurnIdBySession.delete(sessionId);
32832
33620
  routinePressureAppliedBySession.delete(sessionId);
32833
33621
  lastSeenProjectIdentityBySession.delete(sessionId);
33622
+ persistedProjectIdentityBySession.delete(sessionId);
32834
33623
  for (const [projectIdentity, sessions] of sessionsByProject) {
32835
33624
  sessions.delete(sessionId);
32836
33625
  if (sessions.size === 0)
@@ -32985,6 +33774,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
32985
33774
  finished = true;
32986
33775
  return null;
32987
33776
  }
33777
+ deleteChunkEmbedBackoffForSession(db, sessionId);
32988
33778
  db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
32989
33779
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
32990
33780
  insertRecompCompartmentRows(db, sessionId, staging.compartments, now);
@@ -32999,6 +33789,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
32999
33789
  clearCachedM0M1(db, sessionId);
33000
33790
  db.exec("COMMIT");
33001
33791
  finished = true;
33792
+ invalidateAutoEmbedSession(sessionId);
33002
33793
  logSlowWriteTransaction("historian-publish:recomp", transactionStartedAt);
33003
33794
  return { compartments: staging.compartments, facts: staging.facts };
33004
33795
  } finally {
@@ -35025,7 +35816,7 @@ function buildStatusView(source, options) {
35025
35816
  // package.json
35026
35817
  var package_default = {
35027
35818
  name: "@cortexkit/pi-magic-context",
35028
- version: "0.44.1",
35819
+ version: "0.44.2",
35029
35820
  type: "module",
35030
35821
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
35031
35822
  main: "dist/index.js",
@@ -35143,6 +35934,11 @@ async function showStatusDialog(pi, ctx, deps) {
35143
35934
  overlayOptions: { anchor: "center", width: 78 }
35144
35935
  });
35145
35936
  }
35937
+ var openStatusDialogs = new Set;
35938
+ function stopStatusDialogRefresh() {
35939
+ for (const dialog of openStatusDialogs)
35940
+ dialog.close();
35941
+ }
35146
35942
 
35147
35943
  class StatusDialogComponent {
35148
35944
  props;
@@ -35152,6 +35948,7 @@ class StatusDialogComponent {
35152
35948
  constructor(props) {
35153
35949
  this.props = props;
35154
35950
  this.detail = buildPiStatusDetail(props.pi, props.ctx, props.deps, props.sessionId);
35951
+ openStatusDialogs.add(this);
35155
35952
  this.refreshTimer = setInterval(() => {
35156
35953
  if (this.closed)
35157
35954
  return;
@@ -35169,11 +35966,7 @@ class StatusDialogComponent {
35169
35966
  close() {
35170
35967
  if (this.closed)
35171
35968
  return;
35172
- this.closed = true;
35173
- if (this.refreshTimer) {
35174
- clearInterval(this.refreshTimer);
35175
- this.refreshTimer = null;
35176
- }
35969
+ this.dispose();
35177
35970
  this.props.done(undefined);
35178
35971
  }
35179
35972
  invalidate() {}
@@ -35183,6 +35976,8 @@ class StatusDialogComponent {
35183
35976
  return drawBorder(inner, width, this.props.theme);
35184
35977
  }
35185
35978
  dispose() {
35979
+ this.closed = true;
35980
+ openStatusDialogs.delete(this);
35186
35981
  if (this.refreshTimer) {
35187
35982
  clearInterval(this.refreshTimer);
35188
35983
  this.refreshTimer = null;
@@ -35726,7 +36521,10 @@ No active Pi session is available.`,
35726
36521
  });
35727
36522
  return;
35728
36523
  }
35729
- const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
36524
+ const currentDeps = {
36525
+ ...deps.resolveRuntimeDeps?.(ctx) ?? deps,
36526
+ shutdownSignal: deps.shutdownSignal
36527
+ };
35730
36528
  if (currentDeps.compactionOff) {
35731
36529
  sendStatus({
35732
36530
  title: "/ctx-wrapup",
@@ -35859,6 +36657,8 @@ No runnable wrapup boundary is available yet; wrapped up 0 messages into 0 compa
35859
36657
  console.warn(`[magic-context][pi] /ctx-wrapup marker renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
35860
36658
  }
35861
36659
  }, 60000);
36660
+ const stopRenewal = () => clearInterval(renewal);
36661
+ deps.shutdownSignal?.addEventListener("abort", stopRenewal, { once: true });
35862
36662
  try {
35863
36663
  sendStatus({
35864
36664
  title: "/ctx-wrapup",
@@ -35925,7 +36725,7 @@ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleS
35925
36725
  Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1} (~${plan.snapshot.trueRawEligibleTokens.toLocaleString()} eligible tokens remain).`,
35926
36726
  level: "info"
35927
36727
  });
35928
- const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps));
36728
+ const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps), deps.shutdownSignal);
35929
36729
  if (!leaseResult.ok) {
35930
36730
  failure = ownershipLost ? `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : leaseResult.reason === "timeout" ? `Timed out waiting for another process to release the compartment-state lease; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : "Another Magic Context rebuild started while wrapup was waiting. Run /ctx-wrapup again to continue.";
35931
36731
  break;
@@ -35938,6 +36738,10 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
35938
36738
  console.warn(`[magic-context][pi] /ctx-wrapup compartment lease renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
35939
36739
  }
35940
36740
  }, COMPARTMENT_LEASE_RENEWAL_MS);
36741
+ const stopLeaseRenewal = () => clearInterval(leaseRenewal);
36742
+ deps.shutdownSignal?.addEventListener("abort", stopLeaseRenewal, {
36743
+ once: true
36744
+ });
35941
36745
  try {
35942
36746
  const runHistorian = deps.runPiHistorianForWrapup ?? runPiHistorian;
35943
36747
  await runHistorian({
@@ -35946,6 +36750,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
35946
36750
  directory: ctx.cwd,
35947
36751
  provider,
35948
36752
  runner: deps.runner,
36753
+ signal: deps.shutdownSignal,
35949
36754
  historianModel: deps.historianModel,
35950
36755
  fallbackModels: deps.historianFallbacks,
35951
36756
  fallbackModelId: modelKey,
@@ -35989,6 +36794,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
35989
36794
  });
35990
36795
  } finally {
35991
36796
  clearInterval(leaseRenewal);
36797
+ deps.shutdownSignal?.removeEventListener("abort", stopLeaseRenewal);
35992
36798
  releaseCompartmentLeaseBestEffort(deps.db, sessionId, leaseHolder, sessionLog);
35993
36799
  }
35994
36800
  const afterEnd = getLastCompartmentEndMessage(deps.db, sessionId);
@@ -36018,6 +36824,7 @@ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments;
36018
36824
  Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments. The compacted history is queued and materializes on your next message.`;
36019
36825
  } finally {
36020
36826
  clearInterval(renewal);
36827
+ deps.shutdownSignal?.removeEventListener("abort", stopRenewal);
36021
36828
  releaseWrapupInProgress(deps.db, sessionId, holderId);
36022
36829
  }
36023
36830
  } finally {
@@ -36039,10 +36846,12 @@ function resolvePiContextLimit(ctx, db, sessionId) {
36039
36846
  detectedContextLimit
36040
36847
  }) ?? 128000;
36041
36848
  }
36042
- async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs) {
36849
+ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs, signal) {
36043
36850
  const waitStartedAt = Date.now();
36044
36851
  const remainingMs = () => Math.max(0, waitStartedAt + maxWaitMs - Date.now());
36045
36852
  for (;; ) {
36853
+ if (signal?.aborted)
36854
+ return { ok: false, reason: "ownership_lost" };
36046
36855
  if (remainingMs() <= 0)
36047
36856
  return { ok: false, reason: "timeout" };
36048
36857
  const holderId = crypto4.randomUUID();
@@ -36051,7 +36860,17 @@ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarke
36051
36860
  return { ok: true, holderId };
36052
36861
  if (!renewWrapupMarker({}))
36053
36862
  return { ok: false, reason: "ownership_lost" };
36054
- await new Promise((resolve) => setTimeout(resolve, Math.min(LEASE_WAIT_MS, remainingMs())));
36863
+ await new Promise((resolve) => {
36864
+ const finish = () => {
36865
+ clearTimeout(timer);
36866
+ signal?.removeEventListener("abort", finish);
36867
+ resolve();
36868
+ };
36869
+ const timer = setTimeout(finish, Math.min(LEASE_WAIT_MS, remainingMs()));
36870
+ signal?.addEventListener("abort", finish, { once: true });
36871
+ if (signal?.aborted)
36872
+ finish();
36873
+ });
36055
36874
  }
36056
36875
  }
36057
36876
  function estimateChunks(tokens, chunkTokens) {
@@ -36075,6 +36894,32 @@ function readBranchEntries(ctx) {
36075
36894
  return Array.isArray(branch?.entries) ? branch.entries : [];
36076
36895
  }
36077
36896
 
36897
+ // src/dreamer/session-activity-pi.ts
36898
+ function observePiMessageActivity(db, sessionId, timestamp) {
36899
+ observeSessionActivity(db, sessionId, timestamp);
36900
+ }
36901
+ function latestPiMessageTime(path) {
36902
+ let latest = 0;
36903
+ for (const entry of sessionEntries(path)) {
36904
+ const row = entry;
36905
+ if (row.type === "message" && typeof row.message?.timestamp === "number")
36906
+ latest = Math.max(latest, row.message.timestamp);
36907
+ }
36908
+ return latest || undefined;
36909
+ }
36910
+ async function backfillPiSessionActivity(db, harness, paths) {
36911
+ await backfillSessionActivity(db, harness, (sessionId) => {
36912
+ const path = paths.get(sessionId);
36913
+ if (!path)
36914
+ return;
36915
+ try {
36916
+ return latestPiMessageTime(path);
36917
+ } catch {
36918
+ return;
36919
+ }
36920
+ });
36921
+ }
36922
+
36078
36923
  // src/dropped-input-guard-pi.ts
36079
36924
  function createPiDroppedInputGuard(options = {}) {
36080
36925
  const guard = createDroppedInputGuard({
@@ -36273,6 +37118,8 @@ function stripTagPrefixFromAssistantMessage(message) {
36273
37118
  var PI_HARNESS_DETECTION = await resolvePiHarnessDetection();
36274
37119
  var PI_HARNESS_KIND = PI_HARNESS_DETECTION.kind;
36275
37120
  var PREFIX2 = `[magic-context][${PI_HARNESS_KIND}]`;
37121
+ var databaseExitHookRegistered = false;
37122
+ var activePiRuntimes = new Set;
36276
37123
  var PI_BOOT_DEADLINE_MS = 15000;
36277
37124
  var PI_CHILD_INIT_CONTEXT = Symbol.for("magic-context.pi.child-init-context");
36278
37125
  var SUBAGENT_CHILD_SESSION_CREATED = "subagents:child:session-created";
@@ -36720,6 +37567,10 @@ async function src_default(pi) {
36720
37567
  cacheSizeMb: bootConfig.config.sqlite.cache_size_mb,
36721
37568
  mmapSizeMb: bootConfig.config.sqlite.mmap_size_mb
36722
37569
  });
37570
+ if (!databaseExitHookRegistered) {
37571
+ process.once("exit", closeDatabase);
37572
+ databaseExitHookRegistered = true;
37573
+ }
36723
37574
  const storageDir = getMagicContextStorageDir();
36724
37575
  const dbPath = join11(storageDir, "context.db");
36725
37576
  let openFailureCause = null;
@@ -36793,6 +37644,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36793
37644
  const offset = afterSessionId === null ? 0 : sessions.findIndex((session) => session.sessionId === afterSessionId) + 1;
36794
37645
  return sessions.slice(offset, offset + limit);
36795
37646
  });
37647
+ const api = await loadDefaultPiSessionApi();
37648
+ const paths = new Map;
37649
+ for (const session of await api.listSessions()) {
37650
+ if (typeof session.id === "string" && typeof session.path === "string")
37651
+ paths.set(session.id, session.path);
37652
+ }
37653
+ await backfillPiSessionActivity(database, PI_HARNESS_KIND, paths);
36796
37654
  } catch (err) {
36797
37655
  warn(`[session-projects] background runner failed: ${err}`);
36798
37656
  }
@@ -36802,6 +37660,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36802
37660
  const projectDir = process.cwd();
36803
37661
  const seenDreamerProjectIdentities = new Set;
36804
37662
  const dreamerRegistrationOwner = {};
37663
+ activePiRuntimes.add(dreamerRegistrationOwner);
36805
37664
  const commandLifecycleController = new AbortController;
36806
37665
  registerCtxStatusLifecycleSignal(pi, commandLifecycleController.signal);
36807
37666
  let sessionShuttingDown = false;
@@ -37055,7 +37914,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37055
37914
  projectDepsByDir.delete(ctx.cwd);
37056
37915
  const current = resolveCurrentProjectDeps(ctx);
37057
37916
  activeModelRegistry = ctx.modelRegistry;
37058
- syncDreamerProjectRegistration(current, ctx.modelRegistry);
37917
+ if (ctx.hasUI)
37918
+ syncDreamerProjectRegistration(current, ctx.modelRegistry);
37059
37919
  syncCtxMemoryToolEnabled(pi, current.config.memory.enabled);
37060
37920
  const failuresToShow = claimConfigParseFailuresOnce("pi", current.configParseFailures);
37061
37921
  if (ctx.hasUI && failuresToShow.length > 0) {
@@ -37190,6 +38050,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37190
38050
  });
37191
38051
  info("registered /ctx-recomp");
37192
38052
  registerCtxWrapupCommand(pi, {
38053
+ shutdownSignal: commandLifecycleController.signal,
37193
38054
  db,
37194
38055
  runner: wrapupRunner,
37195
38056
  historianModel: bootProjectDeps.historianConfig?.model,
@@ -37241,7 +38102,10 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37241
38102
  dreamerEnabled: bootProjectDeps.dreamerEnabled,
37242
38103
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
37243
38104
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity),
37244
- ensureRegistered: (ctx) => syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry),
38105
+ ensureRegistered: (ctx) => {
38106
+ if (ctx.hasUI)
38107
+ syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry);
38108
+ },
37245
38109
  registrationOwner: dreamerRegistrationOwner
37246
38110
  });
37247
38111
  info("registered /ctx-dream");
@@ -37260,7 +38124,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37260
38124
  info("registered /ctx-embed");
37261
38125
  const dreamerConfig = bootProjectDeps.dreamerConfig;
37262
38126
  if (dreamerConfig) {
37263
- syncDreamerProjectRegistration(bootProjectDeps);
37264
38127
  info(`registered dreamer (${summarizeDreamSchedule(dreamerConfig)})`);
37265
38128
  } else {
37266
38129
  info(bootProjectDeps.dreamerEnabled ? "registered dreamer: DISABLED (no dreamer config)" : "registered dreamer: DISABLED (dreamer.disable=true or no dreamer config)");
@@ -37293,7 +38156,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37293
38156
  const effectiveConfig = effectiveProjectDeps.config;
37294
38157
  try {
37295
38158
  activeModelRegistry = ctx.modelRegistry;
37296
- syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
38159
+ if (ctx.hasUI)
38160
+ syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
37297
38161
  } catch (err) {
37298
38162
  warn("before_agent_start: dreamer registration sync threw:", err);
37299
38163
  }
@@ -37389,6 +38253,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37389
38253
  } catch (err) {
37390
38254
  log(`agent_end: channel2 delivery skipped: ${String(err)}`);
37391
38255
  }
38256
+ if (!ctx.hasUI)
38257
+ flushLogger();
37392
38258
  });
37393
38259
  pi.on("tool_execution_start", async (event, ctx) => {
37394
38260
  try {
@@ -37469,6 +38335,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37469
38335
  if (typeof sessionId !== "string" || sessionId.length === 0)
37470
38336
  return;
37471
38337
  const endedMsg = event.message;
38338
+ observePiMessageActivity(db, sessionId, endedMsg?.timestamp);
37472
38339
  if (endedMsg?.role === "assistant" && typeof endedMsg.id === "string" && endedMsg.id.length > 0) {
37473
38340
  const messageId = endedMsg.id;
37474
38341
  scheduleIncrementalIndex(db, sessionId, messageId, () => {
@@ -37549,6 +38416,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37549
38416
  pi.on("session_shutdown", async (_event, ctx) => {
37550
38417
  sessionShuttingDown = true;
37551
38418
  commandLifecycleController.abort();
38419
+ stopStatusDialogRefresh();
37552
38420
  const SHUTDOWN_DRAIN_MS = 5000;
37553
38421
  const sessionId = resolveSessionId(ctx);
37554
38422
  try {
@@ -37562,6 +38430,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37562
38430
  warn("shutdown: unregisterPiDreamerProject threw:", err);
37563
38431
  }
37564
38432
  if (sessionId) {
38433
+ abortInFlightHistorians(sessionId);
38434
+ abortInFlightRecomps(sessionId);
37565
38435
  try {
37566
38436
  await withTimeout(awaitInFlightHistorians(sessionId), SHUTDOWN_DRAIN_MS);
37567
38437
  } catch (err) {
@@ -37574,6 +38444,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37574
38444
  }
37575
38445
  abortInFlightRecomps(sessionId);
37576
38446
  }
38447
+ abortInFlightDreamers(dreamerRegistrationOwner);
37577
38448
  try {
37578
38449
  await withTimeout(awaitInFlightDreamers(dreamerRegistrationOwner), SHUTDOWN_DRAIN_MS);
37579
38450
  } catch (err) {
@@ -37588,6 +38459,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
37588
38459
  clearContextHandlerSession(sessionId);
37589
38460
  }
37590
38461
  } catch {}
38462
+ activePiRuntimes.delete(dreamerRegistrationOwner);
38463
+ if (activePiRuntimes.size === 0)
38464
+ unregisterPiProjectEmbeddings(db);
37591
38465
  });
37592
38466
  pi.on("session_before_switch", (_event, ctx) => {
37593
38467
  try {