@cortexkit/pi-magic-context 0.34.2 → 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,
@@ -298,6 +308,7 @@ import {
298
308
  resolveCompactionModeRecord,
299
309
  resolveGitTopLevel,
300
310
  resolveLimit,
311
+ resolveModelConfigOrDefault,
301
312
  resolveModelRefForPi,
302
313
  resolveMuralWire,
303
314
  resolvePiStableId,
@@ -331,7 +342,6 @@ import {
331
342
  setStoragePrivatePermissionEnforcement,
332
343
  setTodoSnapshot,
333
344
  sourceNameForMemory,
334
- stableStringify,
335
345
  stripMemoryMuralBlock,
336
346
  stripPersistedAssistantText,
337
347
  stripTagPrefix,
@@ -363,7 +373,7 @@ import {
363
373
  withMigrationLanguageDirective,
364
374
  withRawMessageProvider,
365
375
  withRawSessionMessageCache
366
- } from "./index-fw42dv6t.js";
376
+ } from "./index-xbk9epe8.js";
367
377
  import {
368
378
  pushNotification
369
379
  } from "./index-hw34xmzk.js";
@@ -1488,16 +1498,7 @@ function resolveCacheTtl(cacheTtl, modelKey) {
1488
1498
  if (typeof cacheTtl === "string") {
1489
1499
  return cacheTtl;
1490
1500
  }
1491
- if (modelKey && typeof cacheTtl[modelKey] === "string") {
1492
- return cacheTtl[modelKey];
1493
- }
1494
- if (modelKey) {
1495
- const bareModelId = modelKey.split("/").slice(1).join("/");
1496
- if (bareModelId && typeof cacheTtl[bareModelId] === "string") {
1497
- return cacheTtl[bareModelId];
1498
- }
1499
- }
1500
- return cacheTtl.default ?? "5m";
1501
+ return resolveModelConfigOrDefault(cacheTtl, modelKey, cacheTtl.default ?? "5m");
1501
1502
  }
1502
1503
  var clampWarnSeen = new Set;
1503
1504
  function isFinitePositive(v) {
@@ -3579,7 +3580,7 @@ function openOpenCodeDb() {
3579
3580
  }
3580
3581
 
3581
3582
  // ../plugin/src/features/magic-context/dreamer/task-executor.ts
3582
- import { createHash as createHash6 } from "node:crypto";
3583
+ import { createHash as createHash8 } from "node:crypto";
3583
3584
  import { existsSync as existsSync7 } from "node:fs";
3584
3585
 
3585
3586
  // ../plugin/src/agents/dreamer.ts
@@ -4164,6 +4165,9 @@ function parseFields(json) {
4164
4165
  return {};
4165
4166
  }
4166
4167
 
4168
+ // ../plugin/src/features/magic-context/mural/compress-cues.ts
4169
+ import { createHash as createHash2 } from "node:crypto";
4170
+
4167
4171
  // ../plugin/src/features/magic-context/dreamer/storage-dream-state.ts
4168
4172
  var getDreamStateStatements = new WeakMap;
4169
4173
  var setDreamStateStatements = new WeakMap;
@@ -4388,6 +4392,64 @@ function assertManifestCoversExactly(ids, expectedIds, rootName) {
4388
4392
  }
4389
4393
  }
4390
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
+
4391
4453
  // ../plugin/src/features/magic-context/mural/compress-cues-prompt.ts
4392
4454
  var CUE_BUDGET_HIGH = 90;
4393
4455
  var CUE_BUDGET_LOW = 50;
@@ -4659,7 +4721,7 @@ async function compressOneChunk(args, chunk, sliceMs, signal) {
4659
4721
  return text;
4660
4722
  }
4661
4723
  });
4662
- return applyCues(args, chunk, run.validated);
4724
+ return args.moduleRoute ? await applyCuesThroughModule(args, chunk, run.validated, signal) : applyCues(args, chunk, run.validated);
4663
4725
  } catch (error) {
4664
4726
  const desc = describeError(error);
4665
4727
  log(`[dreamer] compress-cues chunk failed: ${desc.brief}`, desc.stackHead ? { stackHead: desc.stackHead } : undefined);
@@ -4717,6 +4779,111 @@ function applyCues(args, chunk, manifestText) {
4717
4779
  });
4718
4780
  return { compressed, skipped };
4719
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
+ }
4720
4887
 
4721
4888
  // ../plugin/src/features/magic-context/dreamer/task-prompts.ts
4722
4889
  var PROJECT_MEMORY_TAXONOMY = `## Memory taxonomy (5 categories)
@@ -5231,7 +5398,7 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
5231
5398
  }
5232
5399
 
5233
5400
  // ../plugin/src/features/magic-context/dreamer/classify.ts
5234
- import { createHash as createHash2 } from "node:crypto";
5401
+ import { createHash as createHash3 } from "node:crypto";
5235
5402
 
5236
5403
  // ../plugin/src/plugin/rust-tool-backends.ts
5237
5404
  function isRustAuthorityDrainingError(error) {
@@ -5499,64 +5666,6 @@ function parseClassifyManifest(text) {
5499
5666
  return out;
5500
5667
  }
5501
5668
 
5502
- // ../plugin/src/features/magic-context/dreamer/module-apply.ts
5503
- class DreamerModuleFailureError extends Error {
5504
- transient = true;
5505
- constructor(operation, cause) {
5506
- super(`Rust dreamer ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`);
5507
- this.name = "DreamerModuleFailureError";
5508
- this.cause = cause;
5509
- }
5510
- }
5511
- async function resolveDreamerModuleRoute(args) {
5512
- const transport = args.transformMode === "ts" ? undefined : args.moduleClient;
5513
- if (!transport?.authorityStatus)
5514
- return;
5515
- const contextStoreUuid = getContextStoreUuid(args.db);
5516
- if (!contextStoreUuid)
5517
- throw new Error("Rust dreamer requires a context store identity");
5518
- const result = await transport.authorityStatus({
5519
- context_store_uuid: contextStoreUuid,
5520
- project: args.projectIdentity,
5521
- projectRoot: args.projectRoot,
5522
- domain: "memories"
5523
- });
5524
- if (result.authority?.state !== "MODULE")
5525
- return;
5526
- const generation = result.authority.generation;
5527
- if (typeof generation !== "number")
5528
- throw new Error("Rust authority status omitted generation");
5529
- return {
5530
- moduleClient: transport,
5531
- moduleSessionId: args.projectIdentity,
5532
- moduleProjectRoot: args.projectRoot,
5533
- moduleContextStoreUuid: contextStoreUuid,
5534
- moduleAuthorityGeneration: generation,
5535
- moduleCommandId: args.commandId
5536
- };
5537
- }
5538
- function getModuleMemoryIdentities(db, projectIdentity, contextIds) {
5539
- if (contextIds.length === 0)
5540
- return new Map;
5541
- const placeholders = contextIds.map(() => "?").join(", ");
5542
- const rows = db.prepare(`SELECT identity.context_row_id, identity.module_row_id, live.normalized_hash
5543
- FROM mirror_identity identity
5544
- LEFT JOIN mirror_live_memory_rows live
5545
- ON live.module_project = identity.module_project
5546
- AND live.module_row_id = identity.module_row_id
5547
- WHERE identity.domain = 'memories' AND identity.module_project = ?
5548
- AND identity.context_row_id IN (${placeholders})`).all(projectIdentity, ...contextIds);
5549
- return new Map(rows.flatMap((row) => Number.isInteger(row.context_row_id) && Number.isInteger(row.module_row_id) && typeof row.normalized_hash === "string" ? [
5550
- [
5551
- row.context_row_id,
5552
- {
5553
- moduleId: row.module_row_id,
5554
- normalizedHash: row.normalized_hash
5555
- }
5556
- ]
5557
- ] : []));
5558
- }
5559
-
5560
5669
  // ../plugin/src/features/magic-context/dreamer/classify.ts
5561
5670
  var MIN_POOL_TO_CLASSIFY = 10;
5562
5671
  var FULL_POOL_CEILING = 100;
@@ -5793,7 +5902,7 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
5793
5902
  v: 1,
5794
5903
  session_id: args.moduleSessionId,
5795
5904
  task: "classify",
5796
- 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)}`,
5797
5906
  authority_generation: args.moduleAuthorityGeneration,
