@cortexkit/pi-magic-context 0.34.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -40,6 +40,8 @@ import {
40
40
  buildCompartmentSummaryFallbackText,
41
41
  buildPrimaryLanguageDirective,
42
42
  buildSyntheticTodoPart,
43
+ buildToolArcs,
44
+ buildTrueRawTokenIndex,
43
45
  bumpEpochsForWorkspaceMembers,
44
46
  bumpProjectUserProfileVersion,
45
47
  byteSize,
@@ -67,8 +69,10 @@ import {
67
69
  clearPersistedTodoSyntheticAnchor,
68
70
  clearRecompStaging,
69
71
  closeQuietly,
72
+ completedToolArcCrossesBoundary,
70
73
  computeCueContentHash,
71
74
  computeNormalizedHash,
75
+ computeRawRangeFingerprint,
72
76
  computeWorkspaceEpochFingerprint,
73
77
  contentSha256,
74
78
  convertEntriesToRawMessages,
@@ -76,6 +80,8 @@ import {
76
80
  cosineSimilarity,
77
81
  countPrimerCandidatesForProject,
78
82
  createPrimer,
83
+ createPromptSurfaceGuidanceEpochCache,
84
+ createPromptSurfaceRuntime,
79
85
  decodeCachedM0UpgradeIdentity,
80
86
  deleteMemory,
81
87
  deleteUserMemoryCandidates,
@@ -98,10 +104,12 @@ import {
98
104
  escapeXmlAttr,
99
105
  escapeXmlContent,
100
106
  estimateTokens,
107
+ estimateTrueRawMessageTokens,
101
108
  expandWorkspaceIdentitySetWithAliases,
102
109
  extractM0Block,
103
110
  extractTexts,
104
111
  extractToolCallSummaries,
112
+ fenceBoundaryForToolArcs,
105
113
  findAdoptableFallbackTags,
106
114
  findLastModelKeyFromBranch,
107
115
  findPiFallbackToolOwnerTags,
@@ -213,6 +221,7 @@ import {
213
221
  insertUserMemory,
214
222
  insertUserMemoryCandidates,
215
223
  invalidateMemory,
224
+ invalidateTrueRawTokenCache,
216
225
  isCompactionEnabled,
217
226
  isCompartmentLeaseHeld,
218
227
  isDreamerRunnable,
@@ -253,6 +262,7 @@ import {
253
262
  primerOccurrenceKey,
254
263
  primerOccurrenceUtcDay,
255
264
  promoteSessionFactsDurable,
265
+ promptSurfaceHashMaterial,
256
266
  pruneAutoSearchHintDecisions,
257
267
  pruneExpiredUserMemoryCandidates,
258
268
  pruneNoteNudgeAnchors,
@@ -270,6 +280,7 @@ import {
270
280
  recordHistorianDrainFailure,
271
281
  recordMemoryMapping,
272
282
  recordMemoryVerifications,
283
+ recordMuralCueRejection,
273
284
  recordOverflowDetected,
274
285
  recordProtectedTailNoEligibleHead,
275
286
  recordProtectedTailPublicationFloor,
@@ -297,6 +308,7 @@ import {
297
308
  resolveCompactionModeRecord,
298
309
  resolveGitTopLevel,
299
310
  resolveLimit,
311
+ resolveModelConfigOrDefault,
300
312
  resolveModelRefForPi,
301
313
  resolveMuralWire,
302
314
  resolvePiStableId,
@@ -330,7 +342,6 @@ import {
330
342
  setStoragePrivatePermissionEnforcement,
331
343
  setTodoSnapshot,
332
344
  sourceNameForMemory,
333
- stableStringify,
334
345
  stripMemoryMuralBlock,
335
346
  stripPersistedAssistantText,
336
347
  stripTagPrefix,
@@ -362,7 +373,7 @@ import {
362
373
  withMigrationLanguageDirective,
363
374
  withRawMessageProvider,
364
375
  withRawSessionMessageCache
365
- } from "./index-ekzg3w5y.js";
376
+ } from "./index-xbk9epe8.js";
366
377
  import {
367
378
  pushNotification
368
379
  } from "./index-hw34xmzk.js";
@@ -583,6 +594,15 @@ function formatFailClosedBlockingProcesses(processes) {
583
594
  }
584
595
  function formatFailClosedBlockingMessage(reason) {
585
596
  if (reason.kind === "migration_guard") {
597
+ if (reason.unreadableFile) {
598
+ const arm = reason.unreadableArm ?? "io";
599
+ const recovery = arm === "io" ? `If no OpenCode server is running, it is safe to delete ${reason.unreadableFile} and retry.` : `The file may be a recent incomplete write; retry after the file is older than the ten-minute grace window, or stop OpenCode before deleting it.`;
600
+ return [
601
+ `Magic Context cannot migrate the shared database because RPC discovery file ${reason.unreadableFile} is uncertain (${arm} arm), so the absence of a live OpenCode server cannot be proven.`,
602
+ recovery,
603
+ `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
604
+ ].join(" ");
605
+ }
586
606
  return [
587
607
  `Magic Context cannot migrate the shared database because ${formatFailClosedBlockingProcesses(reason.blockingProcesses)} may be running an older Magic Context build that would fail against the migrated database.`,
588
608
  "Restart the blocking process (it will pick up the new build and migrate on start), or shut it down and retry.",
@@ -1478,16 +1498,7 @@ function resolveCacheTtl(cacheTtl, modelKey) {
1478
1498
  if (typeof cacheTtl === "string") {
1479
1499
  return cacheTtl;
1480
1500
  }
1481
- if (modelKey && typeof cacheTtl[modelKey] === "string") {
1482
- return cacheTtl[modelKey];
1483
- }
1484
- if (modelKey) {
1485
- const bareModelId = modelKey.split("/").slice(1).join("/");
1486
- if (bareModelId && typeof cacheTtl[bareModelId] === "string") {
1487
- return cacheTtl[bareModelId];
1488
- }
1489
- }
1490
- return cacheTtl.default ?? "5m";
1501
+ return resolveModelConfigOrDefault(cacheTtl, modelKey, cacheTtl.default ?? "5m");
1491
1502
  }
1492
1503
  var clampWarnSeen = new Set;
1493
1504
  function isFinitePositive(v) {
@@ -3286,6 +3297,70 @@ function backoffMs(failureCount) {
3286
3297
  return minutes * 60 * 1000;
3287
3298
  }
3288
3299
 
3300
+ // ../plugin/src/features/magic-context/dreamer/storage-task-schedule.ts
3301
+ function toRow(r) {
3302
+ return {
3303
+ projectPath: r.project_path,
3304
+ task: r.task,
3305
+ lastRunAt: r.last_run_at,
3306
+ nextDueAt: r.next_due_at,
3307
+ schedule: r.schedule ?? null,
3308
+ lastStatus: r.last_status ?? null,
3309
+ lastError: r.last_error,
3310
+ retryCount: r.retry_count ?? 0,
3311
+ lastCheckedCommit: r.last_checked_commit ?? null,
3312
+ lastBroadRunAt: r.last_broad_run_at ?? null,
3313
+ retrospectiveWatermarkMs: r.retrospective_watermark_ms ?? null
3314
+ };
3315
+ }
3316
+ var SELECT_COLUMNS = "project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count, last_checked_commit, last_broad_run_at, retrospective_watermark_ms";
3317
+ function getTaskScheduleState(db, projectPath, task) {
3318
+ const row = db.prepare(`SELECT ${SELECT_COLUMNS} FROM task_schedule_state WHERE project_path = ? AND task = ?`).get(projectPath, task);
3319
+ return row ? toRow(row) : null;
3320
+ }
3321
+ function getMostRecentTaskRunAt(db, projectPath) {
3322
+ const row = db.prepare("SELECT MAX(last_run_at) AS max_at FROM task_schedule_state WHERE project_path = ?").get(projectPath);
3323
+ const value = row?.max_at ?? null;
3324
+ return typeof value === "number" && value > 0 ? value : null;
3325
+ }
3326
+ function pruneNonCanonicalTaskRows(db, projectPath, canonicalTasks) {
3327
+ if (canonicalTasks.length === 0)
3328
+ return 0;
3329
+ const placeholders = canonicalTasks.map(() => "?").join(", ");
3330
+ const result = db.prepare(`DELETE FROM task_schedule_state WHERE project_path = ? AND task NOT IN (${placeholders})`).run(projectPath, ...canonicalTasks);
3331
+ return Number(result.changes ?? 0);
3332
+ }
3333
+ function deleteTaskScheduleRowsForProject(db, projectPath) {
3334
+ const result = db.prepare("DELETE FROM task_schedule_state WHERE project_path = ?").run(projectPath);
3335
+ return Number(result.changes ?? 0);
3336
+ }
3337
+ function seedTaskScheduleState(db, projectPath, task, nextDueAt, lastRunAt, schedule) {
3338
+ db.prepare("INSERT INTO task_schedule_state (project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count) VALUES (?, ?, ?, ?, ?, NULL, NULL, 0) ON CONFLICT(project_path, task) DO NOTHING").run(projectPath, task, lastRunAt, nextDueAt, schedule);
3339
+ }
3340
+ function writeTaskScheduleState(db, row) {
3341
+ const broadCycleUpdate = row.lastBroadRunAt === undefined ? "last_broad_run_at = task_schedule_state.last_broad_run_at" : "last_broad_run_at = excluded.last_broad_run_at";
3342
+ db.prepare(`INSERT INTO task_schedule_state
3343
+ (project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count, last_checked_commit, last_broad_run_at, retrospective_watermark_ms)
3344
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3345
+ ON CONFLICT(project_path, task) DO UPDATE SET
3346
+ last_run_at = excluded.last_run_at,
3347
+ next_due_at = excluded.next_due_at,
3348
+ schedule = excluded.schedule,
3349
+ last_status = excluded.last_status,
3350
+ last_error = excluded.last_error,
3351
+ retry_count = excluded.retry_count,
3352
+ last_checked_commit = COALESCE(excluded.last_checked_commit, task_schedule_state.last_checked_commit),
3353
+ ${broadCycleUpdate},
3354
+ retrospective_watermark_ms = COALESCE(excluded.retrospective_watermark_ms, task_schedule_state.retrospective_watermark_ms)`).run(row.projectPath, row.task, row.lastRunAt, row.nextDueAt, row.schedule, row.lastStatus, row.lastError, row.retryCount, row.lastCheckedCommit ?? null, row.lastBroadRunAt ?? null, row.retrospectiveWatermarkMs ?? null);
3355
+ }
3356
+ function isRetrospectiveWindowProcessed(db, projectPath, windowKey) {
3357
+ const row = db.prepare("SELECT 1 AS one FROM retrospective_processed_windows WHERE project_path = ? AND window_key = ?").get(projectPath, windowKey);
3358
+ return row != null;
3359
+ }
3360
+ function recordRetrospectiveWindowProcessed(db, projectPath, windowKey) {
3361
+ db.prepare("INSERT INTO retrospective_processed_windows (project_path, window_key, processed_at) VALUES (?, ?, ?) ON CONFLICT(project_path, window_key) DO NOTHING").run(projectPath, windowKey, Date.now());
3362
+ }
3363
+
3289
3364
  // ../plugin/src/features/magic-context/dreamer/task-gates.ts
3290
3365
  function countActiveMemories(db, projectPath) {
3291
3366
  const row = db.prepare("SELECT COUNT(*) AS cnt FROM memories WHERE project_path = ? AND status IN ('active','permanent')").get(projectPath);
@@ -3331,6 +3406,19 @@ function countUnverifiedMappedMemories(db, projectPath) {
3331
3406
  AND v.verified_at = 0`).get(projectPath);
3332
3407
  return row?.cnt ?? 0;
3333
3408
  }
3409
+ function countBroadCycleCandidates(db, projectPath, cycleStartAt) {
3410
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3411
+ FROM memories m
3412
+ WHERE m.project_path = ?
3413
+ AND m.status IN ('active','permanent')
3414
+ AND (
3415
+ SELECT MAX(v.verified_at)
3416
+ FROM memory_verifications v
3417
+ WHERE v.memory_id = m.id
3418
+ AND v.file_path <> ''
3419
+ ) < ?`).get(projectPath, cycleStartAt);
3420
+ return row?.cnt ?? 0;
3421
+ }
3334
3422
  function countCueCandidates(db, projectPath) {
3335
3423
  if (!hasMuralCueColumns(db))
3336
3424
  return countActiveMemories(db, projectPath);
@@ -3386,7 +3474,9 @@ function getDreamTaskBacklog(db, projectPath, task, options = {}) {
3386
3474
  }
3387
3475
  case "verify-broad": {
3388
3476
  const total = countMappedMemories(db, projectPath);
3389
- return { pending: total, total };
3477
+ const cycleStartAt = getTaskScheduleState(db, projectPath, "verify-broad")?.lastBroadRunAt;
3478
+ const pending = cycleStartAt == null ? total : countBroadCycleCandidates(db, projectPath, cycleStartAt);
3479
+ return { pending, total };
3390
3480
  }
3391
3481
  case "curate": {
3392
3482
  const total = countActiveMemories(db, projectPath);
@@ -3445,7 +3535,7 @@ function evaluateTaskGate(task, ctx) {
3445
3535
  case "verify":
3446
3536
  return countActiveMemories(db, project) > 0;
3447
3537
  case "verify-broad":
3448
- return countActiveMemories(db, project) > 0;
3538
+ return getTaskScheduleState(db, project, "verify-broad")?.lastBroadRunAt != null || countActiveMemories(db, project) > 0;
3449
3539
  case "curate":
3450
3540
  return countActiveMemories(db, project) > 0;
3451
3541
  case "compress-cues":
@@ -3490,7 +3580,7 @@ function openOpenCodeDb() {
3490
3580
  }
3491
3581
 
3492
3582
  // ../plugin/src/features/magic-context/dreamer/task-executor.ts
3493
- import { createHash as createHash6 } from "node:crypto";
3583
+ import { createHash as createHash8 } from "node:crypto";
3494
3584
  import { existsSync as existsSync7 } from "node:fs";
3495
3585
 
3496
3586
  // ../plugin/src/agents/dreamer.ts
@@ -4075,6 +4165,9 @@ function parseFields(json) {
4075
4165
  return {};
4076
4166
  }
4077
4167
 
4168
+ // ../plugin/src/features/magic-context/mural/compress-cues.ts
4169
+ import { createHash as createHash2 } from "node:crypto";
4170
+
4078
4171
  // ../plugin/src/features/magic-context/dreamer/storage-dream-state.ts
4079
4172
  var getDreamStateStatements = new WeakMap;
4080
4173
  var setDreamStateStatements = new WeakMap;
@@ -4299,6 +4392,64 @@ function assertManifestCoversExactly(ids, expectedIds, rootName) {
4299
4392
  }
4300
4393
  }
4301
4394
 
4395
+ // ../plugin/src/features/magic-context/dreamer/module-apply.ts
4396
+ class DreamerModuleFailureError extends Error {
4397
+ transient = true;
4398
+ constructor(operation, cause) {
4399
+ super(`Rust dreamer ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`);
4400
+ this.name = "DreamerModuleFailureError";
4401
+ this.cause = cause;
4402
+ }
4403
+ }
4404
+ async function resolveDreamerModuleRoute(args) {
4405
+ const transport = args.transformMode === "ts" ? undefined : args.moduleClient;
4406
+ if (!transport?.authorityStatus)
4407
+ return;
4408
+ const contextStoreUuid = getContextStoreUuid(args.db);
4409
+ if (!contextStoreUuid)
4410
+ throw new Error("Rust dreamer requires a context store identity");
4411
+ const result = await transport.authorityStatus({
4412
+ context_store_uuid: contextStoreUuid,
4413
+ project: args.projectIdentity,
4414
+ projectRoot: args.projectRoot,
4415
+ domain: "memories"
4416
+ });
4417
+ if (result.authority?.state !== "MODULE")
4418
+ return;
4419
+ const generation = result.authority.generation;
4420
+ if (typeof generation !== "number")
4421
+ throw new Error("Rust authority status omitted generation");
4422
+ return {
4423
+ moduleClient: transport,
4424
+ moduleSessionId: args.projectIdentity,
4425
+ moduleProjectRoot: args.projectRoot,
4426
+ moduleContextStoreUuid: contextStoreUuid,
4427
+ moduleAuthorityGeneration: generation,
4428
+ moduleCommandId: args.commandId
4429
+ };
4430
+ }
4431
+ function getModuleMemoryIdentities(db, projectIdentity, contextIds) {
4432
+ if (contextIds.length === 0)
4433
+ return new Map;
4434
+ const placeholders = contextIds.map(() => "?").join(", ");
4435
+ const rows = db.prepare(`SELECT identity.context_row_id, identity.module_row_id, live.normalized_hash
4436
+ FROM mirror_identity identity
4437
+ LEFT JOIN mirror_live_memory_rows live
4438
+ ON live.module_project = identity.module_project
4439
+ AND live.module_row_id = identity.module_row_id
4440
+ WHERE identity.domain = 'memories' AND identity.module_project = ?
4441
+ AND identity.context_row_id IN (${placeholders})`).all(projectIdentity, ...contextIds);
4442
+ return new Map(rows.flatMap((row) => Number.isInteger(row.context_row_id) && Number.isInteger(row.module_row_id) && typeof row.normalized_hash === "string" ? [
4443
+ [
4444
+ row.context_row_id,
4445
+ {
4446
+ moduleId: row.module_row_id,
4447
+ normalizedHash: row.normalized_hash
4448
+ }
4449
+ ]
4450
+ ] : []));
4451
+ }
4452
+
4302
4453
  // ../plugin/src/features/magic-context/mural/compress-cues-prompt.ts
4303
4454
  var CUE_BUDGET_HIGH = 90;
4304
4455
  var CUE_BUDGET_LOW = 50;
@@ -4370,7 +4521,7 @@ function hasBalancedParentheses(cue) {
4370
4521
  }
4371
4522
  return depth === 0;
4372
4523
  }
4373
- function validateCue(cue, importance) {
4524
+ function validateCue(cue, importance, ownId) {
4374
4525
  const trimmed = cue.trim();
4375
4526
  if (trimmed.length === 0)
4376
4527
  return { reason: "empty" };
@@ -4378,8 +4529,9 @@ function validateCue(cue, importance) {
4378
4529
  const length = [...trimmed].length;
4379
4530
  if (length > budget)
4380
4531
  return { reason: `over-budget ${length}>${budget}` };
4381
- if (/#\d+/.test(trimmed))
4532
+ if (ownId !== undefined && new RegExp(`#${ownId}\\b`).test(trimmed)) {
4382
4533
  return { reason: "leaked-id" };
4534
+ }
4383
4535
  if (!hasBalancedParentheses(trimmed))
4384
4536
  return { reason: "unbalanced-parens" };
4385
4537
  const markers = trimmed.split("⊘").length - 1;
@@ -4395,6 +4547,7 @@ function validateCue(cue, importance) {
4395
4547
  // ../plugin/src/features/magic-context/mural/compress-cues.ts
4396
4548
  var COMPRESS_CUES_CHUNK_SIZE = 40;
4397
4549
  var CHUNK_TIMEOUT_FLOOR_MS = 240000;
4550
+ var CUE_REJECTION_LATCH_THRESHOLD = 3;
4398
4551
  var CONSECUTIVE_TIMEOUT_LIMIT = 2;
4399
4552
  function toPromptMemory(candidate) {
4400
4553
  const memory = candidate.memory;
@@ -4405,6 +4558,38 @@ function toPromptMemory(candidate) {
4405
4558
  content: memory.content
4406
4559
  };
4407
4560
  }
4561
+ function stripOwnIdToken(value, ownId) {
4562
+ return value.replace(new RegExp(`#${ownId}\\b`, "g"), "");
4563
+ }
4564
+ function truncateCue(value, budget) {
4565
+ const trimmed = value.trim();
4566
+ const codepoints = [...trimmed];
4567
+ if (codepoints.length <= budget)
4568
+ return trimmed;
4569
+ const prefix = codepoints.slice(0, budget).join("");
4570
+ const boundary = prefix.search(/\s+\S*$/);
4571
+ return (boundary > 0 ? prefix.slice(0, boundary) : prefix).trim();
4572
+ }
4573
+ function sanitizeCue(value, candidate) {
4574
+ return truncateCue(stripOwnIdToken(value, candidate.memory.id), cueBudgetFor(candidate.memory.importance ?? 50));
4575
+ }
4576
+ function deterministicFallbackCue(candidate, lastCandidate) {
4577
+ const importance = candidate.memory.importance ?? 50;
4578
+ const budget = cueBudgetFor(importance);
4579
+ const sanitizedCandidate = sanitizeCue(lastCandidate, candidate);
4580
+ if (validateCue(sanitizedCandidate, importance, candidate.memory.id) === null) {
4581
+ return sanitizedCandidate;
4582
+ }
4583
+ const sourceSlice = sanitizeCue(candidate.memory.content, candidate);
4584
+ if (validateCue(sourceSlice, importance, candidate.memory.id) === null) {
4585
+ return sourceSlice;
4586
+ }
4587
+ const grammarSafe = truncateCue(sourceSlice.replaceAll("⊘", "").replace(/[()]/g, "").replace(/\b(?:must not|never|without|instead of|exclude|excludes)\b/gi, "").replace(/\s+/g, " "), budget);
4588
+ if (validateCue(grammarSafe, importance, candidate.memory.id) === null) {
4589
+ return grammarSafe;
4590
+ }
4591
+ return "memory";
4592
+ }
4408
4593
  function selectCandidates(db, projectIdentity) {
4409
4594
  const memories = getMemoriesByProject(db, projectIdentity, ["active", "permanent"]);
4410
4595
  const cueState = getMuralCueState(db, memories.map((memory) => memory.id));
@@ -4536,7 +4721,7 @@ async function compressOneChunk(args, chunk, sliceMs, signal) {
4536
4721
  return text;
4537
4722
  }
4538
4723
  });
4539
- return applyCues(args, chunk, run.validated);
4724
+ return args.moduleRoute ? await applyCuesThroughModule(args, chunk, run.validated, signal) : applyCues(args, chunk, run.validated);
4540
4725
  } catch (error) {
4541
4726
  const desc = describeError(error);
4542
4727
  log(`[dreamer] compress-cues chunk failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
@@ -4573,10 +4758,19 @@ function applyCues(args, chunk, manifestText) {
4573
4758
  const candidate = byId.get(entry.id);
4574
4759
  if (!candidate)
4575
4760
  throw new Error(`cues manifest contains unknown id ${entry.id}`);
4576
- const failure = validateCue(entry.cue, candidate.memory.importance ?? 50);
4761
+ const importance = candidate.memory.importance ?? 50;
4762
+ const failure = validateCue(entry.cue, importance, candidate.memory.id);
4577
4763
  if (failure) {
4764
+ const rejectionCount = recordMuralCueRejection(args.db, args.projectIdentity, entry.id, candidate.contentHash);
4765
+ if (rejectionCount >= CUE_REJECTION_LATCH_THRESHOLD) {
4766
+ const fallback = deterministicFallbackCue(candidate, entry.cue);
4767
+ setMuralCue(args.db, args.projectIdentity, entry.id, fallback, candidate.contentHash);
4768
+ compressed += 1;
4769
+ log(`[dreamer] compress-cues: fallback cue for memory ${entry.id} (${failure.reason}; ${rejectionCount} rejections; fallback)`);
4770
+ continue;
4771
+ }
4578
4772
  skipped += 1;
4579
- log(`[dreamer] compress-cues: skipped cue for memory ${entry.id} (${failure.reason})`);
4773
+ log(`[dreamer] compress-cues: skipped cue for memory ${entry.id} (${failure.reason}; rejection ${rejectionCount}/${CUE_REJECTION_LATCH_THRESHOLD})`);
4580
4774
  continue;
4581
4775
  }
4582
4776
  setMuralCue(args.db, args.projectIdentity, entry.id, entry.cue.trim(), candidate.contentHash);
@@ -4585,6 +4779,111 @@ function applyCues(args, chunk, manifestText) {
4585
4779
  });
4586
4780
  return { compressed, skipped };
4587
4781
  }
4782
+ async function applyCuesThroughModule(args, chunk, manifestText, signal) {
4783
+ const route = args.moduleRoute;
4784
+ if (!route)
4785
+ throw new Error("module cue apply called without a module route");
4786
+ const byId = new Map(chunk.map((candidate) => [candidate.memory.id, candidate]));
4787
+ const parsed = parseCuesManifest(manifestText);
4788
+ assertManifestCoversExactly(parsed.map((entry) => entry.id), new Set(byId.keys()), "cues");
4789
+ const state2 = getMuralCueState(args.db, chunk.map((candidate) => candidate.memory.id));
4790
+ const identities = getModuleMemoryIdentities(args.db, args.projectIdentity, chunk.map((candidate) => candidate.memory.id));
4791
+ const updates = [];
4792
+ for (const entry of parsed) {
4793
+ const candidate = byId.get(entry.id);
4794
+ if (!candidate)
4795
+ throw new Error(`cues manifest contains unknown id ${entry.id}`);
4796
+ const identity = identities.get(candidate.memory.id);
4797
+ if (!identity) {
4798
+ throw new Error(`module mirror identity missing for memory ${candidate.memory.id}`);
4799
+ }
4800
+ const failure = validateCue(entry.cue, candidate.memory.importance ?? 50, candidate.memory.id);
4801
+ if (!failure) {
4802
+ updates.push({
4803
+ contextId: candidate.memory.id,
4804
+ moduleId: identity.moduleId,
4805
+ contentHash: candidate.contentHash,
4806
+ cue: entry.cue.trim(),
4807
+ rejectionCount: 0,
4808
+ kind: "compressed"
4809
+ });
4810
+ continue;
4811
+ }
4812
+ const previous = state2.get(candidate.memory.id);
4813
+ const rejectionCount = previous?.hash === candidate.contentHash ? (previous.rejectionCount ?? 0) + 1 : 1;
4814
+ if (rejectionCount >= CUE_REJECTION_LATCH_THRESHOLD) {
4815
+ updates.push({
4816
+ contextId: candidate.memory.id,
4817
+ moduleId: identity.moduleId,
4818
+ contentHash: candidate.contentHash,
4819
+ cue: deterministicFallbackCue(candidate, entry.cue),
4820
+ rejectionCount: 0,
4821
+ kind: "compressed"
4822
+ });
4823
+ log(`[dreamer] compress-cues: fallback cue for memory ${entry.id} (${failure.reason}; ${rejectionCount} rejections; fallback)`);
4824
+ } else {
4825
+ updates.push({
4826
+ contextId: candidate.memory.id,
4827
+ moduleId: identity.moduleId,
4828
+ contentHash: candidate.contentHash,
4829
+ cue: null,
4830
+ rejectionCount,
4831
+ kind: "skipped"
4832
+ });
4833
+ log(`[dreamer] compress-cues: skipped cue for memory ${entry.id} (${failure.reason}; rejection ${rejectionCount}/${CUE_REJECTION_LATCH_THRESHOLD})`);
4834
+ }
4835
+ }
4836
+ const commandId = `mural-cues:${route.moduleCommandId}:${createHash2("sha256").update(chunk.map((candidate) => candidate.memory.id).join(",")).digest("hex").slice(0, 24)}`;
4837
+ let response;
4838
+ try {
4839
+ response = await route.moduleClient.call({
4840
+ sessionId: route.moduleSessionId,
4841
+ projectRoot: route.moduleProjectRoot,
4842
+ method: "memory.set_mural_cue",
4843
+ body: {
4844
+ name: "memory.set_mural_cue",
4845
+ arguments: {
4846
+ memory_project: args.projectIdentity,
4847
+ context_store_uuid: route.moduleContextStoreUuid,
4848
+ authority_generation: route.moduleAuthorityGeneration,
4849
+ command_id: commandId,
4850
+ rows: updates.map((update) => ({
4851
+ memory_id: update.moduleId,
4852
+ content_hash_at_prompt: update.contentHash,
4853
+ cue: update.cue,
4854
+ rejection_count: update.rejectionCount
4855
+ }))
4856
+ }
4857
+ },
4858
+ signal
4859
+ });
4860
+ } catch (error) {
4861
+ throw new DreamerModuleFailureError("mural cue apply", error);
4862
+ }
4863
+ const result = response?.result ?? response;
4864
+ if (!result || typeof result !== "object") {
4865
+ throw new Error("module returned invalid mural cue apply result");
4866
+ }
4867
+ const accepted = result.accepted;
4868
+ if (!Array.isArray(accepted) || !accepted.every((id) => Number.isInteger(id))) {
4869
+ throw new Error("module returned no mural cue acceptance list");
4870
+ }
4871
+ const acceptedIds = new Set(accepted);
4872
+ const rejected = result.rejected;
4873
+ const rejectedReasons = new Map;
4874
+ for (const row of Array.isArray(rejected) ? rejected : []) {
4875
+ const reason = row && typeof row === "object" && typeof row.reason === "string" ? row.reason : "unknown";
4876
+ rejectedReasons.set(reason, (rejectedReasons.get(reason) ?? 0) + 1);
4877
+ }
4878
+ if ([...rejectedReasons].some(([reason]) => reason !== "stale")) {
4879
+ throw new Error(`module rejected mural cues (${[...rejectedReasons].map(([reason, count]) => `${reason}=${count}`).join(", ")})`);
4880
+ }
4881
+ return updates.reduce((counts, update) => {
4882
+ if (acceptedIds.has(update.moduleId))
4883
+ counts[update.kind] += 1;
4884
+ return counts;
4885
+ }, { compressed: 0, skipped: 0 });
4886
+ }
4588
4887
 
4589
4888
  // ../plugin/src/features/magic-context/dreamer/task-prompts.ts
4590
4889
  var PROJECT_MEMORY_TAXONOMY = `## Memory taxonomy (5 categories)
@@ -5099,7 +5398,7 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
5099
5398
  }
5100
5399
 
5101
5400
  // ../plugin/src/features/magic-context/dreamer/classify.ts
5102
- import { createHash as createHash2 } from "node:crypto";
5401
+ import { createHash as createHash3 } from "node:crypto";
5103
5402
 
5104
5403
  // ../plugin/src/plugin/rust-tool-backends.ts
5105
5404
  function isRustAuthorityDrainingError(error) {
@@ -5367,64 +5666,6 @@ function parseClassifyManifest(text) {
5367
5666
  return out;
5368
5667
  }
5369
5668
 
5370
- // ../plugin/src/features/magic-context/dreamer/module-apply.ts
5371
- class DreamerModuleFailureError extends Error {
5372
- transient = true;
5373
- constructor(operation, cause) {
5374
- super(`Rust dreamer ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`);
5375
- this.name = "DreamerModuleFailureError";
5376
- this.cause = cause;
5377
- }
5378
- }
5379
- async function resolveDreamerModuleRoute(args) {
5380
- const transport = args.transformMode === "ts" ? undefined : args.moduleClient;
5381
- if (!transport?.authorityStatus)
5382
- return;
5383
- const contextStoreUuid = getContextStoreUuid(args.db);
5384
- if (!contextStoreUuid)
5385
- throw new Error("Rust dreamer requires a context store identity");
5386
- const result = await transport.authorityStatus({
5387
- context_store_uuid: contextStoreUuid,
5388
- project: args.projectIdentity,
5389
- projectRoot: args.projectRoot,
5390
- domain: "memories"
5391
- });
5392
- if (result.authority?.state !== "MODULE")
5393
- return;
5394
- const generation = result.authority.generation;
5395
- if (typeof generation !== "number")
5396
- throw new Error("Rust authority status omitted generation");
5397
- return {
5398
- moduleClient: transport,
5399
- moduleSessionId: args.projectIdentity,
5400
- moduleProjectRoot: args.projectRoot,
5401
- moduleContextStoreUuid: contextStoreUuid,
5402
- moduleAuthorityGeneration: generation,
5403
- moduleCommandId: args.commandId
5404
- };
5405
- }
5406
- function getModuleMemoryIdentities(db, projectIdentity, contextIds) {
5407
- if (contextIds.length === 0)
5408
- return new Map;
5409
- const placeholders = contextIds.map(() => "?").join(", ");
5410
- const rows = db.prepare(`SELECT identity.context_row_id, identity.module_row_id, live.normalized_hash
5411
- FROM mirror_identity identity
5412
- LEFT JOIN mirror_live_memory_rows live
5413
- ON live.module_project = identity.module_project
5414
- AND live.module_row_id = identity.module_row_id
5415
- WHERE identity.domain = 'memories' AND identity.module_project = ?
5416
- AND identity.context_row_id IN (${placeholders})`).all(projectIdentity, ...contextIds);
5417
- return new Map(rows.flatMap((row) => Number.isInteger(row.context_row_id) && Number.isInteger(row.module_row_id) && typeof row.normalized_hash === "string" ? [
5418
- [
5419
- row.context_row_id,
5420
- {
5421
- moduleId: row.module_row_id,
5422
- normalizedHash: row.normalized_hash
5423
- }
5424
- ]
5425
- ] : []));
5426
- }
5427
-
5428
5669
  // ../plugin/src/features/magic-context/dreamer/classify.ts
5429
5670
  var MIN_POOL_TO_CLASSIFY = 10;
5430
5671
  var FULL_POOL_CEILING = 100;
@@ -5661,7 +5902,7 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
5661
5902
  v: 1,
5662
5903
  session_id: args.moduleSessionId,
5663
5904
  task: "classify",
5664
- command_id: `classify:${args.moduleCommandId ?? Date.now()}:${createHash2("sha256").update(chunk.map((candidate) => candidate.id).join(",")).digest("hex").slice(0, 24)}`,
5905
+ command_id: `classify:${args.moduleCommandId ?? Date.now()}:${createHash3("sha256").update(chunk.map((candidate) => candidate.id).join(",")).digest("hex").slice(0, 24)}`,
5665
5906
  authority_generation: args.moduleAuthorityGeneration,
5666
5907
  payload: {
5667
5908
  prompt_body: prompt,
@@ -6299,7 +6540,7 @@ function throwIfAborted2(signal) {
6299
6540
  }
6300
6541
 
6301
6542
  // ../plugin/src/features/magic-context/smart-notes/compiler.ts
6302
- import { createHash as createHash3 } from "node:crypto";
6543
+ import { createHash as createHash4 } from "node:crypto";
6303
6544
 
6304
6545
  // ../plugin/src/features/magic-context/smart-notes/compiler-prompt.ts
6305
6546
  var SMART_NOTE_COMPILER_SYSTEM_PROMPT = `You are the Magic Context smart-note compiler for the magic-context system.
@@ -6723,7 +6964,7 @@ function manifestAdvisoryWarnings(code, manifest) {
6723
6964
  return warnings;
6724
6965
  }
6725
6966
  function hashCheck(surfaceCondition, compiledCheck, manifest, checkCron) {
6726
- return createHash3("sha256").update(surfaceCondition ?? "").update("\x00").update(compiledCheck).update("\x00").update(JSON.stringify(manifest)).update("\x00").update(checkCron).digest("hex");
6967
+ return createHash4("sha256").update(surfaceCondition ?? "").update("\x00").update(compiledCheck).update("\x00").update(JSON.stringify(manifest)).update("\x00").update(checkCron).digest("hex");
6727
6968
  }
6728
6969
  function extractJsonObject(output) {
6729
6970
  const fenced = output.match(/```(?:json)?\s*([\s\S]*?)```/i);
@@ -7403,7 +7644,7 @@ function enforceMaintainDocsProtectedRegions(args) {
7403
7644
  }
7404
7645
 
7405
7646
  // ../plugin/src/features/magic-context/dreamer/map-memories.ts
7406
- import { createHash as createHash4 } from "node:crypto";
7647
+ import { createHash as createHash5 } from "node:crypto";
7407
7648
 
7408
7649
  // ../plugin/src/features/magic-context/dreamer/map-memories-prompt.ts
7409
7650
  import { existsSync as existsSync6, statSync } from "node:fs";
@@ -7664,7 +7905,7 @@ async function applyBatchMappings(args, batch, manifestText) {
7664
7905
  memory_project: args.projectIdentity,
7665
7906
  context_store_uuid: args.moduleRoute.moduleContextStoreUuid,
7666
7907
  authority_generation: args.moduleRoute.moduleAuthorityGeneration,
7667
- command_id: `${args.moduleRoute.moduleCommandId}:${createHash4("sha256").update(rows.map((row) => row.memory_id).join(",")).digest("hex").slice(0, 16)}`,
7908
+ command_id: `${args.moduleRoute.moduleCommandId}:${createHash5("sha256").update(rows.map((row) => row.memory_id).join(",")).digest("hex").slice(0, 16)}`,
7668
7909
  rows
7669
7910
  }
7670
7911
  }
@@ -8721,77 +8962,100 @@ function insertDreamRun(db, run) {
8721
8962
  getInsertDreamRunStatement(db).run(run.projectPath, run.startedAt, run.finishedAt, run.holderId, JSON.stringify(run.tasks), run.tasksSucceeded, run.tasksFailed, run.smartNotesSurfaced, run.smartNotesPending, run.memoryChanges ? JSON.stringify(run.memoryChanges) : null, run.parentSessionId ?? null);
8722
8963
  }
8723
8964
 
8724
- // ../plugin/src/features/magic-context/dreamer/storage-task-schedule.ts
8725
- function toRow(r) {
8726
- return {
8727
- projectPath: r.project_path,
8728
- task: r.task,
8729
- lastRunAt: r.last_run_at,
8730
- nextDueAt: r.next_due_at,
8731
- schedule: r.schedule ?? null,
8732
- lastStatus: r.last_status ?? null,
8733
- lastError: r.last_error,
8734
- retryCount: r.retry_count ?? 0,
8735
- lastCheckedCommit: r.last_checked_commit ?? null,
8736
- lastBroadRunAt: r.last_broad_run_at ?? null,
8737
- retrospectiveWatermarkMs: r.retrospective_watermark_ms ?? null
8738
- };
8739
- }
8740
- var SELECT_COLUMNS = "project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count, last_checked_commit, last_broad_run_at, retrospective_watermark_ms";
8741
- function getTaskScheduleState(db, projectPath, task) {
8742
- const row = db.prepare(`SELECT ${SELECT_COLUMNS} FROM task_schedule_state WHERE project_path = ? AND task = ?`).get(projectPath, task);
8743
- return row ? toRow(row) : null;
8744
- }
8745
- function getMostRecentTaskRunAt(db, projectPath) {
8746
- const row = db.prepare("SELECT MAX(last_run_at) AS max_at FROM task_schedule_state WHERE project_path = ?").get(projectPath);
8747
- const value = row?.max_at ?? null;
8748
- return typeof value === "number" && value > 0 ? value : null;
8749
- }
8750
- function pruneNonCanonicalTaskRows(db, projectPath, canonicalTasks) {
8751
- if (canonicalTasks.length === 0)
8752
- return 0;
8753
- const placeholders = canonicalTasks.map(() => "?").join(", ");
8754
- const result = db.prepare(`DELETE FROM task_schedule_state WHERE project_path = ? AND task NOT IN (${placeholders})`).run(projectPath, ...canonicalTasks);
8755
- return Number(result.changes ?? 0);
8756
- }
8757
- function deleteTaskScheduleRowsForProject(db, projectPath) {
8758
- const result = db.prepare("DELETE FROM task_schedule_state WHERE project_path = ?").run(projectPath);
8759
- return Number(result.changes ?? 0);
8760
- }
8761
- function seedTaskScheduleState(db, projectPath, task, nextDueAt, lastRunAt, schedule) {
8762
- db.prepare("INSERT INTO task_schedule_state (project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count) VALUES (?, ?, ?, ?, ?, NULL, NULL, 0) ON CONFLICT(project_path, task) DO NOTHING").run(projectPath, task, lastRunAt, nextDueAt, schedule);
8763
- }
8764
- function writeTaskScheduleState(db, row) {
8765
- db.prepare(`INSERT INTO task_schedule_state
8766
- (project_path, task, last_run_at, next_due_at, schedule, last_status, last_error, retry_count, last_checked_commit, last_broad_run_at, retrospective_watermark_ms)
8767
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8768
- ON CONFLICT(project_path, task) DO UPDATE SET
8769
- last_run_at = excluded.last_run_at,
8770
- next_due_at = excluded.next_due_at,
8771
- schedule = excluded.schedule,
8772
- last_status = excluded.last_status,
8773
- last_error = excluded.last_error,
8774
- retry_count = excluded.retry_count,
8775
- last_checked_commit = COALESCE(excluded.last_checked_commit, task_schedule_state.last_checked_commit),
8776
- last_broad_run_at = COALESCE(excluded.last_broad_run_at, task_schedule_state.last_broad_run_at),
8777
- retrospective_watermark_ms = COALESCE(excluded.retrospective_watermark_ms, task_schedule_state.retrospective_watermark_ms)`).run(row.projectPath, row.task, row.lastRunAt, row.nextDueAt, row.schedule, row.lastStatus, row.lastError, row.retryCount, row.lastCheckedCommit ?? null, row.lastBroadRunAt ?? null, row.retrospectiveWatermarkMs ?? null);
8965
+ // ../plugin/src/features/magic-context/dreamer/verify.ts
8966
+ import { createHash as createHash7 } from "node:crypto";
8967
+
8968
+ // ../plugin/src/features/magic-context/dreamer/provider-output-failure.ts
8969
+ import { createHash as createHash6 } from "node:crypto";
8970
+ var MAX_NEAR_ZERO_OUTPUT_TOKENS = 32;
8971
+
8972
+ class DreamerProviderOutputFailureError extends Error {
8973
+ fingerprint;
8974
+ outputTokens;
8975
+ reasoningTokens;
8976
+ transient = true;
8977
+ constructor(fingerprint, outputTokens, reasoningTokens, responseText) {
8978
+ const preview = responseText.trim().replace(/\s+/g, " ").slice(0, 160);
8979
+ super(`verify provider-outage completion (output_tokens=${outputTokens}, reasoning_tokens=${reasoningTokens}): ${JSON.stringify(preview)}`);
8980
+ this.fingerprint = fingerprint;
8981
+ this.outputTokens = outputTokens;
8982
+ this.reasoningTokens = reasoningTokens;
8983
+ this.name = "DreamerProviderOutputFailureError";
8984
+ }
8778
8985
  }
8779
- function isRetrospectiveWindowProcessed(db, projectPath, windowKey) {
8780
- const row = db.prepare("SELECT 1 AS one FROM retrospective_processed_windows WHERE project_path = ? AND window_key = ?").get(projectPath, windowKey);
8781
- return row != null;
8986
+ function finiteTokenCount(value) {
8987
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
8782
8988
  }
8783
- function recordRetrospectiveWindowProcessed(db, projectPath, windowKey) {
8784
- db.prepare("INSERT INTO retrospective_processed_windows (project_path, window_key, processed_at) VALUES (?, ?, ?) ON CONFLICT(project_path, window_key) DO NOTHING").run(projectPath, windowKey, Date.now());
8989
+ function completionShape(value) {
8990
+ if (!isRecord(value))
8991
+ return null;
8992
+ const info = isRecord(value.info) ? value.info : value;
8993
+ if (info.role !== "assistant")
8994
+ return null;
8995
+ const time = isRecord(info.time) ? info.time : null;
8996
+ const tokens = isRecord(info.tokens) ? info.tokens : null;
8997
+ return {
8998
+ createdAt: typeof time?.created === "number" ? time.created : 0,
8999
+ finish: typeof info.finish === "string" ? info.finish : typeof info.finish_reason === "string" ? info.finish_reason : typeof info.finishReason === "string" ? info.finishReason : null,
9000
+ error: info.error,
9001
+ outputTokens: finiteTokenCount(tokens?.output),
9002
+ reasoningTokens: finiteTokenCount(tokens?.reasoning)
9003
+ };
9004
+ }
9005
+ function latestAssistantCompletion(messages) {
9006
+ if (!Array.isArray(messages))
9007
+ return null;
9008
+ let latest = null;
9009
+ for (const message of messages) {
9010
+ const completion = completionShape(message);
9011
+ if (completion && (!latest || completion.createdAt >= latest.createdAt))
9012
+ latest = completion;
9013
+ }
9014
+ return latest;
9015
+ }
9016
+ function providerOutputFailureFromInvalidManifest(messages, responseText) {
9017
+ const completion = latestAssistantCompletion(messages);
9018
+ if (completion?.finish?.toLowerCase() !== "stop")
9019
+ return null;
9020
+ if (completion.error != null || completion.outputTokens === null || completion.outputTokens > MAX_NEAR_ZERO_OUTPUT_TOKENS || completion.reasoningTokens !== 0) {
9021
+ return null;
9022
+ }
9023
+ const normalized = responseText.trim().replace(/\s+/g, " ").toLowerCase();
9024
+ if (!normalized)
9025
+ return null;
9026
+ const fingerprint = createHash6("sha256").update(normalized).digest("hex").slice(0, 16);
9027
+ return new DreamerProviderOutputFailureError(fingerprint, completion.outputTokens, completion.reasoningTokens, responseText);
8785
9028
  }
8786
-
8787
- // ../plugin/src/features/magic-context/dreamer/verify.ts
8788
- import { createHash as createHash5 } from "node:crypto";
8789
9029
 
8790
9030
  // ../plugin/src/features/magic-context/dreamer/verify-gate.ts
8791
9031
  import path5 from "node:path";
8792
9032
  function minOf(values) {
8793
9033
  return values.reduce((acc, v) => v < acc ? v : acc, Number.POSITIVE_INFINITY);
8794
9034
  }
9035
+ function ensureBroadCycleStart(args) {
9036
+ const current = getTaskScheduleState(args.db, args.projectIdentity, "verify-broad");
9037
+ if (current?.lastBroadRunAt != null && current.lastBroadRunAt > 0) {
9038
+ return current.lastBroadRunAt;
9039
+ }
9040
+ if (!current)
9041
+ return args.runStartedAt;
9042
+ if (!args.holderId || !args.leaseKey) {
9043
+ throw new Error("verify-broad cycle opening requires the task lease");
9044
+ }
9045
+ return runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
9046
+ const latest = getTaskScheduleState(args.db, args.projectIdentity, "verify-broad");
9047
+ if (!latest)
9048
+ return args.runStartedAt;
9049
+ if (latest.lastBroadRunAt != null && latest.lastBroadRunAt > 0) {
9050
+ return latest.lastBroadRunAt;
9051
+ }
9052
+ writeTaskScheduleState(args.db, {
9053
+ ...latest,
9054
+ lastBroadRunAt: args.runStartedAt
9055
+ });
9056
+ return args.runStartedAt;
9057
+ });
9058
+ }
8795
9059
  async function partitionVerifyScope(args) {
8796
9060
  const runStartedAt = args.now ?? Date.now();
8797
9061
  const active = getMemoriesByProject(args.db, args.projectIdentity);
@@ -8803,6 +9067,29 @@ async function partitionVerifyScope(args) {
8803
9067
  content: m.content,
8804
9068
  mappedFiles: verById.get(m.id)?.files ?? []
8805
9069
  });
9070
+ if (args.forceBroad) {
9071
+ const broadCycleStartAt = ensureBroadCycleStart({
9072
+ db: args.db,
9073
+ projectIdentity: args.projectIdentity,
9074
+ holderId: args.holderId,
9075
+ leaseKey: args.leaseKey,
9076
+ runStartedAt
9077
+ });
9078
+ const broadCandidates = candidates.filter((m) => (verById.get(m.id)?.verifiedAt ?? 0) < broadCycleStartAt).sort((a, b) => {
9079
+ const verifiedAtA = verById.get(a.id)?.verifiedAt ?? 0;
9080
+ const verifiedAtB = verById.get(b.id)?.verifiedAt ?? 0;
9081
+ return verifiedAtA - verifiedAtB || a.id - b.id;
9082
+ });
9083
+ return {
9084
+ runStartedAt,
9085
+ mode: "broad",
9086
+ inScope: broadCandidates.map(toPrompt),
9087
+ inScopeIds: broadCandidates.map((m) => m.id),
9088
+ skippedIds: candidates.filter((m) => !broadCandidates.some((candidate) => candidate.id === m.id)).map((m) => m.id),
9089
+ broadCycleStartAt,
9090
+ reason: `broad cycle (${broadCandidates.length} remain; started ${broadCycleStartAt})`
9091
+ };
9092
+ }
8806
9093
  if (candidates.length === 0) {
8807
9094
  return {
8808
9095
  runStartedAt,
@@ -8813,16 +9100,6 @@ async function partitionVerifyScope(args) {
8813
9100
  reason: "no file-mapped memories in scope"
8814
9101
  };
8815
9102
  }
8816
- if (args.forceBroad) {
8817
- return {
8818
- runStartedAt,
8819
- mode: "broad",
8820
- inScope: candidates.map(toPrompt),
8821
- inScopeIds: candidates.map((m) => m.id),
8822
- skippedIds: [],
8823
- reason: "broad full-pool verification of file-mapped memories"
8824
- };
8825
- }
8826
9103
  const allInScope = (mode, reason) => ({
8827
9104
  runStartedAt,
8828
9105
  mode,
@@ -8941,7 +9218,26 @@ function parseVerifyManifest(text) {
8941
9218
 
8942
9219
  // ../plugin/src/features/magic-context/dreamer/verify.ts
8943
9220
  var VERIFY_BATCH_SIZE = 50;
9221
+ var IDENTICAL_PROVIDER_FAILURE_BATCH_LIMIT = 2;
9222
+ function closeBroadCycle(args, cycleStartAt) {
9223
+ if (!args.forceBroad || cycleStartAt === undefined)
9224
+ return;
9225
+ if (!getTaskScheduleState(args.db, args.projectIdentity, "verify-broad"))
9226
+ return;
9227
+ runLeaseGuardedWrite(args.db, args.holderId, args.leaseKey, () => {
9228
+ const current = getTaskScheduleState(args.db, args.projectIdentity, "verify-broad");
9229
+ if (current?.lastBroadRunAt !== cycleStartAt)
9230
+ return;
9231
+ if (!current)
9232
+ return;
9233
+ writeTaskScheduleState(args.db, {
9234
+ ...current,
9235
+ lastBroadRunAt: null
9236
+ });
9237
+ });
9238
+ }
8944
9239
  async function runVerify(args) {
9240
+ const runStartedAt = Date.now();
8945
9241
  const result = {
8946
9242
  verified: 0,
8947
9243
  updated: 0,
@@ -8956,20 +9252,29 @@ async function runVerify(args) {
8956
9252
  db: args.db,
8957
9253
  projectIdentity: args.projectIdentity,
8958
9254
  projectDirectory: args.sessionDirectory,
8959
- forceBroad: args.forceBroad
9255
+ forceBroad: args.forceBroad,
9256
+ now: runStartedAt,
9257
+ holderId: args.holderId,
9258
+ leaseKey: args.leaseKey
8960
9259
  });
8961
9260
  result.mode = gate.mode;
9261
+ result.broadCycleStartAt = gate.broadCycleStartAt;
8962
9262
  result.inScope = gate.inScope.length;
8963
9263
  result.remaining = gate.inScope.length;
8964
9264
  log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"} gate: mode=${gate.mode} in_scope=${gate.inScope.length} skipped=${gate.skippedIds.length} reason=${gate.reason}`);
8965
- if (gate.inScope.length === 0)
9265
+ if (gate.inScope.length === 0) {
9266
+ closeBroadCycle(args, gate.broadCycleStartAt);
8966
9267
  return result;
9268
+ }
8967
9269
  const batches = [];
8968
9270
  for (let i = 0;i < gate.inScope.length; i += VERIFY_BATCH_SIZE) {
8969
9271
  batches.push(gate.inScope.slice(i, i + VERIFY_BATCH_SIZE));
8970
9272
  }
8971
9273
  const abortController = new AbortController;
8972
9274
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => abortController.abort());
9275
+ let consecutiveProviderFailures = 0;
9276
+ let priorProviderFailureFingerprint = null;
9277
+ let lastProviderFailure = null;
8973
9278
  try {
8974
9279
  for (let i = 0;i < batches.length; i += 1) {
8975
9280
  const remainingMs = Math.max(0, args.deadline - Date.now());
@@ -8984,8 +9289,28 @@ async function runVerify(args) {
8984
9289
  result.remaining -= counts.verified + counts.updated + counts.archived;
8985
9290
  result.batches += 1;
8986
9291
  args.onProgress?.(result.verified + result.updated + result.archived);
9292
+ if (counts.providerFailure) {
9293
+ lastProviderFailure = counts.providerFailure;
9294
+ if (counts.providerFailure.fingerprint === priorProviderFailureFingerprint) {
9295
+ consecutiveProviderFailures += 1;
9296
+ } else {
9297
+ priorProviderFailureFingerprint = counts.providerFailure.fingerprint;
9298
+ consecutiveProviderFailures = 1;
9299
+ }
9300
+ if (consecutiveProviderFailures >= IDENTICAL_PROVIDER_FAILURE_BATCH_LIMIT) {
9301
+ log(`[dreamer] verify run aborting after ${consecutiveProviderFailures} identical provider-failure batches`);
9302
+ throw counts.providerFailure;
9303
+ }
9304
+ } else {
9305
+ priorProviderFailureFingerprint = null;
9306
+ consecutiveProviderFailures = 0;
9307
+ }
8987
9308
  }
9309
+ if (lastProviderFailure)
9310
+ throw lastProviderFailure;
8988
9311
  result.complete = result.remaining === 0;
9312
+ if (result.complete)
9313
+ closeBroadCycle(args, gate.broadCycleStartAt);
8989
9314
  log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"}: verified=${result.verified} updated=${result.updated} archived=${result.archived} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}`);
8990
9315
  return result;
8991
9316
  } finally {
@@ -9040,7 +9365,14 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
9040
9365
  const text = extractLatestAssistantText(messages);
9041
9366
  if (!text)
9042
9367
  throw new Error("verify returned no output");
9043
- parseVerifyManifest(text);
9368
+ try {
9369
+ parseVerifyManifest(text);
9370
+ } catch (error) {
9371
+ const providerFailure = providerOutputFailureFromInvalidManifest(messages, text);
9372
+ if (providerFailure)
9373
+ throw providerFailure;
9374
+ throw error;
9375
+ }
9044
9376
  return text;
9045
9377
  }
9046
9378
  });
@@ -9048,11 +9380,12 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
9048
9380
  return await applyVerifyManifest(args, batch, run.validated);
9049
9381
  } catch (error) {
9050
9382
  const desc = describeError(error);
9051
- log(`[dreamer] verify batch failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
9383
+ const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
9384
+ log(`[dreamer] verify batch ${providerFailure ? "provider failure" : "failed"}: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
9052
9385
  recordInvocation4(args, startedAt, { status: "failed", error });
9053
9386
  if (error instanceof DreamerModuleFailureError || signal.aborted)
9054
9387
  throw error;
9055
- return { verified: 0, updated: 0, archived: 0 };
9388
+ return { verified: 0, updated: 0, archived: 0, providerFailure };
9056
9389
  } finally {
9057
9390
  if (agentSessionId && !shouldKeepSubagents()) {
9058
9391
  await args.client.session.delete({
@@ -9124,7 +9457,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
9124
9457
  memory_project: args.projectIdentity,
9125
9458
  context_store_uuid: args.moduleRoute.moduleContextStoreUuid,
9126
9459
  authority_generation: args.moduleRoute.moduleAuthorityGeneration,
9127
- command_id: `${args.moduleRoute.moduleCommandId}:${createHash5("sha256").update(rows.map((row) => row.memory_id).join(",")).digest("hex").slice(0, 16)}`,
9460
+ command_id: `${args.moduleRoute.moduleCommandId}:${createHash7("sha256").update(rows.map((row) => row.memory_id).join(",")).digest("hex").slice(0, 16)}`,
9128
9461
  rows
9129
9462
  }
9130
9463
  }
@@ -9319,7 +9652,7 @@ function createDreamTaskExecutor(deps) {
9319
9652
  resultChars: 0,
9320
9653
  ...error ? { error } : {},
9321
9654
  backlog: (() => {
9322
- const end = getDreamTaskBacklog(db, projectIdentity, config.task);
9655
+ const end = extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task);
9323
9656
  const processed = processedDreamTaskItems(backlogAtStart.pending, end.pending);
9324
9657
  const value = {
9325
9658
  pendingAtStart: backlogAtStart.pending,
@@ -9368,12 +9701,6 @@ function createDreamTaskExecutor(deps) {
9368
9701
  recordRun("completed", null);
9369
9702
  return { status: "completed" };
9370
9703
  }
9371
- if (moduleRoute) {
9372
- const reason = "compress-cues parked: MODULE memory authority has no cue write facade";
9373
- log(`[dreamer] compress-cues: skipped (${reason})`);
9374
- recordRun("failed", reason);
9375
- return { status: "failed", transient: true, error: reason };
9376
- }
9377
9704
  const result = await runCompressCues({
9378
9705
  db,
9379
9706
  client: deps.client,
@@ -9385,6 +9712,7 @@ function createDreamTaskExecutor(deps) {
9385
9712
  deadline,
9386
9713
  model: config.model ?? deps.mural.model ?? deps.dreamerModel,
9387
9714
  fallbackModels: config.fallbackModels,
9715
+ moduleRoute,
9388
9716
  onProgress: (processed) => reportProgress(processed)
9389
9717
  });
9390
9718
  log(`[dreamer] compress-cues: compressed=${result.compressed} skipped=${result.skipped} chunks=${result.chunks} remaining=${result.remaining}`);
@@ -9454,19 +9782,31 @@ function createDreamTaskExecutor(deps) {
9454
9782
  fallbackModels: config.fallbackModels,
9455
9783
  language: config.language ?? deps.language,
9456
9784
  moduleRoute,
9457
- onProgress: (processed) => reportProgress(processed)
9785
+ onProgress: (processed2) => reportProgress(processed2)
9458
9786
  });
9787
+ const processed = result.verified + result.updated + result.archived;
9788
+ const broadProgress = config.task === "verify-broad" ? `verify-broad cycle ${result.broadCycleStartAt ?? "open"}: verified ${processed}, ${result.remaining} remain` : null;
9789
+ const backlogAfter = config.task === "verify-broad" ? { pending: result.remaining, total: backlogAtStart.total } : undefined;
9459
9790
  if (!result.complete) {
9791
+ if (broadProgress && processed > 0) {
9792
+ recordRun("completed", broadProgress, {
9793
+ memoryChanges: computeMemoryDelta(memoryBefore),
9794
+ backlogAfter
9795
+ });
9796
+ return { status: "completed", error: broadProgress };
9797
+ }
9460
9798
  const error = incompleteMessage(result.remaining);
9461
9799
  recordRun("failed", error, {
9462
- memoryChanges: computeMemoryDelta(memoryBefore)
9800
+ memoryChanges: computeMemoryDelta(memoryBefore),
9801
+ backlogAfter
9463
9802
  });
9464
9803
  return { status: "failed", transient: true, error };
9465
9804
  }
9466
- recordRun("completed", null, {
9467
- memoryChanges: computeMemoryDelta(memoryBefore)
9805
+ recordRun("completed", broadProgress, {
9806
+ memoryChanges: computeMemoryDelta(memoryBefore),
9807
+ backlogAfter
9468
9808
  });
9469
- return { status: "completed" };
9809
+ return broadProgress ? { status: "completed", error: broadProgress } : { status: "completed" };
9470
9810
  }
9471
9811
  if (config.task === "classify-memories") {
9472
9812
  let moduleArgs;
@@ -9654,7 +9994,7 @@ function parseFrictionGateVerdict(verdict) {
9654
9994
  }
9655
9995
  function computeRetrospectiveWindowKey(flagged) {
9656
9996
  const anchors = flagged.map((message) => `${message.sessionId}:${message.ts}`).sort().join("|");
9657
- return createHash6("sha256").update(anchors).digest("hex").slice(0, 32);
9997
+ return createHash8("sha256").update(anchors).digest("hex").slice(0, 32);
9658
9998
  }
9659
9999
  function renderFrictionWindow(messages, flaggedOrdinals, radius = 2) {
9660
10000
  const flagged = new Set(flaggedOrdinals);
@@ -12757,513 +13097,6 @@ function replayCavemanCompression(sessionId, db, targets, tags) {
12757
13097
  return replayed;
12758
13098
  }
12759
13099
 
12760
- // ../plugin/src/hooks/magic-context/read-session-true-raw-tokens.ts
12761
- var MAX_MESSAGE_CACHE_ENTRIES = 1e5;
12762
- var MAX_MESSAGE_CACHE_KEY_BYTES = 64 * 1024 * 1024;
12763
- var FNV1A_32_OFFSET = 2166136261;
12764
- var FNV1A_32_PRIME = 16777619;
12765
- var messageEstimateCache = new Map;
12766
- var messageEstimateCacheBytes = 0;
12767
- var EMPTY_BREAKDOWN = {
12768
- text: 0,
12769
- reasoning: 0,
12770
- toolInput: 0,
12771
- toolOutput: 0,
12772
- image: 0,
12773
- other: 0,
12774
- total: 0
12775
- };
12776
- function addBreakdown(target, kind, value) {
12777
- if (kind === "total")
12778
- return;
12779
- const safeValue = Number.isFinite(value) && value > 0 ? Math.round(value) : 0;
12780
- target[kind] += safeValue;
12781
- target.total += safeValue;
12782
- }
12783
- function estimateStructured(value) {
12784
- if (typeof value === "string")
12785
- return estimateTokens(value);
12786
- if (value === undefined || value === null)
12787
- return 0;
12788
- return estimateTokens(stableStringify(value));
12789
- }
12790
- function firstStringField(record, fields) {
12791
- for (const field of fields) {
12792
- const value = record[field];
12793
- if (typeof value === "string" && value.length > 0)
12794
- return value;
12795
- }
12796
- return null;
12797
- }
12798
- function stringValue(value) {
12799
- if (typeof value === "string")
12800
- return value;
12801
- if (value === undefined || value === null)
12802
- return "";
12803
- return stableStringify(value);
12804
- }
12805
- function textFromToolResultContent(content) {
12806
- if (typeof content === "string")
12807
- return content;
12808
- if (Array.isArray(content)) {
12809
- const pieces = [];
12810
- for (const entry of content) {
12811
- if (typeof entry === "string") {
12812
- pieces.push(entry);
12813
- } else if (isRecord(entry)) {
12814
- const text = firstStringField(entry, ["text", "content", "value"]);
12815
- pieces.push(text ?? stableStringify(entry));
12816
- } else if (entry !== null && entry !== undefined) {
12817
- pieces.push(String(entry));
12818
- }
12819
- }
12820
- return pieces.join(`
12821
- `);
12822
- }
12823
- return stringValue(content);
12824
- }
12825
- function looksImageLike(part) {
12826
- const type = typeof part.type === "string" ? part.type.toLowerCase() : "";
12827
- const mime = typeof part.mime === "string" ? part.mime.toLowerCase() : "";
12828
- const mediaType = typeof part.mediaType === "string" ? part.mediaType.toLowerCase() : "";
12829
- return type.includes("image") || mime.startsWith("image/") || mediaType.startsWith("image/") || part.image_url !== undefined || part.imageUrl !== undefined || part.image !== undefined;
12830
- }
12831
- function defaultImageTokenHeuristic(part) {
12832
- if (isRecord(part)) {
12833
- const width = part.width;
12834
- const height = part.height;
12835
- if (typeof width === "number" && typeof height === "number" && width > 0 && height > 0) {
12836
- return Math.max(256, Math.min(4096, Math.ceil(width * height / 750)));
12837
- }
12838
- }
12839
- return 1024;
12840
- }
12841
- function partType(part) {
12842
- return typeof part.type === "string" ? part.type : "";
12843
- }
12844
- function hasOwn(record, key) {
12845
- return Object.hasOwn(record, key);
12846
- }
12847
- function recursiveByteLength(value) {
12848
- if (value === null || value === undefined)
12849
- return 0;
12850
- if (typeof value === "string")
12851
- return value.length;
12852
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
12853
- return String(value).length;
12854
- }
12855
- if (Array.isArray(value)) {
12856
- return value.reduce((sum, item) => sum + recursiveByteLength(item), value.length);
12857
- }
12858
- if (isRecord(value)) {
12859
- let total = Object.keys(value).length;
12860
- for (const [key, child] of Object.entries(value)) {
12861
- total += key.length + recursiveByteLength(child);
12862
- }
12863
- return total;
12864
- }
12865
- return String(value).length;
12866
- }
12867
- function updateFnv1a32(hash, text) {
12868
- let next = hash;
12869
- for (let index = 0;index < text.length; index += 1) {
12870
- next ^= text.charCodeAt(index);
12871
- next = Math.imul(next, FNV1A_32_PRIME) >>> 0;
12872
- }
12873
- return next;
12874
- }
12875
- function contentStringsHash(fields) {
12876
- let hash = FNV1A_32_OFFSET;
12877
- for (const field of fields) {
12878
- hash = updateFnv1a32(hash, `${field.length}:`);
12879
- hash = updateFnv1a32(hash, field);
12880
- hash = updateFnv1a32(hash, "\x00");
12881
- }
12882
- return hash.toString(16).padStart(8, "0");
12883
- }
12884
- function rawPartVersion(part) {
12885
- return part.__magicContextPartUpdatedAt ?? part.updated_at ?? part.updatedAt ?? part.version ?? part.revision ?? "";
12886
- }
12887
- function callIdFromPart(part) {
12888
- const direct = firstStringField(part, ["callID", "callId", "toolCallId", "tool_call_id", "id"]);
12889
- if (direct)
12890
- return direct;
12891
- const state2 = isRecord(part.state) ? part.state : null;
12892
- return state2 ? firstStringField(state2, ["callID", "callId", "toolCallId", "tool_call_id", "id"]) ?? "" : "";
12893
- }
12894
- function toolSignalFromPart(part) {
12895
- if (!isRecord(part))
12896
- return null;
12897
- const type = partType(part);
12898
- const state2 = isRecord(part.state) ? part.state : null;
12899
- const callId = callIdFromPart(part);
12900
- if (!callId && type !== "tool")
12901
- return null;
12902
- if (type === "tool") {
12903
- const hasInput = state2 !== null && hasOwn(state2, "input");
12904
- const outputKey = state2 ? hasOwn(state2, "output") ? "output" : hasOwn(state2, "error") ? "error" : hasOwn(state2, "result") ? "result" : null : null;
12905
- const hasOutput = outputKey !== null;
12906
- const outputValue = outputKey && state2 ? state2[outputKey] : undefined;
12907
- const providerExecuted = part.providerExecuted === true;
12908
- const openInvocation = !providerExecuted && !hasOutput;
12909
- return {
12910
- callId,
12911
- hasInput: hasInput || openInvocation,
12912
- hasOutput,
12913
- inputText: hasInput && state2 ? stringValue(state2.input) : "",
12914
- outputText: hasOutput ? stringValue(outputValue) : ""
12915
- };
12916
- }
12917
- if (type === "tool-invocation") {
12918
- const args = part.args ?? part.input;
12919
- return {
12920
- callId,
12921
- hasInput: args !== undefined,
12922
- hasOutput: false,
12923
- inputText: args !== undefined ? stringValue(args) : "",
12924
- outputText: ""
12925
- };
12926
- }
12927
- if (type === "tool_use") {
12928
- const input = part.input;
12929
- return {
12930
- callId,
12931
- hasInput: input !== undefined,
12932
- hasOutput: false,
12933
- inputText: input !== undefined ? stringValue(input) : "",
12934
- outputText: ""
12935
- };
12936
- }
12937
- if (type === "tool_result") {
12938
- const content = part.content ?? part.output ?? part.result;
12939
- return {
12940
- callId,
12941
- hasInput: false,
12942
- hasOutput: content !== undefined,
12943
- inputText: "",
12944
- outputText: content !== undefined ? textFromToolResultContent(content) : ""
12945
- };
12946
- }
12947
- return null;
12948
- }
12949
- function partCheapFingerprint(part) {
12950
- if (!isRecord(part))
12951
- return `${typeof part}:${recursiveByteLength(part)}`;
12952
- const version = rawPartVersion(part);
12953
- const type = typeof part.type === "string" ? part.type : "";
12954
- return `${type}:${String(version)}:${recursiveByteLength(part)}`;
12955
- }
12956
- function messageCacheKey(message, options) {
12957
- const namespace = "cacheNamespace" in options ? options.cacheNamespace : "estimate";
12958
- const cheapFingerprint = message.parts.map(partCheapFingerprint).join("|");
12959
- return [
12960
- namespace,
12961
- options.providerShapeVersion,
12962
- message.id || `ordinal:${message.ordinal}`,
12963
- message.role,
12964
- message.parts.length,
12965
- cheapFingerprint
12966
- ].join("\x00");
12967
- }
12968
- function setCachedEstimate(key, breakdown) {
12969
- const keyEstimateBytes = key.length * 2 + 64;
12970
- const existing = messageEstimateCache.get(key);
12971
- if (existing)
12972
- messageEstimateCacheBytes -= existing.keyEstimateBytes;
12973
- messageEstimateCache.set(key, { breakdown, keyEstimateBytes });
12974
- messageEstimateCacheBytes += keyEstimateBytes;
12975
- while (messageEstimateCache.size > MAX_MESSAGE_CACHE_ENTRIES || messageEstimateCacheBytes > MAX_MESSAGE_CACHE_KEY_BYTES) {
12976
- const first = messageEstimateCache.keys().next().value;
12977
- if (typeof first !== "string")
12978
- break;
12979
- const removed = messageEstimateCache.get(first);
12980
- if (removed)
12981
- messageEstimateCacheBytes -= removed.keyEstimateBytes;
12982
- messageEstimateCache.delete(first);
12983
- }
12984
- }
12985
- function cloneBreakdown(value) {
12986
- return { ...value };
12987
- }
12988
- function estimateNonToolPart(part, options, breakdown) {
12989
- if (!isRecord(part)) {
12990
- if (part !== null && part !== undefined)
12991
- addBreakdown(breakdown, "other", estimateStructured(part));
12992
- return true;
12993
- }
12994
- const type = partType(part);
12995
- if (type === "step-start" || type === "step-finish" || type === "meta" && Object.keys(part).length <= 1) {
12996
- return true;
12997
- }
12998
- if (type === "text") {
12999
- const text = firstStringField(part, ["text", "content"]);
13000
- if (text)
13001
- addBreakdown(breakdown, "text", estimateTokens(text));
13002
- return true;
13003
- }
13004
- if (type === "reasoning" || type === "thinking" || type === "redacted_thinking") {
13005
- const text = firstStringField(part, ["thinking", "text", "content", "reasoning"]);
13006
- if (text) {
13007
- addBreakdown(breakdown, "reasoning", estimateTokens(text));
13008
- } else {
13009
- addBreakdown(breakdown, "other", estimateStructured(part));
13010
- }
13011
- return true;
13012
- }
13013
- const reasoningText = firstStringField(part, ["thinking", "reasoning"]);
13014
- if (reasoningText && type.length === 0) {
13015
- addBreakdown(breakdown, "reasoning", estimateTokens(reasoningText));
13016
- return true;
13017
- }
13018
- if (looksImageLike(part)) {
13019
- addBreakdown(breakdown, "image", options.imageTokenHeuristic?.(part) ?? defaultImageTokenHeuristic(part));
13020
- const altText = firstStringField(part, ["alt", "text", "description"]);
13021
- if (altText)
13022
- addBreakdown(breakdown, "text", estimateTokens(altText));
13023
- return true;
13024
- }
13025
- if (type.includes("file") || type === "source") {
13026
- const content = firstStringField(part, ["content", "text", "source"]);
13027
- if (content)
13028
- addBreakdown(breakdown, "text", estimateTokens(content));
13029
- else
13030
- addBreakdown(breakdown, "other", estimateStructured(part));
13031
- return true;
13032
- }
13033
- return false;
13034
- }
13035
- function estimateTrueRawMessageTokens(message, options) {
13036
- const breakdown = cloneBreakdown(EMPTY_BREAKDOWN);
13037
- const countedInput = new Set;
13038
- const countedOutput = new Set;
13039
- let ordinalToolIndex = 0;
13040
- for (const part of message.parts) {
13041
- const signal = toolSignalFromPart(part);
13042
- if (signal) {
13043
- const localKey = `${signal.callId || "tool"}:${message.ordinal}:${ordinalToolIndex}`;
13044
- ordinalToolIndex += 1;
13045
- if (signal.hasInput) {
13046
- const key = `${signal.callId}:input:${message.ordinal}`;
13047
- if (!countedInput.has(key)) {
13048
- countedInput.add(key);
13049
- addBreakdown(breakdown, "toolInput", estimateTokens(signal.inputText));
13050
- }
13051
- }
13052
- if (signal.hasOutput) {
13053
- const key = `${signal.callId}:output:${message.ordinal}:${localKey}`;
13054
- if (!countedOutput.has(key)) {
13055
- countedOutput.add(key);
13056
- addBreakdown(breakdown, "toolOutput", estimateTokens(signal.outputText));
13057
- }
13058
- }
13059
- continue;
13060
- }
13061
- if (!estimateNonToolPart(part, options, breakdown)) {
13062
- addBreakdown(breakdown, "other", estimateStructured(part));
13063
- }
13064
- }
13065
- return breakdown;
13066
- }
13067
- function buildToolArcs(messages) {
13068
- const openQueues = new Map;
13069
- const arcs = [];
13070
- for (const message of messages) {
13071
- for (const part of message.parts) {
13072
- const signal = toolSignalFromPart(part);
13073
- if (!signal || signal.callId.length === 0)
13074
- continue;
13075
- if (signal.hasInput && signal.hasOutput) {
13076
- arcs.push({
13077
- callId: signal.callId,
13078
- invOrdinal: message.ordinal,
13079
- resOrdinal: message.ordinal
13080
- });
13081
- continue;
13082
- }
13083
- if (signal.hasInput) {
13084
- const queue = openQueues.get(signal.callId) ?? [];
13085
- queue.push(message.ordinal);
13086
- openQueues.set(signal.callId, queue);
13087
- continue;
13088
- }
13089
- if (signal.hasOutput) {
13090
- const queue = openQueues.get(signal.callId) ?? [];
13091
- const invOrdinal = queue.shift();
13092
- if (queue.length === 0)
13093
- openQueues.delete(signal.callId);
13094
- else
13095
- openQueues.set(signal.callId, queue);
13096
- if (invOrdinal !== undefined) {
13097
- arcs.push({ callId: signal.callId, invOrdinal, resOrdinal: message.ordinal });
13098
- }
13099
- }
13100
- }
13101
- }
13102
- for (const [callId, queue] of openQueues) {
13103
- for (const invOrdinal of queue) {
13104
- arcs.push({ callId, invOrdinal, resOrdinal: null });
13105
- }
13106
- }
13107
- return arcs.sort((a, b) => a.invOrdinal - b.invOrdinal || (a.resOrdinal ?? Number.MAX_SAFE_INTEGER) - (b.resOrdinal ?? Number.MAX_SAFE_INTEGER));
13108
- }
13109
- function fenceBoundaryForToolArcs(candidate, arcs, lastCompartmentEndOrdinal, recentOpenArcCutoff) {
13110
- let boundary = candidate;
13111
- for (const arc of arcs) {
13112
- if (arc.resOrdinal !== null) {
13113
- if (arc.invOrdinal < boundary && boundary <= arc.resOrdinal) {
13114
- boundary = arc.resOrdinal + 1;
13115
- }
13116
- continue;
13117
- }
13118
- if (arc.invOrdinal < recentOpenArcCutoff)
13119
- continue;
13120
- if (arc.invOrdinal >= lastCompartmentEndOrdinal + 1 && arc.invOrdinal < boundary) {
13121
- return arc.invOrdinal;
13122
- }
13123
- if (arc.invOrdinal >= boundary) {
13124
- return arc.invOrdinal;
13125
- }
13126
- }
13127
- return boundary;
13128
- }
13129
- function tokenForMessage(message, options) {
13130
- const key = messageCacheKey(message, options);
13131
- const cached = messageEstimateCache.get(key);
13132
- if (cached)
13133
- return cloneBreakdown(cached.breakdown);
13134
- const breakdown = estimateTrueRawMessageTokens(message, options);
13135
- setCachedEstimate(key, breakdown);
13136
- return cloneBreakdown(breakdown);
13137
- }
13138
- function buildTrueRawTokenIndex(sessionId, messages, options) {
13139
- const ordered = [...messages].sort((a, b) => a.ordinal - b.ordinal);
13140
- const sliceCount = ordered.length;
13141
- const firstOrdinal = ordered.length > 0 ? ordered[0].ordinal : 1;
13142
- const terminalOrdinal = ordered.length > 0 ? ordered[ordered.length - 1].ordinal : 0;
13143
- const rawMessageCount = Math.max(sliceCount, terminalOrdinal, options.absoluteMessageCount ?? sliceCount);
13144
- const ordinalSpan = terminalOrdinal >= firstOrdinal ? terminalOrdinal - firstOrdinal + 1 : 0;
13145
- const tokensByOrdinal = new Map;
13146
- const idsByOrdinal = new Map;
13147
- const prefix = new Array(ordinalSpan + 1).fill(0);
13148
- for (const message of ordered) {
13149
- const stored = options.storedTotalForMessage?.(message);
13150
- const total = stored !== undefined && stored !== null ? stored : tokenForMessage(message, options).total;
13151
- tokensByOrdinal.set(message.ordinal, total);
13152
- idsByOrdinal.set(message.ordinal, message.id);
13153
- const relative = message.ordinal - firstOrdinal + 1;
13154
- if (relative >= 1 && relative <= ordinalSpan) {
13155
- prefix[relative] = total;
13156
- }
13157
- }
13158
- for (let k = 1;k <= ordinalSpan; k += 1) {
13159
- prefix[k] += prefix[k - 1];
13160
- }
13161
- const ordinalToIndex = (ordinal) => Math.max(0, Math.min(ordinalSpan, ordinal - firstOrdinal));
13162
- return {
13163
- sessionId,
13164
- providerShapeVersion: options.providerShapeVersion,
13165
- rawMessageCount,
13166
- tokenForOrdinal(ordinal) {
13167
- return tokensByOrdinal.get(ordinal) ?? 0;
13168
- },
13169
- messageIdAtOrdinal(ordinal) {
13170
- return idsByOrdinal.get(ordinal) ?? null;
13171
- },
13172
- suffixTokensFromOrdinal(ordinal) {
13173
- if (ordinal <= firstOrdinal)
13174
- return prefix[ordinalSpan];
13175
- if (ordinal > terminalOrdinal)
13176
- return 0;
13177
- return prefix[ordinalSpan] - prefix[ordinalToIndex(ordinal)];
13178
- },
13179
- rangeTokens(startInclusive, endExclusive) {
13180
- const start = Math.max(firstOrdinal, startInclusive);
13181
- const end = Math.max(start, Math.min(terminalOrdinal + 1, endExclusive));
13182
- return prefix[end - firstOrdinal] - prefix[start - firstOrdinal];
13183
- },
13184
- findSuffixStartForTokens(tokens) {
13185
- if (!Number.isFinite(tokens) || tokens <= 0)
13186
- return terminalOrdinal + 1;
13187
- const target = Math.max(0, Math.floor(tokens));
13188
- const total = prefix[ordinalSpan];
13189
- if (total < target)
13190
- return firstOrdinal;
13191
- const cut = total - target;
13192
- let lo = 0;
13193
- let hi = ordinalSpan;
13194
- let best = 0;
13195
- while (lo <= hi) {
13196
- const mid = lo + hi >> 1;
13197
- if (prefix[mid] <= cut) {
13198
- best = mid;
13199
- lo = mid + 1;
13200
- } else {
13201
- hi = mid - 1;
13202
- }
13203
- }
13204
- return firstOrdinal + best;
13205
- },
13206
- findHeadEndForCap(startInclusive, endExclusive, capTokens) {
13207
- const start = Math.max(firstOrdinal, Math.min(terminalOrdinal + 1, startInclusive));
13208
- const end = Math.max(start, Math.min(terminalOrdinal + 1, endExclusive));
13209
- if (!Number.isFinite(capTokens) || capTokens <= 0)
13210
- return start;
13211
- const startIndex = start - firstOrdinal;
13212
- const endIndex = end - firstOrdinal;
13213
- const cut = prefix[startIndex] + Math.floor(capTokens);
13214
- let lo = startIndex + 1;
13215
- let hi = endIndex;
13216
- let bestEndIndex = startIndex;
13217
- while (lo <= hi) {
13218
- const mid = lo + hi >> 1;
13219
- if (prefix[mid] <= cut) {
13220
- bestEndIndex = mid;
13221
- lo = mid + 1;
13222
- } else {
13223
- hi = mid - 1;
13224
- }
13225
- }
13226
- let bestEnd = firstOrdinal + bestEndIndex;
13227
- if (bestEnd === start && start < end)
13228
- bestEnd = start + 1;
13229
- return Math.min(bestEnd, end);
13230
- }
13231
- };
13232
- }
13233
- function partContentFingerprint(part) {
13234
- if (!isRecord(part))
13235
- return `${typeof part}:${recursiveByteLength(part)}`;
13236
- const tool = toolSignalFromPart(part);
13237
- if (tool) {
13238
- return contentStringsHash([tool.inputText, tool.outputText]);
13239
- }
13240
- const text = firstStringField(part, ["text", "thinking", "reasoning", "content", "url"]) ?? "";
13241
- return contentStringsHash([text]);
13242
- }
13243
- function computeRawRangeFingerprint(messages, startInclusive, endExclusive) {
13244
- const pieces = [];
13245
- for (const message of messages) {
13246
- if (message.ordinal < startInclusive || message.ordinal >= endExclusive)
13247
- continue;
13248
- const partFingerprint = message.parts.map(partContentFingerprint).join(",");
13249
- pieces.push(`${message.ordinal}:${message.id}:${message.parts.length}:${partFingerprint}`);
13250
- }
13251
- return pieces.join("|");
13252
- }
13253
- function invalidateTrueRawTokenCache(args) {
13254
- const sessionNeedle = args.sessionId ? `${args.sessionId}` : null;
13255
- const messageNeedle = args.messageId ? `\x00${args.messageId}\x00` : null;
13256
- for (const [key, value] of messageEstimateCache) {
13257
- const sessionMatches = sessionNeedle === null || key.includes(sessionNeedle);
13258
- const messageMatches = messageNeedle === null || key.includes(messageNeedle);
13259
- if (sessionMatches && messageMatches) {
13260
- messageEstimateCache.delete(key);
13261
- messageEstimateCacheBytes -= value.keyEstimateBytes;
13262
- }
13263
- }
13264
- args.reason;
13265
- }
13266
-
13267
13100
  // ../plugin/src/hooks/magic-context/protected-tail-boundary.ts
13268
13101
  var ALPHA = 0.3;
13269
13102
  var FLOOR_RATIO = 0.08;
@@ -13384,7 +13217,7 @@ function fenceWrapupBoundaryForToolArcs(args) {
13384
13217
  if (arc.resOrdinal === null) {
13385
13218
  continue;
13386
13219
  }
13387
- if (arc.invOrdinal >= args.lastCompartmentEndOrdinal + 1 && arc.invOrdinal < next && next <= arc.resOrdinal) {
13220
+ if (arc.invOrdinal >= args.lastCompartmentEndOrdinal + 1 && completedToolArcCrossesBoundary(arc.invOrdinal, arc.resOrdinal, next)) {
13388
13221
  next = arc.invOrdinal;
13389
13222
  }
13390
13223
  }
@@ -14401,7 +14234,7 @@ function advanceToolReclaimWatermarkToCurrentMax(db, sessionId) {
14401
14234
  }
14402
14235
 
14403
14236
  // ../plugin/src/shared/tag-transcript.ts
14404
- import { createHash as createHash7 } from "node:crypto";
14237
+ import { createHash as createHash9 } from "node:crypto";
14405
14238
 
14406
14239
  // ../plugin/src/hooks/magic-context/image-token-estimate.ts
14407
14240
  var IMAGE_TOKEN_DIVISOR = 750;
@@ -14556,7 +14389,7 @@ function readUint32BE(b, offset) {
14556
14389
  // ../plugin/src/shared/tag-transcript.ts
14557
14390
  var TEXT_TAG_IDENTITY_MARKER = ":mc-text-v1:";
14558
14391
  function textIdentityDigest(value) {
14559
- return createHash7("sha256").update(value).digest("hex");
14392
+ return createHash9("sha256").update(value).digest("hex");
14560
14393
  }
14561
14394
  function buildContentDerivedTextIds(messageId, parts) {
14562
14395
  const sources = parts.filter((part) => part.kind === "text").map((part) => stripTagPrefix(part.getText() ?? ""));
@@ -18443,6 +18276,7 @@ function mapParsedCompartmentsToChunk(compartments, chunk, sequenceOffset) {
18443
18276
 
18444
18277
  // ../plugin/src/hooks/magic-context/compartment-runner-validation.ts
18445
18278
  var MIN_RECOMP_CHUNK_TOKEN_BUDGET = 20;
18279
+ var HISTORIAN_BOUNDARY_HEALING_SLACK = 2;
18446
18280
  function healCompartmentGaps(compartments, toolOnlyRanges = []) {
18447
18281
  for (let i = 1;i < compartments.length; i++) {
18448
18282
  const prev = compartments[i - 1];
@@ -18458,6 +18292,36 @@ function healCompartmentGaps(compartments, toolOnlyRanges = []) {
18458
18292
  }
18459
18293
  }
18460
18294
  }
18295
+ function boundarySplitsCompletedToolArc(boundary, arcs = []) {
18296
+ return arcs.some((arc) => completedToolArcCrossesBoundary(arc.start, arc.end, boundary));
18297
+ }
18298
+ function healTerminalCompletedToolArc(compartments, unprocessedFrom, arcs = [], chunkEnd) {
18299
+ const last = compartments[compartments.length - 1];
18300
+ if (!last)
18301
+ return unprocessedFrom;
18302
+ const originalEnd = last.endMessage;
18303
+ for (let pass = 0;pass <= arcs.length; pass += 1) {
18304
+ const boundary = last.endMessage + 1;
18305
+ let nextEnd = last.endMessage;
18306
+ for (const arc of arcs) {
18307
+ if (arc.end <= chunkEnd && completedToolArcCrossesBoundary(arc.start, arc.end, boundary)) {
18308
+ nextEnd = Math.max(nextEnd, arc.end);
18309
+ }
18310
+ }
18311
+ if (nextEnd === last.endMessage)
18312
+ break;
18313
+ last.endMessage = nextEnd;
18314
+ }
18315
+ return last.endMessage !== originalEnd && unprocessedFrom !== null ? last.endMessage + 1 : unprocessedFrom;
18316
+ }
18317
+ function shouldDiscardLastHistorianCompartment(compartments, chunk) {
18318
+ if (compartments.length < 2)
18319
+ return false;
18320
+ const last = compartments[compartments.length - 1];
18321
+ const previous = compartments[compartments.length - 2];
18322
+ const lookaheadMargin = chunk.endIndex - last.endMessage;
18323
+ return lookaheadMargin <= HISTORIAN_BOUNDARY_HEALING_SLACK && !boundarySplitsCompletedToolArc(previous.endMessage + 1, chunk.completedToolArcs);
18324
+ }
18461
18325
  function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, sequenceOffset) {
18462
18326
  const parsed = parseCompartmentOutput(text);
18463
18327
  if (parsed.compartments.length === 0) {
@@ -18467,6 +18331,7 @@ function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, se
18467
18331
  };
18468
18332
  }
18469
18333
  healCompartmentGaps(parsed.compartments, chunk.toolOnlyRanges);
18334
+ parsed.unprocessedFrom = healTerminalCompletedToolArc(parsed.compartments, parsed.unprocessedFrom, chunk.completedToolArcs, chunk.endIndex);
18470
18335
  const mapped = mapParsedCompartmentsToChunk(parsed.compartments, chunk, sequenceOffset);
18471
18336
  if (!mapped.ok) {
18472
18337
  return {
@@ -18481,6 +18346,13 @@ function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, se
18481
18346
  error: `Historian returned invalid compartment output: ${parsedValidationError}`
18482
18347
  };
18483
18348
  }
18349
+ const last = parsed.compartments[parsed.compartments.length - 1];
18350
+ if (last && boundarySplitsCompletedToolArc(last.endMessage + 1, chunk.completedToolArcs)) {
18351
+ return {
18352
+ ok: false,
18353
+ error: "Historian terminal boundary splits a completed tool invocation/result arc"
18354
+ };
18355
+ }
18484
18356
  return {
18485
18357
  ok: true,
18486
18358
  compartments: mapped.compartments,
@@ -20910,17 +20782,14 @@ ${chunkText}`,
20910
20782
  }
20911
20783
  }
20912
20784
  }
20913
- const BOUNDARY_HEALING_SLACK = 2;
20914
20785
  const inEmergency = getOverflowState(db, sessionId).needsEmergencyRecovery;
20915
20786
  const emittedCompartments = validatedPass.compartments;
20916
20787
  let newCompartments = emittedCompartments;
20917
- if (!inEmergency && !forceKeepLastCompartmentForChunk && emittedCompartments.length >= 2) {
20788
+ if (!inEmergency && !forceKeepLastCompartmentForChunk && shouldDiscardLastHistorianCompartment(emittedCompartments, chunk)) {
20918
20789
  const lastEmitted = emittedCompartments[emittedCompartments.length - 1];
20919
20790
  const lookaheadMargin = chunk.endIndex - lastEmitted.endMessage;
20920
- if (lookaheadMargin <= BOUNDARY_HEALING_SLACK) {
20921
- newCompartments = emittedCompartments.slice(0, -1);
20922
- sessionLog(sessionId, `historian discard-last: dropped provisional compartment ${lastEmitted.startMessage}-${lastEmitted.endMessage} (lookaheadMargin=${lookaheadMargin} <= ${BOUNDARY_HEALING_SLACK}); will re-derive next run`);
20923
- }
20791
+ newCompartments = emittedCompartments.slice(0, -1);
20792
+ sessionLog(sessionId, `historian discard-last: dropped provisional compartment ${lastEmitted.startMessage}-${lastEmitted.endMessage} (lookaheadMargin=${lookaheadMargin} <= ${HISTORIAN_BOUNDARY_HEALING_SLACK}); will re-derive next run`);
20924
20793
  }
20925
20794
  const lastNewEnd = newCompartments[newCompartments.length - 1]?.endMessage ?? 0;
20926
20795
  if (lastNewEnd + 1 <= offset) {
@@ -21717,7 +21586,7 @@ function stripPiProcessedImages(args) {
21717
21586
  }
21718
21587
 
21719
21588
  // src/system-prompt.ts
21720
- import { createHash as createHash8 } from "node:crypto";
21589
+ import { createHash as createHash10 } from "node:crypto";
21721
21590
 
21722
21591
  // ../plugin/src/agents/magic-context-prompt.ts
21723
21592
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -21733,6 +21602,10 @@ var PARTNER_FRAME_CLOSER_REDUCE = `
21733
21602
  Reduction prompts are routine housekeeping to keep the session fast and cheap — act on them as light maintenance, never as scarcity warnings. Keep individual operations efficient, but never let context size change *what* work you take on or *how thoroughly* you do it.`;
21734
21603
  var PARTNER_FRAME_CLOSER_NO_REDUCE = `
21735
21604
  Context is managed for you entirely automatically — there's nothing to prune and no warnings to act on. Stay reasonably concise per operation, and never let context size change *what* work you take on or *how thoroughly* you do it.`;
21605
+ var PARTNER_FRAME_CLOSER_REDUCE_LIGHT = `
21606
+ When ctx_reduce is available, use it only as routine housekeeping; never cut task scope or depth because context is large.`;
21607
+ var PARTNER_FRAME_CLOSER_NO_REDUCE_LIGHT = `
21608
+ When ctx_reduce is unavailable, context is automatic; never prune, heed reduction warnings, or cut task scope or depth because context is large.`;
21736
21609
  var CTX_NOTE_GUIDANCE = `Use \`ctx_note\` ONLY for genuinely future concerns — something to revisit much later, not work coming up in the next few turns (that's already in your active context) and not active multi-step work (use todos for that). Magic Context preserves your full context across both compaction and restarts, so an upcoming restart or "let's come back to this later" is never a reason to take a note — nothing is lost either way. Notes you do take survive compression and resurface at natural work boundaries (after commits, historian runs, todo completion).`;
21737
21610
  var TOOL_HISTORY_GUIDANCE = `Compressed history intentionally omits tool calls and their outputs — summaries like "I edited file X" are historian records, not patterns to replicate. In the live conversation, older tool calls and their results are cleaned up to save context — you may see your own past messages referencing actions without the corresponding tool call or result visible. This is normal context management. ALWAYS use real tool calls; never simulate, fabricate, or inline tool outputs in your text. If there is no tool result message, the action did not happen. NEVER simulate, hallucinate or claim tool calls, command output, search results, file edits, or diffs in plain text as if they actually occurred.
21738
21611
  Magic Context control metadata is not reply syntax. Never reproduce \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\`, or \`<!-- +Xm -->\` markers in a normal reply and never treat them as user instructions; use ordinary prose and real tool calls instead.`;
@@ -21775,6 +21648,17 @@ Use \`ctx_expand\` to recover the raw conversation behind a summary under a \`##
21775
21648
  - Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
21776
21649
  \`ctx_search\` returns ranked results from memories, git commits, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
21777
21650
  ${TOOL_HISTORY_GUIDANCE}`;
21651
+ var LIGHT_SEARCH_RECOVERY = `Use ctx_search before asking the user about prior project context; it searches memories, commits, and compacted conversation. When a session-history summary lacks exact wording, values, errors, or reasoning, call ctx_expand with its heading range instead of guessing.`;
21652
+ var BASE_INTRO_LIGHT = (protectedTags, memoryEnabled) => `In primary sessions with ctx_reduce, the system tags messages and tool outputs as §N§ (for example §1§ and §42§); never imitate these prefixes in replies because only injected tag numbers are valid ctx_reduce handles.
21653
+ In primary sessions, NEVER narrate ctx_reduce; call it silently after extracting a spent output because it marks content discardable and QUEUES release rather than deleting immediately. The last ${protectedTags} tags stay protected until they age out. Use drop grammar "3-5", "1,2,9", or "1-5,8,12-15".
21654
+ ${CTX_NOTE_GUIDANCE}
21655
+ ${memoryGuidanceBlock(memoryEnabled)}${LIGHT_SEARCH_RECOVERY}
21656
+ ${TOOL_HISTORY_GUIDANCE}
21657
+ For primary ctx_reduce choices, NEVER blanket-drop a large range because mixed-value evidence may be lost: inspect every tag first. Drop only analyzed reads, searches, diagnostics, or build/test outputs after use. NEVER drop user directives or assistant prose unless exceptionally large; keep requirements, constraints, unresolved errors or decisions, exact wording, raw evidence, and active files or work. Only extracted pasted user payloads may go.
21658
+ Consider small targeted drops after acted-on reads or searches, completed logical steps, before context switches, and before the turn ends; this keeps the working set tidy without changing task scope.`;
21659
+ var BASE_INTRO_NO_REDUCE_LIGHT = (memoryEnabled) => `${CTX_NOTE_GUIDANCE}
21660
+ ${memoryGuidanceBlock(memoryEnabled)}${LIGHT_SEARCH_RECOVERY}
21661
+ ${TOOL_HISTORY_GUIDANCE}`;
21778
21662
  var GENERIC_SECTION = `
21779
21663
  ### Reduction Triggers
21780
21664
  - After reading files or search results you already acted on — drop raw outputs.
@@ -21791,21 +21675,27 @@ var GENERIC_SECTION = `
21791
21675
  - Your current task requirements and constraints.
21792
21676
  - Recent errors and unresolved decisions.
21793
21677
  - Active work context and files being edited.`;
21678
+ var SMART_NOTE_GUIDANCE_LIGHT = `
21679
+ surface_condition creates a smart note checked nightly against external signals on ctx_note write.`;
21794
21680
  var TEMPORAL_AWARENESS_GUIDANCE = `
21795
21681
  **Temporal awareness**: User messages may be preceded by HTML comments like \`<!-- +12m -->\`, \`<!-- +2h 15m -->\`, or \`<!-- +3d 4h -->\` indicating time elapsed since the previous message's completion. Compartments in \`<session-history>\` carry \`start-date\` and \`end-date\` attributes (YYYY-MM-DD) showing real-time boundaries. Use these when reasoning about workflow pacing, log durations, build times, or how long ago something happened.`;
21796
21682
  var SUBAGENT_REDUCE_INTRO = (protectedTags) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
21797
21683
  Use \`ctx_reduce\` to drop tool outputs you have already finished with, keeping your working context lean. Syntax: "3-5", "1,2,9", or "1-5,8,12-15". The last ${protectedTags} tags are protected.
21798
21684
  Drop silently — do not narrate it. NEVER drop large ranges blindly (e.g., "1-50"); review each tag first. Do not drop user or assistant text messages — only large tool outputs are worth dropping.
21799
21685
  Older tool calls may show \`[dropped §N§]\` sentinels; that is normal context management, not a pattern to copy. ALWAYS make fresh real tool calls when you need data again; never fabricate or inline tool output.`;
21686
+ var SUBAGENT_REDUCE_INTRO_LIGHT = (protectedTags) => `In bounded subagent sessions, the system tags messages and tool outputs as §N§; use only those IDs in ctx_reduce drop ranges such as "3-5", "1,2,9", or "1-5,8,12-15". The last ${protectedTags} tags stay protected.
21687
+ When dropping, do it silently and NEVER choose a large range before reviewing every tag; drop only finished large tool outputs, never user or assistant messages.
21688
+ If older calls show [dropped §N§], never copy that system sentinel because it is not reply syntax; make a fresh real tool call and never fabricate or inline output.`;
21800
21689
  var CAVEMAN_COMPRESSION_WARNING = `
21801
21690
  **BEWARE**: History compression is on; older user AND assistant text — including your own earlier responses — has been deterministically rewritten in a terse caveman style (dropped articles, missing auxiliaries, \`//\` instead of connectives like \`because\`). This is automatic context compression that runs after the fact, not your actual prior wording or the user's. **DO NOT mimic this style in new turns.** Write fresh responses in normal prose. If you notice your output drifting into caveman cadence, that drift is in-context-learning bleeding from the compressed history — consciously revert to full sentences.`;
21802
- function buildMagicContextSection(_agent, protectedTags, ctxReduceCallable = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true) {
21691
+ function buildMagicContextSection(_agent, protectedTags, ctxReduceCallable = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true, preset = "full", primaryOverride) {
21803
21692
  if (subagentMode) {
21693
+ const intro = preset === "light" ? SUBAGENT_REDUCE_INTRO_LIGHT(protectedTags) : SUBAGENT_REDUCE_INTRO(protectedTags);
21804
21694
  return `## Magic Context
21805
21695
 
21806
- ${SUBAGENT_REDUCE_INTRO(protectedTags)}`;
21696
+ ${intro}`;
21807
21697
  }
21808
- const smartNoteGuidance = dreamerEnabled ? `
21698
+ const smartNoteGuidance = dreamerEnabled ? preset === "light" ? SMART_NOTE_GUIDANCE_LIGHT : `
21809
21699
  When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
21810
21700
  The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
21811
21701
  Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
@@ -21815,13 +21705,32 @@ Example: \`ctx_note(action="write", content="Implement X because Y", surface_con
21815
21705
  const languageGuidance = languageDirective ? `
21816
21706
 
21817
21707
  ${languageDirective}` : "";
21708
+ if (primaryOverride !== undefined) {
21709
+ return `${primaryOverride}${temporalGuidance}${cavemanWarning}${languageGuidance}`;
21710
+ }
21818
21711
  if (!ctxReduceCallable) {
21712
+ if (preset === "light") {
21713
+ return `## Magic Context
21714
+
21715
+ ${LONG_TERM_PARTNER_FRAME}
21716
+ ${PARTNER_FRAME_CLOSER_NO_REDUCE_LIGHT}
21717
+
21718
+ ${BASE_INTRO_NO_REDUCE_LIGHT(memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}${languageGuidance}`;
21719
+ }
21819
21720
  return `## Magic Context
21820
21721
 
21821
21722
  ${LONG_TERM_PARTNER_FRAME}
21822
21723
  ${PARTNER_FRAME_CLOSER_NO_REDUCE}
21823
21724
 
21824
21725
  ${BASE_INTRO_NO_REDUCE(memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}${languageGuidance}`;
21726
+ }
21727
+ if (preset === "light") {
21728
+ return `## Magic Context
21729
+
21730
+ ${LONG_TERM_PARTNER_FRAME}
21731
+ ${PARTNER_FRAME_CLOSER_REDUCE_LIGHT}
21732
+
21733
+ ${BASE_INTRO_LIGHT(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}${languageGuidance}`;
21825
21734
  }
21826
21735
  return `## Magic Context
21827
21736
 
@@ -21842,7 +21751,7 @@ function buildMagicContextBlock(opts) {
21842
21751
  const includeGuidance = (opts.includeGuidance ?? true) && !existing.includes(MAGIC_CONTEXT_MARKER);
21843
21752
  if (!includeGuidance)
21844
21753
  return null;
21845
- return buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceCallable ?? true, opts.dreamerEnabled ?? false, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false, false, opts.language, opts.memoryEnabled !== false);
21754
+ return buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceCallable ?? true, opts.dreamerEnabled ?? false, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false, false, opts.language, opts.memoryEnabled !== false, opts.promptSurfacePreset, opts.primaryGuidanceOverride);
21846
21755
  }
21847
21756
  var DATE_PATTERN = /Today's date: .+/;
21848
21757
  function processSystemPromptForCache(args) {
@@ -21870,7 +21779,7 @@ function processSystemPromptForCache(args) {
21870
21779
  sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
21871
21780
  }
21872
21781
  }
21873
- const currentHash = createHash8("md5").update(frozenPrompt).digest("hex");
21782
+ const currentHash = createHash10("md5").update(promptSurfaceHashMaterial(frozenPrompt, args.promptSurfacePreset)).digest("hex");
21874
21783
  const hashChanged = !isFirstHash && currentHash !== previousHash;
21875
21784
  if (hashChanged) {
21876
21785
  sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
@@ -27539,7 +27448,7 @@ ${c.content}
27539
27448
  // package.json
27540
27449
  var package_default = {
27541
27450
  name: "@cortexkit/pi-magic-context",
27542
- version: "0.34.1",
27451
+ version: "0.35.0",
27543
27452
  type: "module",
27544
27453
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
27545
27454
  main: "dist/index.js",
@@ -28995,9 +28904,15 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28995
28904
  const projectDir = process.cwd();
28996
28905
  const seenDreamerProjectIdentities = new Set;
28997
28906
  ensureConfigLocationsMigrated(projectDir);
28998
- const { config, warnings, loadedFromPaths } = loadPiConfig({
28907
+ const { config, warnings, loadedFromPaths, registrationPromptSurface } = loadPiConfig({
28999
28908
  cwd: projectDir
29000
28909
  });
28910
+ const promptSurfaceRuntime = createPromptSurfaceRuntime({
28911
+ harness: "pi",
28912
+ directory: projectDir,
28913
+ warn: (message) => warn(`config: ${message}`)
28914
+ });
28915
+ const promptSurfaceGuidanceEpochs = createPromptSurfaceGuidanceEpochCache(promptSurfaceRuntime);
29001
28916
  const projectIdentity = resolveProjectIdentityForSession(projectDir, config.allow_home_project) ?? "";
29002
28917
  if (projectIdentity)
29003
28918
  seenDreamerProjectIdentities.add(projectIdentity);
@@ -29143,7 +29058,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29143
29058
  dreamerEnabled: isDreamerRunnable(config),
29144
29059
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
29145
29060
  todowriteEnabled,
29146
- compactionOff
29061
+ compactionOff,
29062
+ promptSurface: registrationPromptSurface,
29063
+ promptSurfaceRuntime
29147
29064
  });
29148
29065
  info(compactionOff ? todowriteEnabled ? "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, todowrite; registered /todos (ctx_reduce unavailable in compaction-off mode)" : "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand (ctx_reduce unavailable in compaction-off mode; todowrite disabled)" : todowriteEnabled ? "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, todowrite, ctx_reduce; registered /todos" : "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, ctx_reduce (todowrite disabled)");
29149
29066
  pi.on("session_start", async (event, ctx) => {
@@ -29449,6 +29366,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29449
29366
  return;
29450
29367
  }
29451
29368
  const isCacheBusting = sessionId ? hasSystemPromptRefresh(sessionId) : true;
29369
+ const promptSurfaceModel = ctx.model;
29370
+ const promptSurfaceModelKey = typeof promptSurfaceModel?.provider === "string" && promptSurfaceModel.provider.length > 0 && typeof promptSurfaceModel.id === "string" && promptSurfaceModel.id.length > 0 ? `${promptSurfaceModel.provider}/${promptSurfaceModel.id}` : undefined;
29371
+ const promptSurface = sessionId ? promptSurfaceGuidanceEpochs.resolve(sessionId, effectiveConfig.prompt_surface, promptSurfaceModelKey) : promptSurfaceRuntime.resolveGuidance(effectiveConfig.prompt_surface, promptSurfaceModelKey);
29452
29372
  const block = buildMagicContextBlock({
29453
29373
  db,
29454
29374
  cwd: currentProject.projectDir,
@@ -29461,6 +29381,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29461
29381
  temporalAwarenessEnabled: effectiveConfig.temporal_awareness ?? false,
29462
29382
  cavemanTextCompressionEnabled: effectiveConfig.caveman_text_compression?.enabled === true,
29463
29383
  language: effectiveConfig.language,
29384
+ promptSurfacePreset: promptSurface.preset,
29385
+ primaryGuidanceOverride: promptSurface.primaryOverride,
29464
29386
  userMemoriesEnabled: userMemoryCollectionEnabled(effectiveConfig.dreamer),
29465
29387
  isCacheBusting,
29466
29388
  existingSystemPrompt: event.systemPrompt
@@ -29477,7 +29399,8 @@ ${block}` : event.systemPrompt;
29477
29399
  db,
29478
29400
  sessionId,
29479
29401
  systemPrompt: composedPrompt,
29480
- isCacheBusting
29402
+ isCacheBusting,
29403
+ promptSurfacePreset: promptSurface.preset
29481
29404
  });
29482
29405
  if (result.hashChanged) {
29483
29406
  signalPiHistoryRefresh(sessionId);
@@ -29691,6 +29614,7 @@ ${block}` : event.systemPrompt;
29691
29614
  const sessionId = typeof sm?.getSessionId === "function" ? sm.getSessionId() : undefined;
29692
29615
  if (typeof sessionId === "string" && sessionId.length > 0) {
29693
29616
  clearPiSystemPromptSession(sessionId);
29617
+ promptSurfaceGuidanceEpochs.clear(sessionId);
29694
29618
  clearContextHandlerSession(sessionId);
29695
29619
  }
29696
29620
  } catch {}
@@ -29702,6 +29626,7 @@ ${block}` : event.systemPrompt;
29702
29626
  const outgoingSessionId = typeof sm?.getSessionId === "function" ? sm.getSessionId() : undefined;
29703
29627
  if (typeof outgoingSessionId === "string" && outgoingSessionId.length > 0) {
29704
29628
  clearPiSystemPromptSession(outgoingSessionId);
29629
+ promptSurfaceGuidanceEpochs.clear(outgoingSessionId);
29705
29630
  clearContextHandlerSession(outgoingSessionId);
29706
29631
  }
29707
29632
  } catch {}