5798
5907
  payload: {
5799
5908
  prompt_body: prompt,
@@ -6431,7 +6540,7 @@ function throwIfAborted2(signal) {
6431
6540
  }
6432
6541
 
6433
6542
  // ../plugin/src/features/magic-context/smart-notes/compiler.ts
6434
- import { createHash as createHash3 } from "node:crypto";
6543
+ import { createHash as createHash4 } from "node:crypto";
6435
6544
 
6436
6545
  // ../plugin/src/features/magic-context/smart-notes/compiler-prompt.ts
6437
6546
  var SMART_NOTE_COMPILER_SYSTEM_PROMPT = `You are the Magic Context smart-note compiler for the magic-context system.
@@ -6855,7 +6964,7 @@ function manifestAdvisoryWarnings(code, manifest) {
6855
6964
  return warnings;
6856
6965
  }
6857
6966
  function hashCheck(surfaceCondition, compiledCheck, manifest, checkCron) {
6858
- 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");
6859
6968
  }
6860
6969
  function extractJsonObject(output) {
6861
6970
  const fenced = output.match(/```(?:json)?\s*([\s\S]*?)```/i);
@@ -7535,7 +7644,7 @@ function enforceMaintainDocsProtectedRegions(args) {
7535
7644
  }
7536
7645
 
7537
7646
  // ../plugin/src/features/magic-context/dreamer/map-memories.ts
7538
- import { createHash as createHash4 } from "node:crypto";
7647
+ import { createHash as createHash5 } from "node:crypto";
7539
7648
 
7540
7649
  // ../plugin/src/features/magic-context/dreamer/map-memories-prompt.ts
7541
7650
  import { existsSync as existsSync6, statSync } from "node:fs";
@@ -7796,7 +7905,7 @@ async function applyBatchMappings(args, batch, manifestText) {
7796
7905
  memory_project: args.projectIdentity,
7797
7906
  context_store_uuid: args.moduleRoute.moduleContextStoreUuid,
7798
7907
  authority_generation: args.moduleRoute.moduleAuthorityGeneration,
7799
- 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)}`,
7800
7909
  rows
7801
7910
  }
7802
7911
  }
@@ -8854,7 +8963,69 @@ function insertDreamRun(db, run) {
8854
8963
  }
8855
8964
 
8856
8965
  // ../plugin/src/features/magic-context/dreamer/verify.ts
8857
- import { createHash as createHash5 } from "node:crypto";
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
+ }
8985
+ }
8986
+ function finiteTokenCount(value) {
8987
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
8988
+ }
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);
9028
+ }
8858
9029
 
8859
9030
  // ../plugin/src/features/magic-context/dreamer/verify-gate.ts
8860
9031
  import path5 from "node:path";
@@ -9047,6 +9218,7 @@ function parseVerifyManifest(text) {
9047
9218
 
9048
9219
  // ../plugin/src/features/magic-context/dreamer/verify.ts
9049
9220
  var VERIFY_BATCH_SIZE = 50;
9221
+ var IDENTICAL_PROVIDER_FAILURE_BATCH_LIMIT = 2;
9050
9222
  function closeBroadCycle(args, cycleStartAt) {
9051
9223
  if (!args.forceBroad || cycleStartAt === undefined)
9052
9224
  return;
@@ -9100,6 +9272,9 @@ async function runVerify(args) {
9100
9272
  }
9101
9273
  const abortController = new AbortController;
9102
9274
  const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, () => abortController.abort());
9275
+ let consecutiveProviderFailures = 0;
9276
+ let priorProviderFailureFingerprint = null;
9277
+ let lastProviderFailure = null;
9103
9278
  try {
9104
9279
  for (let i = 0;i < batches.length; i += 1) {
9105
9280
  const remainingMs = Math.max(0, args.deadline - Date.now());
@@ -9114,7 +9289,25 @@ async function runVerify(args) {
9114
9289
  result.remaining -= counts.verified + counts.updated + counts.archived;
9115
9290
  result.batches += 1;
9116
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
+ }
9117
9308
  }
9309
+ if (lastProviderFailure)
9310
+ throw lastProviderFailure;
9118
9311
  result.complete = result.remaining === 0;
9119
9312
  if (result.complete)
9120
9313
  closeBroadCycle(args, gate.broadCycleStartAt);
@@ -9172,7 +9365,14 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
9172
9365
  const text = extractLatestAssistantText(messages);
9173
9366
  if (!text)
9174
9367
  throw new Error("verify returned no output");
9175
- 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
+ }
9176
9376
  return text;
9177
9377
  }
9178
9378
  });
@@ -9180,11 +9380,12 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
9180
9380
  return await applyVerifyManifest(args, batch, run.validated);
9181
9381
  } catch (error) {
9182
9382
  const desc = describeError(error);
9183
- 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);
9184
9385
  recordInvocation4(args, startedAt, { status: "failed", error });
9185
9386
  if (error instanceof DreamerModuleFailureError || signal.aborted)
9186
9387
  throw error;
9187
- return { verified: 0, updated: 0, archived: 0 };
9388
+ return { verified: 0, updated: 0, archived: 0, providerFailure };
9188
9389
  } finally {
9189
9390
  if (agentSessionId && !shouldKeepSubagents()) {
9190
9391
  await args.client.session.delete({
@@ -9256,7 +9457,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
9256
9457
  memory_project: args.projectIdentity,
9257
9458
  context_store_uuid: args.moduleRoute.moduleContextStoreUuid,
9258
9459
  authority_generation: args.moduleRoute.moduleAuthorityGeneration,
9259
- 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)}`,
9260
9461
  rows
9261
9462
  }
9262
9463
  }
@@ -9500,12 +9701,6 @@ function createDreamTaskExecutor(deps) {
9500
9701
  recordRun("completed", null);
9501
9702
  return { status: "completed" };
9502
9703
  }
9503
- if (moduleRoute) {
9504
- const reason = "compress-cues parked: MODULE memory authority has no cue write facade";
9505
- log(`[dreamer] compress-cues: skipped (${reason})`);
9506
- recordRun("failed", reason);
9507
- return { status: "failed", transient: true, error: reason };
9508
- }
9509
9704
  const result = await runCompressCues({
9510
9705
  db,
9511
9706
  client: deps.client,
@@ -9517,6 +9712,7 @@ function createDreamTaskExecutor(deps) {
9517
9712
  deadline,
9518
9713
  model: config.model ?? deps.mural.model ?? deps.dreamerModel,
9519
9714
  fallbackModels: config.fallbackModels,
9715
+ moduleRoute,
9520
9716
  onProgress: (processed) => reportProgress(processed)
9521
9717
  });
9522
9718
  log(`[dreamer] compress-cues: compressed=${result.compressed} skipped=${result.skipped} chunks=${result.chunks} remaining=${result.remaining}`);
@@ -9798,7 +9994,7 @@ function parseFrictionGateVerdict(verdict) {
9798
9994
  }
9799
9995
  function computeRetrospectiveWindowKey(flagged) {
9800
9996
  const anchors = flagged.map((message) => `${message.sessionId}:${message.ts}`).sort().join("|");
9801
- return createHash6("sha256").update(anchors).digest("hex").slice(0, 32);
9997
+ return createHash8("sha256").update(anchors).digest("hex").slice(0, 32);
9802
9998
  }
9803
9999
  function renderFrictionWindow(messages, flaggedOrdinals, radius = 2) {
9804
10000
  const flagged = new Set(flaggedOrdinals);
@@ -12901,513 +13097,6 @@ function replayCavemanCompression(sessionId, db, targets, tags) {
12901
13097
  return replayed;
12902
13098
  }
12903
13099
 
12904
- // ../plugin/src/hooks/magic-context/read-session-true-raw-tokens.ts
12905
- var MAX_MESSAGE_CACHE_ENTRIES = 1e5;
12906
- var MAX_MESSAGE_CACHE_KEY_BYTES = 64 * 1024 * 1024;
12907
- var FNV1A_32_OFFSET = 2166136261;
12908
- var FNV1A_32_PRIME = 16777619;
12909
- var messageEstimateCache = new Map;
12910
- var messageEstimateCacheBytes = 0;
12911
- var EMPTY_BREAKDOWN = {
12912
- text: 0,
12913
- reasoning: 0,
12914
- toolInput: 0,
12915
- toolOutput: 0,
12916
- image: 0,
12917
- other: 0,
12918
- total: 0
12919
- };
12920
- function addBreakdown(target, kind, value) {
12921
- if (kind === "total")
12922
- return;
12923
- const safeValue = Number.isFinite(value) && value > 0 ? Math.round(value) : 0;
12924
- target[kind] += safeValue;
12925
- target.total += safeValue;
12926
- }
12927
- function estimateStructured(value) {
12928
- if (typeof value === "string")
12929
- return estimateTokens(value);
12930
- if (value === undefined || value === null)
12931
- return 0;
12932
- return estimateTokens(stableStringify(value));
12933
- }
12934
- function firstStringField(record, fields) {
12935
- for (const field of fields) {
12936
- const value = record[field];
12937
- if (typeof value === "string" && value.length > 0)
12938
- return value;
12939
- }
12940
- return null;
12941
- }
12942
- function stringValue(value) {
12943
- if (typeof value === "string")
12944
- return value;
12945
- if (value === undefined || value === null)
12946
- return "";
12947
- return stableStringify(value);
12948
- }
12949
- function textFromToolResultContent(content) {
12950
- if (typeof content === "string")
12951
- return content;
12952
- if (Array.isArray(content)) {
12953
- const pieces = [];
12954
- for (const entry of content) {
12955
- if (typeof entry === "string") {
12956
- pieces.push(entry);
12957
- } else if (isRecord(entry)) {
12958
- const text = firstStringField(entry, ["text", "content", "value"]);
12959
- pieces.push(text ?? stableStringify(entry));
12960
- } else if (entry !== null && entry !== undefined) {
12961
- pieces.push(String(entry));
12962
- }
12963
- }
12964
- return pieces.join(`
12965
- `);
12966
- }
12967
- return stringValue(content);
12968
- }
12969
- function looksImageLike(part) {
12970
- const type = typeof part.type === "string" ? part.type.toLowerCase() : "";
12971
- const mime = typeof part.mime === "string" ? part.mime.toLowerCase() : "";
12972
- const mediaType = typeof part.mediaType === "string" ? part.mediaType.toLowerCase() : "";
12973
- return type.includes("image") || mime.startsWith("image/") || mediaType.startsWith("image/") || part.image_url !== undefined || part.imageUrl !== undefined || part.image !== undefined;
12974
- }
12975
- function defaultImageTokenHeuristic(part) {
12976
- if (isRecord(part)) {
12977
- const width = part.width;
12978
- const height = part.height;
12979
- if (typeof width === "number" && typeof height === "number" && width > 0 && height > 0) {
12980
- return Math.max(256, Math.min(4096, Math.ceil(width * height / 750)));
12981
- }
12982
- }
12983
- return 1024;
12984
- }
12985
- function partType(part) {
12986
- return typeof part.type === "string" ? part.type : "";
12987
- }
12988
- function hasOwn(record, key) {
12989
- return Object.hasOwn(record, key);
12990
- }
12991
- function recursiveByteLength(value) {
12992
- if (value === null || value === undefined)
12993
- return 0;
12994
- if (typeof value === "string")
12995
- return value.length;
12996
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
12997
- return String(value).length;
12998
- }
12999
- if (Array.isArray(value)) {
13000
- return value.reduce((sum, item) => sum + recursiveByteLength(item), value.length);
13001
- }
13002
- if (isRecord(value)) {
13003
- let total = Object.keys(value).length;
13004
- for (const [key, child] of Object.entries(value)) {
13005
- total += key.length + recursiveByteLength(child);
13006
- }
13007
- return total;
13008
- }
13009
- return String(value).length;
13010
- }
13011
- function updateFnv1a32(hash, text) {
13012
- let next = hash;
13013
- for (let index = 0;index < text.length; index += 1) {
13014
- next ^= text.charCodeAt(index);
13015
- next = Math.imul(next, FNV1A_32_PRIME) >>> 0;
13016
- }
13017
- return next;
13018
- }
13019
- function contentStringsHash(fields) {
13020
- let hash = FNV1A_32_OFFSET;
13021
- for (const field of fields) {
13022
- hash = updateFnv1a32(hash, `${field.length}:`);
13023
- hash = updateFnv1a32(hash, field);
13024
- hash = updateFnv1a32(hash, "\x00");
13025
- }
13026
- return hash.toString(16).padStart(8, "0");
13027
- }
13028
- function rawPartVersion(part) {
13029
- return part.__magicContextPartUpdatedAt ?? part.updated_at ?? part.updatedAt ?? part.version ?? part.revision ?? "";
13030
- }
13031
- function callIdFromPart(part) {
13032
- const direct = firstStringField(part, ["callID", "callId", "toolCallId", "tool_call_id", "id"]);
13033
- if (direct)
13034
- return direct;
13035
- const state2 = isRecord(part.state) ? part.state : null;
13036
- return state2 ? firstStringField(state2, ["callID", "callId", "toolCallId", "tool_call_id", "id"]) ?? "" : "";
13037
- }
13038
- function toolSignalFromPart(part) {
13039
- if (!isRecord(part))
13040
- return null;
13041
- const type = partType(part);
13042
- const state2 = isRecord(part.state) ? part.state : null;
13043
- const callId = callIdFromPart(part);
13044
- if (!callId && type !== "tool")
13045
- return null;
13046
- if (type === "tool") {
13047
- const hasInput = state2 !== null && hasOwn(state2, "input");
13048
- const outputKey = state2 ? hasOwn(state2, "output") ? "output" : hasOwn(state2, "error") ? "error" : hasOwn(state2, "result") ? "result" : null : null;
13049
- const hasOutput = outputKey !== null;
13050
- const outputValue = outputKey && state2 ? state2[outputKey] : undefined;
13051
- const providerExecuted = part.providerExecuted === true;
13052
- const openInvocation = !providerExecuted && !hasOutput;
13053
- return {
13054
- callId,
13055
- hasInput: hasInput || openInvocation,
13056
- hasOutput,
13057
- inputText: hasInput && state2 ? stringValue(state2.input) : "",
13058
- outputText: hasOutput ? stringValue(outputValue) : ""
13059
- };
13060
- }
13061
- if (type === "tool-invocation") {
13062
- const args = part.args ?? part.input;
13063
- return {
13064
- callId,
13065
- hasInput: args !== undefined,
13066
- hasOutput: false,
13067
- inputText: args !== undefined ? stringValue(args) : "",
13068
- outputText: ""
13069
- };
13070
- }
13071
- if (type === "tool_use") {
13072
- const input = part.input;
13073
- return {
13074
- callId,
13075
- hasInput: input !== undefined,
13076
- hasOutput: false,
13077
- inputText: input !== undefined ? stringValue(input) : "",
13078
- outputText: ""
13079
- };
13080
- }
13081
- if (type === "tool_result") {
13082
- const content = part.content ?? part.output ?? part.result;
13083
- return {
13084
- callId,
13085
- hasInput: false,
13086
- hasOutput: content !== undefined,
13087
- inputText: "",
13088
- outputText: content !== undefined ? textFromToolResultContent(content) : ""
13089
- };
13090
- }
13091
- return null;
13092
- }
13093
- function partCheapFingerprint(part) {
13094
- if (!isRecord(part))
13095
- return `${typeof part}:${recursiveByteLength(part)}`;
13096
- const version = rawPartVersion(part);
13097
- const type = typeof part.type === "string" ? part.type : "";
13098
- return `${type}:${String(version)}:${recursiveByteLength(part)}`;
13099
- }
13100
- function messageCacheKey(message, options) {
13101
- const namespace = "cacheNamespace" in options ? options.cacheNamespace : "estimate";
13102
- const cheapFingerprint = message.parts.map(partCheapFingerprint).join("|");
13103
- return [
13104
- namespace,
13105
- options.providerShapeVersion,
13106
- message.id || `ordinal:${message.ordinal}`,
13107
- message.role,
13108
- message.parts.length,
13109
- cheapFingerprint
13110
- ].join("\x00");
13111
- }
13112
- function setCachedEstimate(key, breakdown) {
13113
- const keyEstimateBytes = key.length * 2 + 64;
13114
- const existing = messageEstimateCache.get(key);
13115
- if (existing)
13116
- messageEstimateCacheBytes -= existing.keyEstimateBytes;
13117
- messageEstimateCache.set(key, { breakdown, keyEstimateBytes });
13118
- messageEstimateCacheBytes += keyEstimateBytes;
13119
- while (messageEstimateCache.size > MAX_MESSAGE_CACHE_ENTRIES || messageEstimateCacheBytes > MAX_MESSAGE_CACHE_KEY_BYTES) {
13120
- const first = messageEstimateCache.keys().next().value;
13121
- if (typeof first !== "string")
13122
- break;
13123
- const removed = messageEstimateCache.get(first);
13124
- if (removed)
13125
- messageEstimateCacheBytes -= removed.keyEstimateBytes;
13126
- messageEstimateCache.delete(first);
13127
- }
13128
- }
13129
- function cloneBreakdown(value) {
13130
- return { ...value };
13131
- }
13132
- function estimateNonToolPart(part, options, breakdown) {
13133
- if (!isRecord(part)) {
13134
- if (part !== null && part !== undefined)
13135
- addBreakdown(breakdown, "other", estimateStructured(part));
13136
- return true;
13137
- }
13138
- const type = partType(part);
13139
- if (type === "step-start" || type === "step-finish" || type === "meta" && Object.keys(part).length <= 1) {
13140
- return true;
13141
- }
13142
- if (type === "text") {
13143
- const text = firstStringField(part, ["text", "content"]);
13144
- if (text)
13145
- addBreakdown(breakdown, "text", estimateTokens(text));
13146
- return true;
13147
- }
13148
- if (type === "reasoning" || type === "thinking" || type === "redacted_thinking") {
13149
- const text = firstStringField(part, ["thinking", "text", "content", "reasoning"]);
13150
- if (text) {
13151
- addBreakdown(breakdown, "reasoning", estimateTokens(text));
13152
- } else {
13153
- addBreakdown(breakdown, "other", estimateStructured(part));
13154
- }
13155
- return true;
13156
- }
13157
- const reasoningText = firstStringField(part, ["thinking", "reasoning"]);
13158
- if (reasoningText && type.length === 0) {
13159
- addBreakdown(breakdown, "reasoning", estimateTokens(reasoningText));
13160
- return true;
13161
- }
13162
- if (looksImageLike(part)) {
13163
- addBreakdown(breakdown, "image", options.imageTokenHeuristic?.(part) ?? defaultImageTokenHeuristic(part));
13164
- const altText = firstStringField(part, ["alt", "text", "description"]);
13165
- if (altText)
13166
- addBreakdown(breakdown, "text", estimateTokens(altText));
13167
- return true;
13168
- }
13169
- if (type.includes("file") || type === "source") {
13170
- const content = firstStringField(part, ["content", "text", "source"]);
13171
- if (content)
13172
- addBreakdown(breakdown, "text", estimateTokens(content));
13173
- else
13174
- addBreakdown(breakdown, "other", estimateStructured(part));
13175
- return true;
13176
- }
13177
- return false;
13178
- }
13179
- function estimateTrueRawMessageTokens(message, options) {
13180
- const breakdown = cloneBreakdown(EMPTY_BREAKDOWN);
13181
- const countedInput = new Set;
13182
- const countedOutput = new Set;
13183
- let ordinalToolIndex = 0;
13184
- for (const part of message.parts) {
13185
- const signal = toolSignalFromPart(part);
13186
- if (signal) {
13187
- const localKey = `${signal.callId || "tool"}:${message.ordinal}:${ordinalToolIndex}`;
13188
- ordinalToolIndex += 1;
13189
- if (signal.hasInput) {
13190
- const key = `${signal.callId}:input:${message.ordinal}`;
13191
- if (!countedInput.has(key)) {
13192
- countedInput.add(key);
13193
- addBreakdown(breakdown, "toolInput", estimateTokens(signal.inputText));
13194
- }
13195
- }
13196
- if (signal.hasOutput) {
13197
- const key = `${signal.callId}:output:${message.ordinal}:${localKey}`;
13198
- if (!countedOutput.has(key)) {
13199
- countedOutput.add(key);
13200
- addBreakdown(breakdown, "toolOutput", estimateTokens(signal.outputText));
13201
- }
13202
- }
13203
- continue;
13204
- }
13205
- if (!estimateNonToolPart(part, options, breakdown)) {
13206
- addBreakdown(breakdown, "other", estimateStructured(part));
13207
- }
13208
- }
13209
- return breakdown;
13210
- }
13211
- function buildToolArcs(messages) {
13212
- const openQueues = new Map;
13213
- const arcs = [];
13214
- for (const message of messages) {
13215
- for (const part of message.parts) {
13216
- const signal = toolSignalFromPart(part);
13217
- if (!signal || signal.callId.length === 0)
13218
- continue;
13219
- if (signal.hasInput && signal.hasOutput) {
13220
- arcs.push({
13221
- callId: signal.callId,
13222
- invOrdinal: message.ordinal,
13223
- resOrdinal: message.ordinal
13224
- });
13225
- continue;
13226
- }
13227
- if (signal.hasInput) {
13228
- const queue = openQueues.get(signal.callId) ?? [];
13229
- queue.push(message.ordinal);
13230
- openQueues.set(signal.callId, queue);
13231
- continue;
13232
- }
13233
- if (signal.hasOutput) {
13234
- const queue = openQueues.get(signal.callId) ?? [];
13235
- const invOrdinal = queue.shift();
13236
- if (queue.length === 0)
13237
- openQueues.delete(signal.callId);
13238
- else
13239
- openQueues.set(signal.callId, queue);
13240
- if (invOrdinal !== undefined) {
13241
- arcs.push({ callId: signal.callId, invOrdinal, resOrdinal: message.ordinal });
13242
- }
13243
- }
13244
- }
13245
- }
13246
- for (const [callId, queue] of openQueues) {
13247
- for (const invOrdinal of queue) {
13248
- arcs.push({ callId, invOrdinal, resOrdinal: null });
13249
- }
13250
- }
13251
- return arcs.sort((a, b) => a.invOrdinal - b.invOrdinal || (a.resOrdinal ?? Number.MAX_SAFE_INTEGER) - (b.resOrdinal ?? Number.MAX_SAFE_INTEGER));
13252
- }
13253
- function fenceBoundaryForToolArcs(candidate, arcs, lastCompartmentEndOrdinal, recentOpenArcCutoff) {
13254
- let boundary = candidate;
13255
- for (const arc of arcs) {
13256
- if (arc.resOrdinal !== null) {
13257
- if (arc.invOrdinal < boundary && boundary <= arc.resOrdinal) {
13258
- boundary = arc.resOrdinal + 1;
13259
- }
13260
- continue;
13261
- }
13262
- if (arc.invOrdinal < recentOpenArcCutoff)
13263
- continue;
13264
- if (arc.invOrdinal >= lastCompartmentEndOrdinal + 1 && arc.invOrdinal < boundary) {
13265
- return arc.invOrdinal;
13266
- }
13267
- if (arc.invOrdinal >= boundary) {
13268
- return arc.invOrdinal;
13269
- }
13270
- }
13271
- return boundary;
13272
- }
13273
- function tokenForMessage(message, options) {
13274
- const key = messageCacheKey(message, options);
13275
- const cached = messageEstimateCache.get(key);
13276
- if (cached)
13277
- return cloneBreakdown(cached.breakdown);
13278
- const breakdown = estimateTrueRawMessageTokens(message, options);
13279
- setCachedEstimate(key, breakdown);
13280
- return cloneBreakdown(breakdown);
13281
- }
13282
- function buildTrueRawTokenIndex(sessionId, messages, options) {
13283
- const ordered = [...messages].sort((a, b) => a.ordinal - b.ordinal);
13284
- const sliceCount = ordered.length;
13285
- const firstOrdinal = ordered.length > 0 ? ordered[0].ordinal : 1;
13286
- const terminalOrdinal = ordered.length > 0 ? ordered[ordered.length - 1].ordinal : 0;
13287
- const rawMessageCount = Math.max(sliceCount, terminalOrdinal, options.absoluteMessageCount ?? sliceCount);
13288
- const ordinalSpan = terminalOrdinal >= firstOrdinal ? terminalOrdinal - firstOrdinal + 1 : 0;
13289
- const tokensByOrdinal = new Map;
13290
- const idsByOrdinal = new Map;
13291
- const prefix = new Array(ordinalSpan + 1).fill(0);
13292
- for (const message of ordered) {
13293
- const stored = options.storedTotalForMessage?.(message);
13294
- const total = stored !== undefined && stored !== null ? stored : tokenForMessage(message, options).total;
13295
- tokensByOrdinal.set(message.ordinal, total);
13296
- idsByOrdinal.set(message.ordinal, message.id);
13297
- const relative = message.ordinal - firstOrdinal + 1;
13298
- if (relative >= 1 && relative <= ordinalSpan) {
13299
- prefix[relative] = total;
13300
- }
13301
- }
13302
- for (let k = 1;k <= ordinalSpan; k += 1) {
13303
- prefix[k] += prefix[k - 1];
13304
- }
13305
- const ordinalToIndex = (ordinal) => Math.max(0, Math.min(ordinalSpan, ordinal - firstOrdinal));
13306
- return {
13307
- sessionId,
13308
- providerShapeVersion: options.providerShapeVersion,
13309
- rawMessageCount,
13310
- tokenForOrdinal(ordinal) {
13311
- return tokensByOrdinal.get(ordinal) ?? 0;
13312
- },
13313
- messageIdAtOrdinal(ordinal) {
13314
- return idsByOrdinal.get(ordinal) ?? null;
13315
- },
13316
- suffixTokensFromOrdinal(ordinal) {
13317
- if (ordinal <= firstOrdinal)
13318
- return prefix[ordinalSpan];
13319
- if (ordinal > terminalOrdinal)
13320
- return 0;
13321
- return prefix[ordinalSpan] - prefix[ordinalToIndex(ordinal)];
13322
- },
13323
- rangeTokens(startInclusive, endExclusive) {
13324
- const start = Math.max(firstOrdinal, startInclusive);
13325
- const end = Math.max(start, Math.min(terminalOrdinal + 1, endExclusive));
13326
- return prefix[end - firstOrdinal] - prefix[start - firstOrdinal];
13327
- },
13328
- findSuffixStartForTokens(tokens) {
13329
- if (!Number.isFinite(tokens) || tokens <= 0)
13330
- return terminalOrdinal + 1;
13331
- const target = Math.max(0, Math.floor(tokens));
13332
- const total = prefix[ordinalSpan];
13333
- if (total < target)
13334
- return firstOrdinal;
13335
- const cut = total - target;
13336
- let lo = 0;
13337
- let hi = ordinalSpan;
13338
- let best = 0;
13339
- while (lo <= hi) {
13340
- const mid = lo + hi >> 1;
13341
- if (prefix[mid] <= cut) {
13342
- best = mid;
13343
- lo = mid + 1;
13344
- } else {
13345
- hi = mid - 1;
13346
- }
13347
- }
13348
- return firstOrdinal + best;
13349
- },
13350
- findHeadEndForCap(startInclusive, endExclusive, capTokens) {
13351
- const start = Math.max(firstOrdinal, Math.min(terminalOrdinal + 1, startInclusive));
13352
- const end = Math.max(start, Math.min(terminalOrdinal + 1, endExclusive));
13353
- if (!Number.isFinite(capTokens) || capTokens <= 0)
13354
- return start;
13355
- const startIndex = start - firstOrdinal;
13356
- const endIndex = end - firstOrdinal;
13357
- const cut = prefix[startIndex] + Math.floor(capTokens);
13358
- let lo = startIndex + 1;
13359
- let hi = endIndex;
13360
- let bestEndIndex = startIndex;
13361
- while (lo <= hi) {
13362
- const mid = lo + hi >> 1;
13363
- if (prefix[mid] <= cut) {
13364
- bestEndIndex = mid;
13365
- lo = mid + 1;
13366
- } else {
13367
- hi = mid - 1;
13368
- }
13369
- }
13370
- let bestEnd = firstOrdinal + bestEndIndex;
13371
- if (bestEnd === start && start < end)
13372
- bestEnd = start + 1;
13373
- return Math.min(bestEnd, end);
13374
- }
13375
- };
13376
- }
13377
- function partContentFingerprint(part) {
13378
- if (!isRecord(part))
13379
- return `${typeof part}:${recursiveByteLength(part)}`;
13380
- const tool = toolSignalFromPart(part);
13381
- if (tool) {
13382
- return contentStringsHash([tool.inputText, tool.outputText]);
13383
- }
13384
- const text = firstStringField(part, ["text", "thinking", "reasoning", "content", "url"]) ?? "";
13385
- return contentStringsHash([text]);
13386
- }
13387
- function computeRawRangeFingerprint(messages, startInclusive, endExclusive) {
13388
- const pieces = [];
13389
- for (const message of messages) {
13390
- if (message.ordinal < startInclusive || message.ordinal >= endExclusive)
13391
- continue;
13392
- const partFingerprint = message.parts.map(partContentFingerprint).join(",");
13393
- pieces.push(`${message.ordinal}:${message.id}:${message.parts.length}:${partFingerprint}`);
13394
- }
13395
- return pieces.join("|");
13396
- }
13397
- function invalidateTrueRawTokenCache(args) {
13398
- const sessionNeedle = args.sessionId ? `${args.sessionId}` : null;
13399
- const messageNeedle = args.messageId ? `\x00${args.messageId}\x00` : null;
13400
- for (const [key, value] of messageEstimateCache) {
13401
- const sessionMatches = sessionNeedle === null || key.includes(sessionNeedle);
13402
- const messageMatches = messageNeedle === null || key.includes(messageNeedle);
13403
- if (sessionMatches && messageMatches) {
13404
- messageEstimateCache.delete(key);
13405
- messageEstimateCacheBytes -= value.keyEstimateBytes;
13406
- }
13407
- }
13408
- args.reason;
13409
- }
13410
-
13411
13100
  // ../plugin/src/hooks/magic-context/protected-tail-boundary.ts
13412
13101
  var ALPHA = 0.3;
13413
13102
  var FLOOR_RATIO = 0.08;
@@ -13528,7 +13217,7 @@ function fenceWrapupBoundaryForToolArcs(args) {
13528
13217
  if (arc.resOrdinal === null) {
13529
13218
  continue;
13530
13219
  }
13531
- 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)) {
13532
13221
  next = arc.invOrdinal;
13533
13222
  }
13534
13223
  }
@@ -14545,7 +14234,7 @@ function advanceToolReclaimWatermarkToCurrentMax(db, sessionId) {
14545
14234
  }
14546
14235
 
14547
14236
  // ../plugin/src/shared/tag-transcript.ts
14548
- import { createHash as createHash7 } from "node:crypto";
14237
+ import { createHash as createHash9 } from "node:crypto";
14549
14238
 
14550
14239
  // ../plugin/src/hooks/magic-context/image-token-estimate.ts
14551
14240
  var IMAGE_TOKEN_DIVISOR = 750;
@@ -14700,7 +14389,7 @@ function readUint32BE(b, offset) {
14700
14389
  // ../plugin/src/shared/tag-transcript.ts
14701
14390
  var TEXT_TAG_IDENTITY_MARKER = ":mc-text-v1:";
14702
14391
  function textIdentityDigest(value) {
14703
- return createHash7("sha256").update(value).digest("hex");
14392
+ return createHash9("sha256").update(value).digest("hex");
14704
14393
  }
14705
14394
  function buildContentDerivedTextIds(messageId, parts) {
14706
14395
  const sources = parts.filter((part) => part.kind === "text").map((part) => stripTagPrefix(part.getText() ?? ""));
@@ -18587,6 +18276,7 @@ function mapParsedCompartmentsToChunk(compartments, chunk, sequenceOffset) {
18587
18276
 
18588
18277
  // ../plugin/src/hooks/magic-context/compartment-runner-validation.ts
18589
18278
  var MIN_RECOMP_CHUNK_TOKEN_BUDGET = 20;
18279
+ var HISTORIAN_BOUNDARY_HEALING_SLACK = 2;
18590
18280
  function healCompartmentGaps(compartments, toolOnlyRanges = []) {
18591
18281
  for (let i = 1;i < compartments.length; i++) {
18592
18282
  const prev = compartments[i - 1];
@@ -18602,6 +18292,36 @@ function healCompartmentGaps(compartments, toolOnlyRanges = []) {
18602
18292
  }
18603
18293
  }
18604
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
+ }
18605
18325
  function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, sequenceOffset) {
18606
18326
  const parsed = parseCompartmentOutput(text);
18607
18327
  if (parsed.compartments.length === 0) {
@@ -18611,6 +18331,7 @@ function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, se
18611
18331
  };
18612
18332
  }
18613
18333
  healCompartmentGaps(parsed.compartments, chunk.toolOnlyRanges);
18334
+ parsed.unprocessedFrom = healTerminalCompletedToolArc(parsed.compartments, parsed.unprocessedFrom, chunk.completedToolArcs, chunk.endIndex);
18614
18335
  const mapped = mapParsedCompartmentsToChunk(parsed.compartments, chunk, sequenceOffset);
18615
18336
  if (!mapped.ok) {
18616
18337
  return {
@@ -18625,6 +18346,13 @@ function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, se
18625
18346
  error: `Historian returned invalid compartment output: ${parsedValidationError}`
18626
18347
  };
18627
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
+ }
18628
18356
  return {
18629
18357
  ok: true,
18630
18358
  compartments: mapped.compartments,
@@ -21054,17 +20782,14 @@ ${chunkText}`,
21054
20782
  }
21055
20783
  }
21056
20784
  }
21057
- const BOUNDARY_HEALING_SLACK = 2;
21058
20785
  const inEmergency = getOverflowState(db, sessionId).needsEmergencyRecovery;
21059
20786
  const emittedCompartments = validatedPass.compartments;
21060
20787
  let newCompartments = emittedCompartments;
21061
- if (!inEmergency && !forceKeepLastCompartmentForChunk && emittedCompartments.length >= 2) {
20788
+ if (!inEmergency && !forceKeepLastCompartmentForChunk && shouldDiscardLastHistorianCompartment(emittedCompartments, chunk)) {
21062
20789
  const lastEmitted = emittedCompartments[emittedCompartments.length - 1];
21063
20790
  const lookaheadMargin = chunk.endIndex - lastEmitted.endMessage;
21064
- if (lookaheadMargin <= BOUNDARY_HEALING_SLACK) {
21065
- newCompartments = emittedCompartments.slice(0, -1);
21066
- sessionLog(sessionId, `historian discard-last: dropped provisional compartment ${lastEmitted.startMessage}-${lastEmitted.endMessage} (lookaheadMargin=${lookaheadMargin} <= ${BOUNDARY_HEALING_SLACK}); will re-derive next run`);
21067
- }
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`);
21068
20793
  }
21069
20794
  const lastNewEnd = newCompartments[newCompartments.length - 1]?.endMessage ?? 0;
21070
20795
  if (lastNewEnd + 1 <= offset) {
@@ -21861,7 +21586,7 @@ function stripPiProcessedImages(args) {
21861
21586
  }
21862
21587
 
21863
21588
  // src/system-prompt.ts
21864
- import { createHash as createHash8 } from "node:crypto";
21589
+ import { createHash as createHash10 } from "node:crypto";
21865
21590
 
21866
21591
  // ../plugin/src/agents/magic-context-prompt.ts
21867
21592
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -21877,6 +21602,10 @@ var PARTNER_FRAME_CLOSER_REDUCE = `
21877
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.`;
21878
21603
  var PARTNER_FRAME_CLOSER_NO_REDUCE = `
21879
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.`;
21880
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).`;
21881
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.
21882
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.`;
@@ -21919,6 +21648,17 @@ Use \`ctx_expand\` to recover the raw conversation behind a summary under a \`##
21919
21648
  - Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
21920
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.
21921
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}`;
21922
21662
  var GENERIC_SECTION = `
21923
21663
  ### Reduction Triggers
21924
21664
  - After reading files or search results you already acted on — drop raw outputs.
@@ -21935,21 +21675,27 @@ var GENERIC_SECTION = `
21935
21675
  - Your current task requirements and constraints.
21936
21676
  - Recent errors and unresolved decisions.
21937
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.`;
21938
21680
  var TEMPORAL_AWARENESS_GUIDANCE = `
21939
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.`;
21940
21682
  var SUBAGENT_REDUCE_INTRO = (protectedTags) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
21941
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.
21942
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.
21943
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.`;
21944
21689
  var CAVEMAN_COMPRESSION_WARNING = `
21945
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.`;
21946
- 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) {
21947
21692
  if (subagentMode) {
21693
+ const intro = preset === "light" ? SUBAGENT_REDUCE_INTRO_LIGHT(protectedTags) : SUBAGENT_REDUCE_INTRO(protectedTags);
21948
21694
  return `## Magic Context
21949
21695
 
21950
- ${SUBAGENT_REDUCE_INTRO(protectedTags)}`;
21696
+ ${intro}`;
21951
21697
  }
21952
- const smartNoteGuidance = dreamerEnabled ? `
21698
+ const smartNoteGuidance = dreamerEnabled ? preset === "light" ? SMART_NOTE_GUIDANCE_LIGHT : `
21953
21699
  When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
21954
21700
  The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
21955
21701
  Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
@@ -21959,13 +21705,32 @@ Example: \`ctx_note(action="write", content="Implement X because Y", surface_con
21959
21705
  const languageGuidance = languageDirective ? `
21960
21706
 
21961
21707
  ${languageDirective}` : "";
21708
+ if (primaryOverride !== undefined) {
21709
+ return `${primaryOverride}${temporalGuidance}${cavemanWarning}${languageGuidance}`;
21710
+ }
21962
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
+ }
21963
21720
  return `## Magic Context
21964
21721
 
21965
21722
  ${LONG_TERM_PARTNER_FRAME}
21966
21723
  ${PARTNER_FRAME_CLOSER_NO_REDUCE}
21967
21724
 
21968
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}`;
21969
21734
  }
21970
21735
  return `## Magic Context
21971
21736
 
@@ -21986,7 +21751,7 @@ function buildMagicContextBlock(opts) {
21986
21751
  const includeGuidance = (opts.includeGuidance ?? true) && !existing.includes(MAGIC_CONTEXT_MARKER);
21987
21752
  if (!includeGuidance)
21988
21753
  return null;
21989
- 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);
21990
21755
  }
21991
21756
  var DATE_PATTERN = /Today's date: .+/;
21992
21757
  function processSystemPromptForCache(args) {
@@ -22014,7 +21779,7 @@ function processSystemPromptForCache(args) {
22014
21779
  sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
22015
21780
  }
22016
21781
  }
22017
- const currentHash = createHash8("md5").update(frozenPrompt).digest("hex");
21782
+ const currentHash = createHash10("md5").update(promptSurfaceHashMaterial(frozenPrompt, args.promptSurfacePreset)).digest("hex");
22018
21783
  const hashChanged = !isFirstHash && currentHash !== previousHash;
22019
21784
  if (hashChanged) {
22020
21785
  sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
@@ -27683,7 +27448,7 @@ ${c.content}
27683
27448
  // package.json
27684
27449
  var package_default = {
27685
27450
  name: "@cortexkit/pi-magic-context",
27686
- version: "0.34.2",
27451
+ version: "0.35.0",
27687
27452
  type: "module",
27688
27453
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
27689
27454
  main: "dist/index.js",
@@ -29139,9 +28904,15 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29139
28904
  const projectDir = process.cwd();
29140
28905
  const seenDreamerProjectIdentities = new Set;
29141
28906
  ensureConfigLocationsMigrated(projectDir);
29142
- const { config, warnings, loadedFromPaths } = loadPiConfig({
28907
+ const { config, warnings, loadedFromPaths, registrationPromptSurface } = loadPiConfig({
29143
28908
  cwd: projectDir
29144
28909
  });
28910
+ const promptSurfaceRuntime = createPromptSurfaceRuntime({
28911
+ harness: "pi",
28912
+ directory: projectDir,
28913
+ warn: (message) => warn(`config: ${message}`)
28914
+ });
28915
+ const promptSurfaceGuidanceEpochs = createPromptSurfaceGuidanceEpochCache(promptSurfaceRuntime);
29145
28916
  const projectIdentity = resolveProjectIdentityForSession(projectDir, config.allow_home_project) ?? "";
29146
28917
  if (projectIdentity)
29147
28918
  seenDreamerProjectIdentities.add(projectIdentity);
@@ -29287,7 +29058,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29287
29058
  dreamerEnabled: isDreamerRunnable(config),
29288
29059
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
29289
29060
  todowriteEnabled,
29290
- compactionOff
29061
+ compactionOff,
29062
+ promptSurface: registrationPromptSurface,
29063
+ promptSurfaceRuntime
29291
29064
  });
29292
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)");
29293
29066
  pi.on("session_start", async (event, ctx) => {
@@ -29593,6 +29366,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29593
29366
  return;
29594
29367
  }
29595
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);
29596
29372
  const block = buildMagicContextBlock({
29597
29373
  db,
29598
29374
  cwd: currentProject.projectDir,
@@ -29605,6 +29381,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29605
29381
  temporalAwarenessEnabled: effectiveConfig.temporal_awareness ?? false,
29606
29382
  cavemanTextCompressionEnabled: effectiveConfig.caveman_text_compression?.enabled === true,
29607
29383
  language: effectiveConfig.language,
29384
+ promptSurfacePreset: promptSurface.preset,
29385
+ primaryGuidanceOverride: promptSurface.primaryOverride,
29608
29386
  userMemoriesEnabled: userMemoryCollectionEnabled(effectiveConfig.dreamer),
29609
29387
  isCacheBusting,
29610
29388
  existingSystemPrompt: event.systemPrompt
@@ -29621,7 +29399,8 @@ ${block}` : event.systemPrompt;
29621
29399
  db,
29622
29400
  sessionId,
29623
29401
  systemPrompt: composedPrompt,
29624
- isCacheBusting
29402
+ isCacheBusting,
29403
+ promptSurfacePreset: promptSurface.preset
29625
29404
  });
29626
29405
  if (result.hashChanged) {
29627
29406
  signalPiHistoryRefresh(sessionId);
@@ -29835,6 +29614,7 @@ ${block}` : event.systemPrompt;
29835
29614
  const sessionId = typeof sm?.getSessionId === "function" ? sm.getSessionId() : undefined;
29836
29615
  if (typeof sessionId === "string" && sessionId.length > 0) {
29837
29616
  clearPiSystemPromptSession(sessionId);
29617
+ promptSurfaceGuidanceEpochs.clear(sessionId);
29838
29618
  clearContextHandlerSession(sessionId);
29839
29619
  }
29840
29620
  } catch {}
@@ -29846,6 +29626,7 @@ ${block}` : event.systemPrompt;
29846
29626
  const outgoingSessionId = typeof sm?.getSessionId === "function" ? sm.getSessionId() : undefined;
29847
29627
  if (typeof outgoingSessionId === "string" && outgoingSessionId.length > 0) {
29848
29628
  clearPiSystemPromptSession(outgoingSessionId);
29629
+ promptSurfaceGuidanceEpochs.clear(outgoingSessionId);
29849
29630
  clearContextHandlerSession(outgoingSessionId);
29850
29631
  }
29851
29632
  } catch {}