@cortexkit/magic-context 0.43.2 → 0.44.1

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.
Files changed (2) hide show
  1. package/dist/index.js +1167 -695
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -13037,6 +13037,15 @@ function extraCliCandidates(d) {
13037
13037
  join11(d.home, ".volta", "bin", "opencode")
13038
13038
  ];
13039
13039
  }
13040
+ function openChamberCliCandidates(d) {
13041
+ if (d.platform !== "darwin")
13042
+ return [];
13043
+ const bundled = ["Contents", "Resources", "opencode-cli", "opencode"];
13044
+ return [
13045
+ join11("/Applications", "OpenChamber.app", ...bundled),
13046
+ join11(d.home, "Applications", "OpenChamber.app", ...bundled)
13047
+ ];
13048
+ }
13040
13049
  function canonicalPath(d, path) {
13041
13050
  try {
13042
13051
  return d.realpath ? d.realpath(path) : realpathSync(path);
@@ -13098,6 +13107,11 @@ function detectOpenCodeInstallations(deps) {
13098
13107
  addCandidate(installations, seenRealpaths, d, candidate, "PATH", "cli");
13099
13108
  }
13100
13109
  }
13110
+ for (const candidate of openChamberCliCandidates(d)) {
13111
+ if (d.isExecutable(candidate)) {
13112
+ addCandidate(installations, seenRealpaths, d, candidate, "openchamber", "cli");
13113
+ }
13114
+ }
13101
13115
  for (const appId of OPENCODE_DESKTOP_APP_IDS) {
13102
13116
  const marker = join11(desktopUserDataDir(d, appId), OPENCODE_DESKTOP_SETTINGS_FILE);
13103
13117
  if (d.exists(marker)) {
@@ -13171,6 +13185,17 @@ function describeOpenCodeInstallations(installations, deps = {}) {
13171
13185
  active: index === 0
13172
13186
  }));
13173
13187
  }
13188
+ function selectOpenCodeStoreHost(reports, generationOf) {
13189
+ const active = reports[0];
13190
+ if (!active)
13191
+ return null;
13192
+ const hasVersion = (report) => /\d/.test(report.version);
13193
+ if (!hasVersion(active) || generationOf(active.version) === "v2") {
13194
+ return { host: active, shadowed: false };
13195
+ }
13196
+ const v2 = reports.find((report) => report.kind === "cli" && hasVersion(report) && generationOf(report.version) === "v2");
13197
+ return v2 ? { host: v2, shadowed: true } : { host: active, shadowed: false };
13198
+ }
13174
13199
  function getAvailableModels2(binary) {
13175
13200
  const output = runOpenCode(["models"], binary);
13176
13201
  if (output === null)
@@ -15523,6 +15548,12 @@ var init_redaction = __esm(() => {
15523
15548
  // ../plugin/src/shared/logger.ts
15524
15549
  import * as fs from "node:fs";
15525
15550
  import * as path2 from "node:path";
15551
+ function boundBuffer() {
15552
+ while (bufferedBytes > MAX_BUFFERED_BYTES && buffer.length > 0) {
15553
+ bufferedBytes -= Buffer.byteLength(buffer.shift() ?? "");
15554
+ droppedLines++;
15555
+ }
15556
+ }
15526
15557
  function recordSwallowedWrite(error) {
15527
15558
  try {
15528
15559
  swallowedWriteCount++;
@@ -15605,12 +15636,12 @@ function flush() {
15605
15636
  clearTimeout(flushTimer);
15606
15637
  flushTimer = null;
15607
15638
  }
15608
- if (buffer.length === 0)
15639
+ if (buffer.length === 0 && droppedLines === 0)
15609
15640
  return;
15610
- const bufferedData = buffer.join("");
15611
- buffer = [];
15641
+ const notice = droppedLines ? `[${new Date().toISOString()}] [magic-context][global] logger dropped ${droppedLines} ${droppedLines === 1 ? "line" : "lines"}: buffer exceeded ${MAX_BUFFERED_BYTES} bytes while writes were pending
15642
+ ` : "";
15612
15643
  try {
15613
- const data = capLogData(bufferedData);
15644
+ const data = capLogData(notice + buffer.join(""));
15614
15645
  const logFile = getMagicContextLogPath();
15615
15646
  ensureDir4(logFile);
15616
15647
  let currentSize = getCurrentLogSize(logFile);
@@ -15620,6 +15651,9 @@ function flush() {
15620
15651
  currentSize = 0;
15621
15652
  }
15622
15653
  fs.appendFileSync(logFile, data, { encoding: "utf8", mode: 384 });
15654
+ buffer = [];
15655
+ bufferedBytes = 0;
15656
+ droppedLines = 0;
15623
15657
  activeLogFile = logFile;
15624
15658
  activeLogSize = currentSize + dataSize;
15625
15659
  flushesSinceSizeCheck++;
@@ -15628,6 +15662,7 @@ function flush() {
15628
15662
  activeLogSize = null;
15629
15663
  flushesSinceSizeCheck = 0;
15630
15664
  recordSwallowedWrite(error);
15665
+ boundBuffer();
15631
15666
  }
15632
15667
  }
15633
15668
  function scheduleFlush() {
@@ -15645,8 +15680,11 @@ function log3(message, data) {
15645
15680
  const timestamp = new Date().toISOString();
15646
15681
  const serialized = data === undefined ? "" : data instanceof Error ? ` ${sanitizeDiagnosticText(`${data.message}${data.stack ? `
15647
15682
  ${data.stack}` : ""}`)}` : ` ${JSON.stringify(sanitizeConfigValue(data))}`;
15648
- buffer.push(`[${timestamp}] ${sanitizeDiagnosticText(message)}${serialized}
15649
- `);
15683
+ const line = `[${timestamp}] ${sanitizeDiagnosticText(message)}${serialized}
15684
+ `;
15685
+ buffer.push(line);
15686
+ bufferedBytes += Buffer.byteLength(line);
15687
+ boundBuffer();
15650
15688
  if (buffer.length >= BUFFER_SIZE_LIMIT) {
15651
15689
  flush();
15652
15690
  } else {
@@ -15654,11 +15692,12 @@ ${data.stack}` : ""}`)}` : ` ${JSON.stringify(sanitizeConfigValue(data))}`;
15654
15692
  }
15655
15693
  } catch {}
15656
15694
  }
15657
- var isTestEnv = false, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50, MAX_LOG_FILE_BYTES, SIZE_CHECK_INTERVAL_FLUSHES = 64, activeLogFile = null, activeLogSize = null, flushesSinceSizeCheck = 0, swallowedWriteCount = 0, lastErrorMessage = null, lastErrorTime = null;
15695
+ var isTestEnv = false, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50, MAX_BUFFERED_BYTES, bufferedBytes = 0, droppedLines = 0, MAX_LOG_FILE_BYTES, SIZE_CHECK_INTERVAL_FLUSHES = 64, activeLogFile = null, activeLogSize = null, flushesSinceSizeCheck = 0, swallowedWriteCount = 0, lastErrorMessage = null, lastErrorTime = null;
15658
15696
  var init_logger = __esm(() => {
15659
15697
  init_data_path();
15660
15698
  init_redaction();
15661
15699
  buffer = [];
15700
+ MAX_BUFFERED_BYTES = 1024 * 1024;
15662
15701
  MAX_LOG_FILE_BYTES = 32 * 1024 * 1024;
15663
15702
  if (!isTestEnv) {
15664
15703
  process.on("exit", flush);
@@ -21370,6 +21409,7 @@ var init_magic_context = __esm(() => {
21370
21409
  maxTokens: true
21371
21410
  });
21372
21411
  DreamerConfigSchema = AgentMetadataSchema.extend({
21412
+ runner: _enum(["broca", "host"]).optional().describe(`Which side runs the dreamer completions the Rust module routes (classify-memories) in Rust transform mode: "host" runs them on this process's carrier, "broca" routes them to the Broca module. When unset, historian.runner applies, and when that is unset too the harness decides the same way it does for the historian. User-level config only.`),
21373
21413
  opencode: DreamerOpenCodeHarnessBlockSchema.optional(),
21374
21414
  pi: DreamerPiHarnessBlockSchema.optional(),
21375
21415
  omp: DreamerOmpHarnessBlockSchema.optional(),
@@ -21380,12 +21420,16 @@ var init_magic_context = __esm(() => {
21380
21420
  opencode: OpenCodeHarnessBlockSchema.optional(),
21381
21421
  pi: PiHarnessBlockSchema.optional(),
21382
21422
  omp: OmpHarnessBlockSchema.optional(),
21423
+ runner: _enum(["broca", "host"]).optional().describe('Which side runs the historian completion in Rust transform mode: "host" queues it for this process to run on the configured historian model, "broca" routes it to the Broca module. When unset the harness decides: OpenCode 1 and OpenCode 2 use "host", Claude Code (through the Thalamus gateway, which has no host to run a completion) uses "broca". User-level config only — it decides whose provider account pays for the call.'),
21424
+ host_runner: object({
21425
+ enabled: boolean2().optional().describe("Whether this process answers historian runs queued for a claimant (default true). Setting it to false stops the pull loop without changing historian.runner, so an operator can take one machine out of the lane and leave the queued runs for another claimant or for the runner setting to be changed deliberately.")
21426
+ }).optional().describe("Controls for this process's historian pull loop, which answers runs the Rust module queues for the host runner (`historian.runner: \"host\"`, or unset on OpenCode 1 and OpenCode 2). User-level config only — it decides whether this machine's provider account is spent on folds."),
21383
21427
  two_pass: boolean2().default(false).describe("Run a second editor pass over historian output to clean low-signal U: lines and cross-compartment duplicates. Adds ~1 extra API call and ~1.3x cost per historian run. Useful for models without extended thinking support. (default: false)"),
21384
21428
  disallowed_tools: array(_enum(["*", "read", "aft_outline", "aft_zoom", "aft_search"])).default([]).describe(`OpenCode only. Tools to REMOVE from the historian's default allow-list [read, aft_outline, aft_zoom, aft_search]. Applies to both historian and historian-editor agents. Use ["*"] to strip all tool definitions from the model request — this prevents weak instruction-following models (e.g. mistral-small-latest) from entering tool-calling loops. Individual tool names remove just that tool. Note: a user-supplied historian.permission override can re-allow a tool that disallowed_tools removed — disallowed_tools sets the baseline, permission overrides take precedence. (default: [])`)
21385
21429
  }).optional();
21386
21430
  EmbeddingFallbackProviderSchema = _enum(["local", "openai-compatible", "off"]);
21387
21431
  BaseEmbeddingConfigSchema = object({
21388
- provider: _enum(["local", "openai-compatible", "off", "synapse"]).default("local").describe("Embedding provider. 'local' uses Xenova/all-MiniLM-L6-v2, 'openai-compatible' requires endpoint and model, 'synapse' uses the certified local Synapse lane with an explicit fallback provider, and 'off' disables embeddings."),
21432
+ provider: _enum(["local", "openai-compatible", "off", "synapse"]).default("local").describe("Embedding provider. 'local' uses Xenova/all-MiniLM-L6-v2, 'openai-compatible' requires endpoint and model, 'synapse' uses the certified local Synapse lane with an explicit fallback provider, and 'off' disables embeddings. Session history is embedded for semantic ctx_search whenever the provider is not 'off', regardless of memory.enabled; memories are embedded only while memory.enabled is true."),
21389
21433
  fallback_provider: EmbeddingFallbackProviderSchema.optional().describe("Fallback provider for the Synapse lane. Required when provider is 'synapse'; local, openai-compatible, and off are valid."),
21390
21434
  model: string2().optional().describe("Embedding model name. Required for openai-compatible, ignored for local."),
21391
21435
  endpoint: string2().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
@@ -21497,7 +21541,7 @@ var init_magic_context = __esm(() => {
21497
21541
  smart_notes: object({
21498
21542
  retina_handoff: boolean2().default(false).describe("When true, dreamer skips smart notes whose surface conditions compiled to retina provider configs at authoring time. Default false keeps both paths active until the retina consumer is deployed.")
21499
21543
  }).default({ retina_handoff: false }).describe("Smart-note ownership transition controls."),
21500
- cache_ttl: union([string2(), object({ default: string2() }).catchall(string2())]).default("5m").describe(`How long Magic Context assumes the provider's cached prefix stays valid. This is MC's own deferral gate — it does not change the provider's actual cache lifetime. String (e.g. "5m", "1h", "30s") or per-model object ({ default: "5m", "provider/model": "1h", "provider/*": "never" }); keys resolve most-specific first (exact provider/model, bare model ID, shorter dash-prefixes, then the provider/* wildcard, then default). Set to "never" to mean MC never assumes expiry (for lanes kept warm externally by a cache-keep tool) — disables the idle-TTL heuristic so MC never initiates a rebuild based on elapsed time. Provider-side extended TTL is a separate request-level concern (cache_control: { ttl } in the request body).`),
21544
+ cache_ttl: union([string2(), object({ default: string2() }).catchall(string2())]).default("5m").describe(`How long Magic Context assumes the provider's cached prefix stays valid. This is MC's own deferral gate — it does not change the provider's actual cache lifetime. String (e.g. "5m", "1h", "30s") or per-model object ({ default: "5m", "provider/model": "1h", "provider/*": "never" }); keys resolve most-specific first (exact provider/model, bare model ID, shorter dash-prefixes, then the provider/* wildcard). Explicit per-model entries win; otherwise GPT-5.6 and later (including gpt-6*, through any provider prefix) use a built-in 30m lifetime before the object default or 5m fallback. An unset or global "5m" opts into built-in defaults; any other global string is an explicit policy and wins. Policy is frozen per session, including across restarts; a model switch resolves against that frozen policy. /ctx-status shows the effective value and source. OpenAI documents at least 30 minutes since the latest write or reuse: https://developers.openai.com/api/docs/guides/prompt-caching (Cache lifetime and Summary of model differences). Set to "never" to mean MC never assumes expiry (for lanes kept warm externally by a cache-keep tool) — disables the idle-TTL heuristic so MC never initiates a rebuild based on elapsed time. Provider-side extended TTL is a separate request-level concern (cache_control: { ttl } in the request body).`),
21501
21545
  prompt_surface: PromptSurfaceConfigSchema.default({ default: "full" }).describe("Prompt-surface presets: default is full; models use bare model IDs, provider/model, or provider/* routing keys. Guidance and tool-description overrides are user-level only. OpenCode 1.x, Pi, and OMP register tool descriptions once per process (they follow the default preset). OpenCode 2 rewrites the five ctx_* descriptions per request from the draft model."),
21502
21546
  output_reserve: union([
21503
21547
  number2().min(0),
@@ -21556,9 +21600,9 @@ var init_magic_context = __esm(() => {
21556
21600
  enabled: boolean2().default(true).describe("When false, Magic Context stops managing the context window and keeps its knowledge layer: memory and docs/user-profile/key-files injection through additive m[0]/m[1], raw-message FTS indexing, dreamer, notes, ctx_search, ctx_expand, ctx_memory, and /ctx-embed remain available. MC's historian/compartment preparation, tagging, markers, pruning, folding, drops, strips, splicing, synthetic context-management todos, temporal markers, nudges, and fail-closed blocking stop; ctx_expand remains a knowledge-surface tool. fail_closed_blocking is inert: a transform failure passes the input messages through without blocking or cancelling. This setting does not enable native compaction: OpenCode's compaction.auto / compaction.prune or Pi's equivalent owns the window, or nothing does. MC's compaction.enabled in magic-context.jsonc is distinct from OpenCode's compaction.auto / compaction.prune in opencode.jsonc; they are different files and different owners. On the first turn after disabling, a long session may trigger one native compaction cycle; MC removes only its own marker boundary, leaves native boundaries and stored compartments intact, and does no pre-trimming mitigation. Marker cleanup is lazy per session, so an unresumed session is cleaned when it is next resumed. If compaction is enabled again, run /ctx-wrapup when the historian is runnable to catch up. OpenCode peer verification against v1.18.4 confirms native compaction covers child sessions: subagents receive additive memory/docs injection and no MC reclaim in this mode, so keep subagent tasks small or leave compaction.enabled on for long subagent runs. This is boot-resolved and requires a process restart; project-tier compaction.enabled is stripped so a cloned repository cannot disable the user's setting. The sidebar reports raw usage as Context: <pct>% · native compaction or Context: <pct>% · no active compaction and does not show an MC execute-threshold fill. /ctx-wrapup, /ctx-recomp, and /ctx-flush refuse without context-management side effects; /ctx-embed remains functional. Raw content hidden by a native boundary before Magic Context's first pass is not retroactively indexed.")
21557
21601
  }).default({ enabled: true }).describe("Compaction-off mode gate. Default true (MC manages the context window as today). Set compaction.enabled=false to keep the knowledge layer while letting native compaction (or nothing) own the window. Boot-resolved; requires a restart to change."),
21558
21602
  todowrite: object({
21559
- enabled: boolean2().default(true).describe("Pi only: register Magic Context's todowrite task-list tool. Disable if you use your own todo extension. OpenCode ships its own built-in todowrite; this setting has no effect there."),
21603
+ enabled: boolean2().default(false).describe("Pi only: off by default. Set todowrite.enabled=true to register Magic Context's todowrite task-list tool and /todos command. OpenCode ships its own built-in todowrite; this setting has no effect there."),
21560
21604
  overlay: boolean2().default(true).describe("Pi only: show the persistent todo overlay above the editor while tasks are active.")
21561
- }).default({ enabled: true, overlay: true }).describe("Pi-only todowrite tool and overlay controls. Pi registers tools and widgets at extension boot, so changing this after /cd requires /reload or restart."),
21605
+ }).default({ enabled: false, overlay: true }).describe("Pi-only todowrite tool and overlay controls. Pi registers tools and widgets at extension boot, so changing this after /cd requires /reload or restart."),
21562
21606
  pi: PiConfigSchema.describe("Pi-only child-process extension controls. This setting is user-level only; project configuration cannot choose which extensions a user's subagent children load."),
21563
21607
  smart_drops: boolean2().default(false).describe("Content-aware reclaim of provably-superseded tool output, layered on the existing execute-pass auto-drop. When on: superseded todowrite (keep newest 1), spent ctx_reduce (keep newest 3), and zero-value meta (bash_status, bash_kill, ctx_note read/dismiss) outputs are dropped; older edits to a file are compressed to a filePath-preserving marker while the newest edit per file stays full. Only acts on passes already busting the cache, so it never originates a cache bust. Honors the protected-tag reserve. Experimental: opt-in, default off until cache stability is proven; when off the wire is byte-identical to the positional-only reclaim. Requires a restart."),
21564
21608
  caveman_text_compression: object({
@@ -21566,7 +21610,7 @@ var init_magic_context = __esm(() => {
21566
21610
  min_chars: number2().min(100).max(1e4).default(500).describe("Text parts shorter than this (characters) stay untouched. Min 100, max 10000. Default: 500.")
21567
21611
  }).default({ enabled: false, min_chars: 500 }).describe("Age-tier caveman compression for long user/assistant text parts. Active for primary sessions when enabled; never for subagents. Oldest 20% of eligible tags (outside protected tail) go to ultra, next 20% to full, next 20% to lite, newest 40% untouched. Graduated from experimental.caveman_text_compression; opt-in, default off (lossy)."),
21568
21612
  memory: object({
21569
- enabled: boolean2().default(true).describe("Enable cross-session memory (default: true)"),
21613
+ enabled: boolean2().default(true).describe("Enable cross-session memory (default: true). Does not affect history embedding or semantic ctx_search over session history; set embedding.provider to 'off' to stop all embedding."),
21570
21614
  injection_budget_tokens: number2().min(500).max(20000).default(4000).describe("Token budget for memory injection on session start (min: 500, max: 20000, default: 4000)"),
21571
21615
  auto_promote: boolean2().default(true).describe("Automatically promote eligible session facts into memory (default: true)"),
21572
21616
  retrieval_count_promotion_threshold: number2().min(1).default(3).describe("retrieval_count threshold for promoting memory to permanent status (min: 1, default: 3)"),
@@ -21607,10 +21651,12 @@ var init_magic_context = __esm(() => {
21607
21651
  "historian.omp.fallback_models",
21608
21652
  "historian.omp.thinking_level",
21609
21653
  "historian.two_pass",
21654
+ "historian.maxTokens",
21610
21655
  "historian_timeout_ms",
21611
21656
  "commit_cluster_trigger.enabled",
21612
21657
  "commit_cluster_trigger.min_clusters",
21613
21658
  "memory.auto_promote",
21659
+ "dreamer.maxTokens",
21614
21660
  "dreamer.opencode.model",
21615
21661
  "dreamer.opencode.fallback_models",
21616
21662
  "dreamer.opencode.variant",
@@ -22015,6 +22061,11 @@ function stripUnsafeProjectConfigFields(projectRaw) {
22015
22061
  warnings.push(`Ignoring ${removed.map((path) => `historian.${path}`).join(", ")} from project config ` + "(security: historian model selection is user-level only; a repository cannot force extra compaction cost).");
22016
22062
  }
22017
22063
  }
22064
+ const dreamer = projectRaw.dreamer;
22065
+ if (isPlainObject3(dreamer) && "runner" in dreamer) {
22066
+ delete dreamer.runner;
22067
+ warnings.push("Ignoring dreamer.runner from project config (security: which process and provider account run dreamer completions is a user-level setting).");
22068
+ }
22018
22069
  const mural = projectRaw.mural;
22019
22070
  if (isPlainObject3(mural) && "model" in mural) {
22020
22071
  delete mural.model;
@@ -22196,7 +22247,11 @@ var init_project_security = __esm(() => {
22196
22247
  init_magic_context();
22197
22248
  HIDDEN_AGENT_KEYS = ["historian", "dreamer"];
22198
22249
  HARNESS_KEYS = PER_HARNESS_MODEL_KEYS;
22199
- HISTORIAN_USER_ONLY_FIELDS = PER_HARNESS_MIGRATION_INVENTORY.historian.migrated_execution;
22250
+ HISTORIAN_USER_ONLY_FIELDS = [
22251
+ ...PER_HARNESS_MIGRATION_INVENTORY.historian.migrated_execution,
22252
+ "runner",
22253
+ "host_runner"
22254
+ ];
22200
22255
  PROMPT_SURFACE_USER_ONLY_FIELDS = ["guidance_override_path", "tool_descriptions"];
22201
22256
  AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools"];
22202
22257
  EMBEDDING_USER_ONLY_FIELDS = [
@@ -22511,6 +22566,25 @@ function redactConfigValue(value) {
22511
22566
  function formatProtectedTokensBelowMinWarning(value) {
22512
22567
  return `protected_tokens is a token floor (minimum ${PROTECTED_TOKENS_MIN}, default derived from the context window); ${value} looks like the old protected_tags count. Remove the key to use the default, or set a token count such as 16000.`;
22513
22568
  }
22569
+ function misplacedAgentModelWarnings(rawConfig) {
22570
+ const warnings = [];
22571
+ for (const agent of ["historian", "dreamer"]) {
22572
+ const block = rawConfig[agent];
22573
+ if (!block || typeof block !== "object" || Array.isArray(block))
22574
+ continue;
22575
+ const record = block;
22576
+ if (!Object.hasOwn(record, "model"))
22577
+ continue;
22578
+ const harnessWithModel = MODEL_HARNESS_BLOCKS.find((harness) => {
22579
+ const sub = record[harness];
22580
+ return sub !== null && typeof sub === "object" && !Array.isArray(sub) && Object.hasOwn(sub, "model");
22581
+ });
22582
+ if (harnessWithModel)
22583
+ continue;
22584
+ warnings.push(`${agent}.model is not read: the ${agent} model is resolved per harness, so it has to be ${agent}.opencode.model (OpenCode 1 and 2), ${agent}.pi.model, or ${agent}.omp.model. As written the ${agent} resolves to no models and never runs.`);
22585
+ }
22586
+ return warnings;
22587
+ }
22514
22588
  function warnProtectedTagsDeprecationOnce() {
22515
22589
  if (!warnedProtectedTagsDeprecation) {
22516
22590
  warnedProtectedTagsDeprecation = true;
@@ -22524,6 +22598,7 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
22524
22598
  warnProtectedTagsDeprecationOnce();
22525
22599
  preMigrationWarnings.push("protected_tags is deprecated and ignored; use protected_tokens instead.");
22526
22600
  }
22601
+ preMigrationWarnings.push(...misplacedAgentModelWarnings(rawConfig));
22527
22602
  const migratedExperimental = migrateLegacyExperimental(configWithoutRemovedAgent, preMigrationWarnings);
22528
22603
  const migratedDreamer = migrateDreamerV2(migratedExperimental, preMigrationWarnings);
22529
22604
  const migrated = migrateLegacyAgentEnabledInMemory(migratedDreamer, preMigrationWarnings);
@@ -22804,7 +22879,7 @@ function loadPluginConfigDetailed(directory, applyRuntimeGlobals = true) {
22804
22879
  cacheTtlConfigured
22805
22880
  };
22806
22881
  }
22807
- var warnedProtectedTagsDeprecation = false;
22882
+ var warnedProtectedTagsDeprecation = false, MODEL_HARNESS_BLOCKS;
22808
22883
  var init_config = __esm(() => {
22809
22884
  init_config_diagnostics();
22810
22885
  init_jsonc_parser();
@@ -22819,6 +22894,7 @@ var init_config = __esm(() => {
22819
22894
  init_removed_agent_config();
22820
22895
  init_magic_context();
22821
22896
  init_variable();
22897
+ MODEL_HARNESS_BLOCKS = ["opencode", "pi", "omp"];
22822
22898
  });
22823
22899
 
22824
22900
  // ../plugin/src/shared/opencode-config-dir.ts
@@ -26747,232 +26823,523 @@ function isRecord3(value) {
26747
26823
  return typeof value === "object" && value !== null && !Array.isArray(value);
26748
26824
  }
26749
26825
 
26750
- // ../plugin/src/hooks/magic-context/module-transport.ts
26751
- import { realpathSync as realpathSync3 } from "node:fs";
26752
- import { join as join17 } from "node:path";
26753
- function getDefaultSubcConnectionFile() {
26754
- return join17(getDataDir(), "cortexkit", "run", "subc-connection.json");
26755
- }
26756
- function errorChainSome(error, predicate) {
26757
- let current = error;
26758
- const seen = new Set;
26759
- while (isRecord3(current) && !seen.has(current)) {
26760
- seen.add(current);
26761
- if (predicate(current))
26762
- return true;
26763
- current = current.cause;
26764
- }
26765
- return false;
26766
- }
26767
- function isStaleOrDeadRouteFailure(error) {
26768
- return errorChainSome(error, (current) => {
26769
- const code = typeof current.code === "string" ? current.code : "";
26770
- const name = typeof current.name === "string" ? current.name : "";
26771
- const message = typeof current.message === "string" ? current.message : "";
26772
- return [
26773
- "stale_route_handle",
26774
- "route_closed",
26775
- "unknown_channel",
26776
- "unrecognized_channel",
26777
- "route_gone"
26778
- ].includes(code) || name === "StaleRouteHandleError" || /route handle \(\d+,\s*\d+\) is not live on the current connection/i.test(message) || /\b(?:unknown|unrecognized) channel\b/i.test(message);
26779
- });
26780
- }
26781
- function isDeadlineFailure(error) {
26782
- if (error instanceof SocketTimeoutError)
26783
- return true;
26784
- return errorChainSome(error, (current) => {
26785
- const code = typeof current.code === "string" ? current.code : "";
26786
- return ["ETIMEDOUT", "request_deadline", "deadline_exceeded_no_drop_observed"].includes(code);
26787
- });
26788
- }
26789
- function isConnectionFailure(error) {
26790
- if (error instanceof SocketClosedError || error instanceof SocketTimeoutError || error instanceof StaleRouteHandleError || isConsumerReconnectTransient(error) || isStaleOrDeadRouteFailure(error)) {
26791
- return true;
26792
- }
26793
- return errorChainSome(error, (current) => {
26794
- const code = typeof current.code === "string" ? current.code : "";
26795
- const message = typeof current.message === "string" ? current.message : "";
26796
- return [
26797
- "ENOENT",
26798
- "ECONNREFUSED",
26799
- "ECONNRESET",
26800
- "EPIPE",
26801
- "ETIMEDOUT",
26802
- "request_deadline",
26803
- "deadline_exceeded_no_drop_observed",
26804
- "connection_dropped",
26805
- "SUBC_CONNECTION_BACKOFF"
26806
- ].includes(code) || /\bclient closed\b|\bconnection closed\b|\bclosed the connection\b/i.test(message);
26807
- });
26808
- }
26809
- function routeOpenWithoutAmbientConsumerIdentity(client, target, identity, options = {}) {
26810
- return client.routeOpen(target, identity, {
26811
- ...options,
26812
- consumerIdentity: null
26813
- });
26826
+ // ../plugin/src/shared/user-facing-codes.ts
26827
+ function userFacingFailureCode(key) {
26828
+ return USER_FACING_FAILURES[key].code;
26814
26829
  }
26815
-
26816
- class SubcModuleTransport {
26817
- connectionFile;
26818
- moduleId;
26819
- requestTimeoutMs;
26820
- routeSessionPrefix;
26821
- client = null;
26822
- routes = new Map;
26823
- routeOpenings = new Map;
26824
- canonicalRootCache = new Map;
26825
- sessionLanes = new Map;
26826
- queuedLaneWaiters = 0;
26827
- wrapupSessions = new Map;
26828
- nextProbeMs = 0;
26829
- connectionPromise = null;
26830
- authorityProjectRoot = "";
26831
- authorityBindRoot = "";
26832
- backoffMs = CONNECT_BACKOFF_INITIAL_MS;
26833
- connectionGeneration = 0;
26834
- stateSyncCapabilityCache = null;
26835
- getCachedStateSyncCapabilities() {
26836
- const cached = this.stateSyncCapabilityCache;
26837
- if (!cached || cached.generation !== this.connectionGeneration)
26838
- return;
26839
- return cached.capabilities;
26840
- }
26841
- invalidateStateSyncCapabilities() {
26842
- this.stateSyncCapabilityCache = null;
26843
- }
26844
- async stateSyncCapabilities(args) {
26845
- const cached = this.getCachedStateSyncCapabilities();
26846
- if (cached)
26847
- return cached;
26848
- const response = await this.call({
26849
- sessionId: args.sessionId,
26850
- projectRoot: args.projectRoot,
26851
- method: "session.status",
26852
- body: { method: "session.status", v: 1, session_id: args.sessionId }
26853
- });
26854
- const raw = isRecord3(response) ? response : {};
26855
- const value = isRecord3(raw.result) ? raw.result : raw;
26856
- const epochs = isRecord3(value.epochs) ? value.epochs : {};
26857
- const capabilities = {
26858
- state_sync_deltas: epochs.state_sync_deltas === true,
26859
- ...epochs.state_sync_resume === true ? { state_sync_resume: true } : {}
26860
- };
26861
- this.stateSyncCapabilityCache = { generation: this.connectionGeneration, capabilities };
26862
- return capabilities;
26863
- }
26864
- constructor(connectionFile, moduleId = DEFAULT_MODULE_ID, requestTimeoutMs = MODULE_SEND_TIMEOUT_MS, routeSessionPrefix = "") {
26865
- this.connectionFile = connectionFile;
26866
- this.moduleId = moduleId;
26867
- this.requestTimeoutMs = requestTimeoutMs;
26868
- this.routeSessionPrefix = routeSessionPrefix;
26869
- }
26870
- deadlineError(detail) {
26871
- const error = new Error(`module transport deadline expired ${detail}`);
26872
- error.code = "ETIMEDOUT";
26873
- return error;
26874
- }
26875
- laneTimeoutError() {
26876
- return this.deadlineError("while queued");
26877
- }
26878
- connectionChangedError(detail) {
26879
- const error = new Error(detail);
26880
- error.code = "ECONNRESET";
26881
- return error;
26882
- }
26883
- async beforeDeadline(operation, deadlineMs, detail) {
26884
- operation.catch(() => {});
26885
- const remainingMs = deadlineMs - Date.now();
26886
- if (remainingMs <= 0)
26887
- throw this.deadlineError(detail);
26888
- let timer;
26889
- try {
26890
- return await Promise.race([
26891
- operation,
26892
- new Promise((_resolve, reject) => {
26893
- timer = setTimeout(() => reject(this.deadlineError(detail)), remainingMs);
26894
- })
26895
- ]);
26896
- } finally {
26897
- if (timer)
26898
- clearTimeout(timer);
26899
- }
26900
- }
26901
- cleanupLane(sessionId, lane) {
26902
- if (!lane.active && lane.waiters.length === 0 && this.sessionLanes.get(sessionId) === lane) {
26903
- this.sessionLanes.delete(sessionId);
26904
- }
26905
- }
26906
- laneRelease(sessionId, lane) {
26907
- let released = false;
26908
- return () => {
26909
- if (released)
26910
- return;
26911
- released = true;
26912
- lane.active = false;
26913
- this.dispatchNextLaneWaiter(sessionId, lane);
26914
- };
26915
- }
26916
- dispatchNextLaneWaiter(sessionId, lane) {
26917
- if (lane.active)
26918
- return;
26919
- while (lane.waiters.length > 0) {
26920
- const waiter = lane.waiters.shift();
26921
- if (!waiter)
26922
- continue;
26923
- this.queuedLaneWaiters = Math.max(0, this.queuedLaneWaiters - 1);
26924
- if (waiter.settled)
26925
- continue;
26926
- waiter.settled = true;
26927
- clearTimeout(waiter.timer);
26928
- waiter.signal?.removeEventListener("abort", waiter.onAbort);
26929
- if (waiter.signal?.aborted) {
26930
- waiter.reject(waiter.signal.reason ?? new Error("module transport call aborted"));
26931
- continue;
26932
- }
26933
- if (waiter.deadlineMs - Date.now() < SERIAL_LANE_MIN_REMAINING_MS) {
26934
- waiter.reject(this.laneTimeoutError());
26935
- continue;
26936
- }
26937
- lane.active = true;
26938
- waiter.resolve(this.laneRelease(sessionId, lane));
26939
- return;
26940
- }
26941
- this.cleanupLane(sessionId, lane);
26942
- }
26943
- queueFullError() {
26944
- const error = new Error("module transport queue is full");
26945
- error.code = "EBUSY";
26946
- return error;
26947
- }
26948
- acquireCorrectnessLane(sessionId, signal, deadlineMs) {
26949
- if (signal?.aborted) {
26950
- return Promise.reject(signal.reason ?? new Error("module transport call aborted"));
26951
- }
26952
- if (deadlineMs - Date.now() < SERIAL_LANE_MIN_REMAINING_MS) {
26953
- return Promise.reject(this.laneTimeoutError());
26954
- }
26955
- const lane = this.sessionLanes.get(sessionId) ?? { active: false, waiters: [] };
26956
- this.sessionLanes.set(sessionId, lane);
26957
- if (!lane.active && lane.waiters.length === 0) {
26958
- lane.active = true;
26959
- return Promise.resolve(this.laneRelease(sessionId, lane));
26960
- }
26961
- if (this.queuedLaneWaiters >= SERIAL_LANE_MAX_WAITERS || lane.waiters.length >= SERIAL_LANE_MAX_WAITERS_PER_SESSION) {
26962
- return Promise.reject(this.queueFullError());
26963
- }
26964
- return new Promise((resolve, reject) => {
26965
- const waiter = {};
26966
- const removeAndReject = (error) => {
26967
- if (waiter.settled)
26968
- return;
26969
- waiter.settled = true;
26970
- clearTimeout(waiter.timer);
26971
- signal?.removeEventListener("abort", waiter.onAbort);
26972
- const index = lane.waiters.indexOf(waiter);
26973
- if (index >= 0) {
26974
- lane.waiters.splice(index, 1);
26975
- this.queuedLaneWaiters = Math.max(0, this.queuedLaneWaiters - 1);
26830
+ var USER_FACING_FAILURES;
26831
+ var init_user_facing_codes = __esm(() => {
26832
+ USER_FACING_FAILURES = {
26833
+ historian_unavailable: {
26834
+ code: "MC-H01",
26835
+ sentence: "History compression could not finish this turn.",
26836
+ action: "It will retry automatically."
26837
+ },
26838
+ hidden_cleanup_unbound: {
26839
+ code: "MC-H02",
26840
+ sentence: "Some finished background sessions could not be removed from this host.",
26841
+ action: "They are removed automatically the next time Magic Context runs inside a registered OpenCode service."
26842
+ },
26843
+ historian_saved_history_misaligned: {
26844
+ code: "MC-H03",
26845
+ sentence: "History compression is paused because this session's saved summaries no longer line up with its messages.",
26846
+ action: "Run /ctx-recomp to rebuild them."
26847
+ },
26848
+ recomp_unavailable: {
26849
+ code: "MC-R01",
26850
+ sentence: "History compression could not be rebuilt.",
26851
+ action: "Run /ctx-recomp again."
26852
+ },
26853
+ dream_provider_timeout: {
26854
+ code: "MC-D01",
26855
+ sentence: "Memory maintenance took too long to respond.",
26856
+ action: "Run /ctx-dream again."
26857
+ },
26858
+ dream_provider_error: {
26859
+ code: "MC-D02",
26860
+ sentence: "Memory maintenance could not reach its model.",
26861
+ action: "Check the model connection, then run /ctx-dream again."
26862
+ },
26863
+ dream_step_limit: {
26864
+ code: "MC-D10",
26865
+ sentence: "Memory maintenance stopped at its hidden agent step limit.",
26866
+ action: "This task needs less work per run; changing the model connection will not help."
26867
+ },
26868
+ dream_empty_completion: {
26869
+ code: "MC-D03",
26870
+ sentence: "Memory maintenance received no usable response.",
26871
+ action: "Run /ctx-dream again."
26872
+ },
26873
+ dream_no_models: {
26874
+ code: "MC-D04",
26875
+ sentence: "Memory maintenance has no model available.",
26876
+ action: "Check the model settings, then run /ctx-dream again."
26877
+ },
26878
+ dream_child_aborted: {
26879
+ code: "MC-D05",
26880
+ sentence: "Memory maintenance was interrupted.",
26881
+ action: "Run /ctx-dream again."
26882
+ },
26883
+ dream_parse_failed: {
26884
+ code: "MC-D06",
26885
+ sentence: "Memory maintenance could not use the model response.",
26886
+ action: "Run /ctx-dream again."
26887
+ },
26888
+ dream_unknown: {
26889
+ code: "MC-D07",
26890
+ sentence: "Memory maintenance could not finish.",
26891
+ action: "Run /ctx-dream again."
26892
+ },
26893
+ dreamer_tick_blocked: {
26894
+ code: "MC-D09",
26895
+ sentence: "Background maintenance is not running: its last pass stopped before it reached the scheduled tasks.",
26896
+ action: "It is retried automatically; if it keeps happening, check the Magic Context log for the stage that stopped and run `npx @cortexkit/magic-context doctor`."
26897
+ },
26898
+ dream_task_needs_tool_loop: {
26899
+ code: "MC-D08",
26900
+ sentence: "Some memory maintenance tasks need a tool loop this host does not provide.",
26901
+ action: "The remaining tasks still run; the listed ones are skipped on this host."
26902
+ },
26903
+ embedding_substitution_rejected: {
26904
+ code: "MC-E01",
26905
+ sentence: "Search indexing could not use the selected model.",
26906
+ action: "Check the embedding model setting, then run /ctx-embed start again."
26907
+ },
26908
+ embedding_http_error: {
26909
+ code: "MC-E02",
26910
+ sentence: "The search indexing provider refused the request.",
26911
+ action: "Check the provider connection and credentials, then run /ctx-embed start again."
26912
+ },
26913
+ embedding_transport_error: {
26914
+ code: "MC-E03",
26915
+ sentence: "Search indexing could not reach its provider.",
26916
+ action: "Check the connection, then run /ctx-embed start again."
26917
+ },
26918
+ embedding_invalid_envelope: {
26919
+ code: "MC-E04",
26920
+ sentence: "Search indexing received an unsupported response.",
26921
+ action: "Check the embedding endpoint, then run /ctx-embed start again."
26922
+ },
26923
+ embedding_empty_result: {
26924
+ code: "MC-E05",
26925
+ sentence: "Search indexing received no usable result.",
26926
+ action: "Run /ctx-embed start again."
26927
+ },
26928
+ embedding_certification_refusal: {
26929
+ code: "MC-E06",
26930
+ sentence: "Search indexing is not ready for this provider.",
26931
+ action: "Finish the provider setup, or set a fallback provider in the embedding settings, then run /ctx-embed start again."
26932
+ },
26933
+ embedding_credential_required: {
26934
+ code: "MC-E07",
26935
+ sentence: "Search indexing needs provider credentials.",
26936
+ action: "Sign in to the provider, then run /ctx-embed start again."
26937
+ },
26938
+ embedding_local_binding_missing: {
26939
+ code: "MC-E08",
26940
+ sentence: "Local search indexing is unavailable on this system.",
26941
+ action: "Run `npx @cortexkit/magic-context doctor`, then retry."
26942
+ },
26943
+ embedding_local_fs_unavailable: {
26944
+ code: "MC-E09",
26945
+ sentence: "Local search indexing cannot save its model files.",
26946
+ action: "Update or reinstall Magic Context, then retry."
26947
+ },
26948
+ embedding_local_download_failure: {
26949
+ code: "MC-E10",
26950
+ sentence: "Local search indexing could not download its model.",
26951
+ action: "Check the network connection, then retry."
26952
+ },
26953
+ embedding_local_runtime_error: {
26954
+ code: "MC-E11",
26955
+ sentence: "Local search indexing could not start.",
26956
+ action: "Run `npx @cortexkit/magic-context doctor`, then retry."
26957
+ },
26958
+ embedding_unavailable: {
26959
+ code: "MC-E12",
26960
+ sentence: "Search indexing could not finish.",
26961
+ action: "Run /ctx-embed start again."
26962
+ },
26963
+ status_unavailable: {
26964
+ code: "MC-S01",
26965
+ sentence: "Magic Context status is temporarily unavailable.",
26966
+ action: "Retry /ctx-status in a moment."
26967
+ },
26968
+ transform_update_failed: {
26969
+ code: "MC-S02",
26970
+ sentence: "The last context update did not finish.",
26971
+ action: "Send another message to retry."
26972
+ },
26973
+ configuration_warning: {
26974
+ code: "MC-S03",
26975
+ sentence: "Some configuration settings could not be applied.",
26976
+ action: "Fix the configuration warning shown in /ctx-status, then restart."
26977
+ },
26978
+ status_log_unavailable: {
26979
+ code: "MC-S04",
26980
+ sentence: "Some diagnostic details could not be saved.",
26981
+ action: "Retry /ctx-status in a moment."
26982
+ },
26983
+ memory_writes_paused: {
26984
+ code: "MC-C01",
26985
+ sentence: "Memory writes are paused while the engine syncs.",
26986
+ action: "Retry in a moment."
26987
+ },
26988
+ memory_access_unavailable: {
26989
+ code: "MC-C02",
26990
+ sentence: "Memory access is temporarily unavailable.",
26991
+ action: "Retry in a moment."
26992
+ },
26993
+ note_changes_paused: {
26994
+ code: "MC-C03",
26995
+ sentence: "Note changes are paused while the engine syncs.",
26996
+ action: "Retry in a moment."
26997
+ },
26998
+ note_access_unavailable: {
26999
+ code: "MC-C04",
27000
+ sentence: "Notes are temporarily unavailable.",
27001
+ action: "Retry in a moment."
27002
+ },
27003
+ context_cleanup_paused: {
27004
+ code: "MC-C05",
27005
+ sentence: "Context cleanup is paused while the engine syncs.",
27006
+ action: "Retry in a moment."
27007
+ },
27008
+ partial_history_unavailable: {
27009
+ code: "MC-C06",
27010
+ sentence: "Partial history compression is not available in the current mode.",
27011
+ action: "Run /ctx-recomp without a range."
27012
+ },
27013
+ smart_note_conditions_unavailable: {
27014
+ code: "MC-C08",
27015
+ sentence: "Conditional notes are not available in the current mode.",
27016
+ action: "Save a regular note without a condition."
27017
+ },
27018
+ history_compression_paused: {
27019
+ code: "MC-C09",
27020
+ sentence: "History compression is paused while the engine syncs.",
27021
+ action: "Retry in a moment."
27022
+ },
27023
+ history_compression_needs_message: {
27024
+ code: "MC-C12",
27025
+ sentence: "History compression has not seen this session since Magic Context reconnected.",
27026
+ action: "Send a message in this session first, then run /ctx-wrapup again."
27027
+ },
27028
+ context_service_unavailable: {
27029
+ code: "MC-C10",
27030
+ sentence: "Magic Context is temporarily unavailable.",
27031
+ action: "Retry in a moment."
27032
+ },
27033
+ store_ahead_of_binary: {
27034
+ code: "MC-C13",
27035
+ sentence: "Magic Context refused to start: its store (store.db) was migrated by a newer ck-mc build than the one running.",
27036
+ action: "Update ck-mc, or roll back by restoring ck-mc together with context.db and store.db from the same backup."
27037
+ },
27038
+ compaction_marker_missing: {
27039
+ code: "MC-C11",
27040
+ sentence: "The history boundary marker is missing from the OpenCode store, so requests carry the full session.",
27041
+ action: "It is retried on every message; if this persists, run `/ctx-flush`."
27042
+ },
27043
+ memory_mirror_stalled: {
27044
+ code: "MC-M01",
27045
+ sentence: "Memory synchronization stopped before the host mirror caught up.",
27046
+ action: "Send another message to resume it, or run `ck doctor drain-authority`."
27047
+ },
27048
+ memory_authority_mismatch: {
27049
+ code: "MC-M02",
27050
+ sentence: "Memory authority is inconsistent between the host and module.",
27051
+ action: "Run `ck doctor drain-authority` before changing Rust mode."
27052
+ },
27053
+ dreamer_task_failing: {
27054
+ code: "MC-S05",
27055
+ sentence: "A background maintenance task keeps failing on its schedule.",
27056
+ action: "Check the Magic Context log for the failing task and its error."
27057
+ }
27058
+ };
27059
+ });
27060
+
27061
+ // ../plugin/src/hooks/magic-context/store-ahead-refusal.ts
27062
+ function renderStoreAheadOfBinaryRefusal(versions) {
27063
+ const failure = USER_FACING_FAILURES.store_ahead_of_binary;
27064
+ const sentence = versions ? `Magic Context refused to start: its store (store.db) is at schema v${versions.dbVersion} but this ck-mc build only knows up to v${versions.binaryMax}.` : failure.sentence;
27065
+ return `${sentence} ${failure.action} (${failure.code})`;
27066
+ }
27067
+ function isRecord4(value) {
27068
+ return value !== null && typeof value === "object";
27069
+ }
27070
+ function nonNegativeInteger(value) {
27071
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
27072
+ }
27073
+ function versionsFromDetail(detail) {
27074
+ if (!isRecord4(detail))
27075
+ return null;
27076
+ const dbVersion = nonNegativeInteger(detail.db_version);
27077
+ const binaryMax = nonNegativeInteger(detail.binary_max);
27078
+ return dbVersion !== null && binaryMax !== null ? { dbVersion, binaryMax } : null;
27079
+ }
27080
+ function versionsFromMessage(message) {
27081
+ if (typeof message !== "string")
27082
+ return null;
27083
+ const match = /db_version=(\d+) binary_max=(\d+)/.exec(message) ?? /schema v(\d+) but this ck-mc build only knows up to v(\d+)/.exec(message);
27084
+ if (!match)
27085
+ return null;
27086
+ return { dbVersion: Number(match[1]), binaryMax: Number(match[2]) };
27087
+ }
27088
+ function storeAheadOfBinaryFailure(error) {
27089
+ let current = error;
27090
+ const seen = new Set;
27091
+ while (isRecord4(current) && !seen.has(current)) {
27092
+ seen.add(current);
27093
+ if (current instanceof StoreAheadOfBinaryError)
27094
+ return current;
27095
+ if (current.code === STORE_AHEAD_OF_BINARY_CODE) {
27096
+ const versions = versionsFromDetail(current.detail) ?? versionsFromMessage(current.message);
27097
+ return new StoreAheadOfBinaryError(versions, { cause: error });
27098
+ }
27099
+ current = current.cause;
27100
+ }
27101
+ return null;
27102
+ }
27103
+ var STORE_AHEAD_OF_BINARY_CODE = "store_ahead_of_binary", StoreAheadOfBinaryError;
27104
+ var init_store_ahead_refusal = __esm(() => {
27105
+ init_user_facing_codes();
27106
+ StoreAheadOfBinaryError = class StoreAheadOfBinaryError extends Error {
27107
+ code = STORE_AHEAD_OF_BINARY_CODE;
27108
+ versions;
27109
+ constructor(versions, options) {
27110
+ super(renderStoreAheadOfBinaryRefusal(versions), options);
27111
+ this.name = "StoreAheadOfBinaryError";
27112
+ this.versions = versions;
27113
+ }
27114
+ };
27115
+ });
27116
+
27117
+ // ../plugin/src/hooks/magic-context/module-transport.ts
27118
+ import { realpathSync as realpathSync3 } from "node:fs";
27119
+ import { join as join17 } from "node:path";
27120
+ function getDefaultSubcConnectionFile() {
27121
+ return join17(getDataDir(), "cortexkit", "run", "subc-connection.json");
27122
+ }
27123
+ function errorChainSome(error, predicate) {
27124
+ let current = error;
27125
+ const seen = new Set;
27126
+ while (isRecord3(current) && !seen.has(current)) {
27127
+ seen.add(current);
27128
+ if (predicate(current))
27129
+ return true;
27130
+ current = current.cause;
27131
+ }
27132
+ return false;
27133
+ }
27134
+ function isStaleOrDeadRouteFailure(error) {
27135
+ return errorChainSome(error, (current) => {
27136
+ const code = typeof current.code === "string" ? current.code : "";
27137
+ const name = typeof current.name === "string" ? current.name : "";
27138
+ const message = typeof current.message === "string" ? current.message : "";
27139
+ return [
27140
+ "stale_route_handle",
27141
+ "route_closed",
27142
+ "unknown_channel",
27143
+ "unrecognized_channel",
27144
+ "route_gone"
27145
+ ].includes(code) || name === "StaleRouteHandleError" || /route handle \(\d+,\s*\d+\) is not live on the current connection/i.test(message) || /\b(?:unknown|unrecognized) channel\b/i.test(message);
27146
+ });
27147
+ }
27148
+ function isDeadlineFailure(error) {
27149
+ if (error instanceof SocketTimeoutError)
27150
+ return true;
27151
+ return errorChainSome(error, (current) => {
27152
+ const code = typeof current.code === "string" ? current.code : "";
27153
+ return ["ETIMEDOUT", "request_deadline", "deadline_exceeded_no_drop_observed"].includes(code);
27154
+ });
27155
+ }
27156
+ function isConnectionFailure(error) {
27157
+ if (error instanceof SocketClosedError || error instanceof SocketTimeoutError || error instanceof StaleRouteHandleError || isConsumerReconnectTransient(error) || isStaleOrDeadRouteFailure(error)) {
27158
+ return true;
27159
+ }
27160
+ return errorChainSome(error, (current) => {
27161
+ const code = typeof current.code === "string" ? current.code : "";
27162
+ const message = typeof current.message === "string" ? current.message : "";
27163
+ return [
27164
+ "ENOENT",
27165
+ "ECONNREFUSED",
27166
+ "ECONNRESET",
27167
+ "EPIPE",
27168
+ "ETIMEDOUT",
27169
+ "request_deadline",
27170
+ "deadline_exceeded_no_drop_observed",
27171
+ "connection_dropped",
27172
+ "SUBC_CONNECTION_BACKOFF"
27173
+ ].includes(code) || /\bclient closed\b|\bconnection closed\b|\bclosed the connection\b/i.test(message);
27174
+ });
27175
+ }
27176
+ function routeOpenWithoutAmbientConsumerIdentity(client, target, identity, options = {}) {
27177
+ return client.routeOpen(target, identity, {
27178
+ ...options,
27179
+ consumerIdentity: null
27180
+ });
27181
+ }
27182
+
27183
+ class SubcModuleTransport {
27184
+ connectionFile;
27185
+ moduleId;
27186
+ requestTimeoutMs;
27187
+ routeSessionPrefix;
27188
+ client = null;
27189
+ routes = new Map;
27190
+ routeOpenings = new Map;
27191
+ canonicalRootCache = new Map;
27192
+ sessionLanes = new Map;
27193
+ queuedLaneWaiters = 0;
27194
+ wrapupSessions = new Map;
27195
+ nextProbeMs = 0;
27196
+ connectionPromise = null;
27197
+ authorityProjectRoot = "";
27198
+ authorityBindRoot = "";
27199
+ backoffMs = CONNECT_BACKOFF_INITIAL_MS;
27200
+ connectionGeneration = 0;
27201
+ stateSyncCapabilityCache = null;
27202
+ getCachedStateSyncCapabilities() {
27203
+ const cached = this.stateSyncCapabilityCache;
27204
+ if (!cached || cached.generation !== this.connectionGeneration)
27205
+ return;
27206
+ return cached.capabilities;
27207
+ }
27208
+ invalidateStateSyncCapabilities() {
27209
+ this.stateSyncCapabilityCache = null;
27210
+ }
27211
+ async stateSyncCapabilities(args) {
27212
+ const cached = this.getCachedStateSyncCapabilities();
27213
+ if (cached)
27214
+ return cached;
27215
+ const response = await this.call({
27216
+ sessionId: args.sessionId,
27217
+ projectRoot: args.projectRoot,
27218
+ method: "session.status",
27219
+ body: { method: "session.status", v: 1, session_id: args.sessionId }
27220
+ });
27221
+ const raw = isRecord3(response) ? response : {};
27222
+ const value = isRecord3(raw.result) ? raw.result : raw;
27223
+ const epochs = isRecord3(value.epochs) ? value.epochs : {};
27224
+ const capabilities = {
27225
+ state_sync_deltas: epochs.state_sync_deltas === true,
27226
+ ...epochs.state_sync_resume === true ? { state_sync_resume: true } : {}
27227
+ };
27228
+ this.stateSyncCapabilityCache = { generation: this.connectionGeneration, capabilities };
27229
+ return capabilities;
27230
+ }
27231
+ constructor(connectionFile, moduleId = DEFAULT_MODULE_ID, requestTimeoutMs = MODULE_SEND_TIMEOUT_MS, routeSessionPrefix = "") {
27232
+ this.connectionFile = connectionFile;
27233
+ this.moduleId = moduleId;
27234
+ this.requestTimeoutMs = requestTimeoutMs;
27235
+ this.routeSessionPrefix = routeSessionPrefix;
27236
+ }
27237
+ deadlineError(detail) {
27238
+ const error = new Error(`module transport deadline expired ${detail}`);
27239
+ error.code = "ETIMEDOUT";
27240
+ return error;
27241
+ }
27242
+ laneTimeoutError() {
27243
+ return this.deadlineError("while queued");
27244
+ }
27245
+ connectionChangedError(detail) {
27246
+ const error = new Error(detail);
27247
+ error.code = "ECONNRESET";
27248
+ return error;
27249
+ }
27250
+ async beforeDeadline(operation, deadlineMs, detail) {
27251
+ operation.catch(() => {});
27252
+ const remainingMs = deadlineMs - Date.now();
27253
+ if (remainingMs <= 0)
27254
+ throw this.deadlineError(detail);
27255
+ let timer;
27256
+ try {
27257
+ return await Promise.race([
27258
+ operation,
27259
+ new Promise((_resolve, reject) => {
27260
+ timer = setTimeout(() => reject(this.deadlineError(detail)), remainingMs);
27261
+ })
27262
+ ]);
27263
+ } finally {
27264
+ if (timer)
27265
+ clearTimeout(timer);
27266
+ }
27267
+ }
27268
+ cleanupLane(sessionId, lane) {
27269
+ if (!lane.active && lane.waiters.length === 0 && this.sessionLanes.get(sessionId) === lane) {
27270
+ this.sessionLanes.delete(sessionId);
27271
+ }
27272
+ }
27273
+ laneRelease(sessionId, lane) {
27274
+ let released = false;
27275
+ return () => {
27276
+ if (released)
27277
+ return;
27278
+ released = true;
27279
+ lane.active = false;
27280
+ this.dispatchNextLaneWaiter(sessionId, lane);
27281
+ };
27282
+ }
27283
+ dispatchNextLaneWaiter(sessionId, lane) {
27284
+ if (lane.active)
27285
+ return;
27286
+ while (lane.waiters.length > 0) {
27287
+ const waiter = lane.waiters.shift();
27288
+ if (!waiter)
27289
+ continue;
27290
+ this.queuedLaneWaiters = Math.max(0, this.queuedLaneWaiters - 1);
27291
+ if (waiter.settled)
27292
+ continue;
27293
+ waiter.settled = true;
27294
+ clearTimeout(waiter.timer);
27295
+ waiter.signal?.removeEventListener("abort", waiter.onAbort);
27296
+ if (waiter.signal?.aborted) {
27297
+ waiter.reject(waiter.signal.reason ?? new Error("module transport call aborted"));
27298
+ continue;
27299
+ }
27300
+ if (waiter.deadlineMs - Date.now() < SERIAL_LANE_MIN_REMAINING_MS) {
27301
+ waiter.reject(this.laneTimeoutError());
27302
+ continue;
27303
+ }
27304
+ lane.active = true;
27305
+ waiter.resolve(this.laneRelease(sessionId, lane));
27306
+ return;
27307
+ }
27308
+ this.cleanupLane(sessionId, lane);
27309
+ }
27310
+ queueFullError() {
27311
+ const error = new Error("module transport queue is full");
27312
+ error.code = "EBUSY";
27313
+ return error;
27314
+ }
27315
+ acquireCorrectnessLane(sessionId, signal, deadlineMs) {
27316
+ if (signal?.aborted) {
27317
+ return Promise.reject(signal.reason ?? new Error("module transport call aborted"));
27318
+ }
27319
+ if (deadlineMs - Date.now() < SERIAL_LANE_MIN_REMAINING_MS) {
27320
+ return Promise.reject(this.laneTimeoutError());
27321
+ }
27322
+ const lane = this.sessionLanes.get(sessionId) ?? { active: false, waiters: [] };
27323
+ this.sessionLanes.set(sessionId, lane);
27324
+ if (!lane.active && lane.waiters.length === 0) {
27325
+ lane.active = true;
27326
+ return Promise.resolve(this.laneRelease(sessionId, lane));
27327
+ }
27328
+ if (this.queuedLaneWaiters >= SERIAL_LANE_MAX_WAITERS || lane.waiters.length >= SERIAL_LANE_MAX_WAITERS_PER_SESSION) {
27329
+ return Promise.reject(this.queueFullError());
27330
+ }
27331
+ return new Promise((resolve, reject) => {
27332
+ const waiter = {};
27333
+ const removeAndReject = (error) => {
27334
+ if (waiter.settled)
27335
+ return;
27336
+ waiter.settled = true;
27337
+ clearTimeout(waiter.timer);
27338
+ signal?.removeEventListener("abort", waiter.onAbort);
27339
+ const index = lane.waiters.indexOf(waiter);
27340
+ if (index >= 0) {
27341
+ lane.waiters.splice(index, 1);
27342
+ this.queuedLaneWaiters = Math.max(0, this.queuedLaneWaiters - 1);
26976
27343
  }
26977
27344
  reject(error);
26978
27345
  this.cleanupLane(sessionId, lane);
@@ -27017,7 +27384,8 @@ class SubcModuleTransport {
27017
27384
  const laneDeadlineMs = Date.now() + attemptTimeoutMs;
27018
27385
  let releaseLane = () => {};
27019
27386
  try {
27020
- if (!args.bypassSessionLane) {
27387
+ const outsideSessionOrder = args.method.startsWith("historian.");
27388
+ if (!args.bypassSessionLane && !outsideSessionOrder) {
27021
27389
  releaseLane = await this.acquireCorrectnessLane(args.sessionId, args.signal, laneDeadlineMs);
27022
27390
  }
27023
27391
  } catch (error) {
@@ -27077,6 +27445,9 @@ class SubcModuleTransport {
27077
27445
  } catch (error) {
27078
27446
  if (args.signal?.aborted)
27079
27447
  throw args.signal.reason ?? error;
27448
+ const storeAhead = storeAheadOfBinaryFailure(error);
27449
+ if (storeAhead)
27450
+ throw storeAhead;
27080
27451
  if (args.method === "state_sync" && isDeadlineFailure(error)) {
27081
27452
  const body = isRecord3(args.body) ? args.body : {};
27082
27453
  throw Object.assign(new Error(`state_sync timeout stage=module_ack page=${body.seed_batch_index ?? 0}/${body.seed_batch_total ?? 1} series=${body.seed_id ?? "delta"} budget_ms=${attemptTimeoutMs}`), {
@@ -27390,6 +27761,7 @@ var DEFAULT_MODULE_ID = "magic-context", CONNECT_BACKOFF_INITIAL_MS = 1000, CONN
27390
27761
  var init_module_transport = __esm(() => {
27391
27762
  init_dist5();
27392
27763
  init_data_path();
27764
+ init_store_ahead_refusal();
27393
27765
  });
27394
27766
  // ../plugin/src/shared/pi-executable.ts
27395
27767
  function piHarnessKindFromExecutable(value) {
@@ -27406,6 +27778,9 @@ var init_pi_executable = __esm(() => {
27406
27778
  // ../plugin/src/shared/rpc-utils.ts
27407
27779
  import { execFileSync as execFileSync4 } from "node:child_process";
27408
27780
  import { readFileSync as readFileSync14 } from "node:fs";
27781
+ function isOwnRpcServerRecord(record) {
27782
+ return record.pid === process.pid && record.instance_id !== undefined && ownRpcServerInstanceIds.has(record.instance_id);
27783
+ }
27409
27784
  function isPidAlive(pid) {
27410
27785
  if (!Number.isInteger(pid) || pid <= 0)
27411
27786
  return "dead";
@@ -27928,10 +28303,11 @@ function parseRpcPortFile(content, fallbackPid = 0) {
27928
28303
  function isValidPort(port) {
27929
28304
  return Number.isInteger(port) && port > 0 && port <= 65535;
27930
28305
  }
27931
- var RPC_IDENTITY_SKEW_TOLERANCE_MS = 120000, LINUX_CLOCK_TICKS_PER_SECOND = 100, PS_PROBE_TIMEOUT_MS = 1000, WINDOWS_CIM_PROBE_TIMEOUT_MS = 5000, MAX_ANCESTOR_WALK_DEPTH = 16, OPEN_CODE_COMMAND_MARKERS, TASKLIST_NO_TASKS_PATTERN, WINDOWS_CIM_COMMAND = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine,CreationDate | ConvertTo-Json -Compress", PI_HARNESS_ARC_MARKERS, rpcIdentityReadFileSync, rpcIdentityExecFileSync, rpcIdentityProcessKill, rpcProcessListExecFileSync, rpcIdentityPlatform, rpcIdentityNowMs = () => Date.now(), windowsProcessFactsCache = null;
28306
+ var ownRpcServerInstanceIds, RPC_IDENTITY_SKEW_TOLERANCE_MS = 120000, LINUX_CLOCK_TICKS_PER_SECOND = 100, PS_PROBE_TIMEOUT_MS = 1000, WINDOWS_CIM_PROBE_TIMEOUT_MS = 5000, MAX_ANCESTOR_WALK_DEPTH = 16, OPEN_CODE_COMMAND_MARKERS, TASKLIST_NO_TASKS_PATTERN, WINDOWS_CIM_COMMAND = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine,CreationDate | ConvertTo-Json -Compress", PI_HARNESS_ARC_MARKERS, rpcIdentityReadFileSync, rpcIdentityExecFileSync, rpcIdentityProcessKill, rpcProcessListExecFileSync, rpcIdentityPlatform, rpcIdentityNowMs = () => Date.now(), windowsProcessFactsCache = null;
27932
28307
  var init_rpc_utils = __esm(() => {
27933
28308
  init_logger();
27934
28309
  init_pi_executable();
28310
+ ownRpcServerInstanceIds = new Set;
27935
28311
  OPEN_CODE_COMMAND_MARKERS = ["opencode", "node", "bun", "electron"];
27936
28312
  TASKLIST_NO_TASKS_PATTERN = /^INFO:\s+No tasks are running which match the specified criteria\.?$/im;
27937
28313
  PI_HARNESS_ARC_MARKERS = [
@@ -28061,7 +28437,9 @@ function formatFailClosedBlockingMessage(reason) {
28061
28437
  }
28062
28438
  return [
28063
28439
  `Magic Context cannot migrate the shared database (one database serves every project on this machine) because ${formatFailClosedBlockingProcesses(reason.blockingProcesses)} may be running an older Magic Context build that would fail against the migrated database.`,
28440
+ `The database is at upstream migration v${reason.persistedVersion}; this build needs v${reason.supportedVersion}.`,
28064
28441
  "Restart the blocking process — even one from a different project — and it will pick up the new build and migrate on start; or shut it down and retry.",
28442
+ "If that host loads a pinned or cached older Magic Context build, a restart alone is not enough: stop it or update its Magic Context build, then start this host once so it migrates the database with nothing older attached.",
28065
28443
  `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
28066
28444
  ].join(" ");
28067
28445
  }
@@ -28096,6 +28474,47 @@ var init_message_fts_rowid_map = __esm(() => {
28096
28474
  activeBackfills = new WeakMap;
28097
28475
  });
28098
28476
 
28477
+ // ../plugin/src/features/magic-context/memory/constants.ts
28478
+ var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
28479
+ var init_constants = __esm(() => {
28480
+ V2_MEMORY_CATEGORIES = [
28481
+ "PROJECT_RULES",
28482
+ "ARCHITECTURE",
28483
+ "CONSTRAINTS",
28484
+ "CONFIG_VALUES",
28485
+ "NAMING"
28486
+ ];
28487
+ PROMOTABLE_CATEGORIES = [
28488
+ ...V2_MEMORY_CATEGORIES,
28489
+ "ARCHITECTURE_DECISIONS",
28490
+ "CONFIG_DEFAULTS",
28491
+ "USER_PREFERENCES",
28492
+ "USER_DIRECTIVES",
28493
+ "ENVIRONMENT",
28494
+ "WORKFLOW_RULES",
28495
+ "KNOWN_ISSUES"
28496
+ ];
28497
+ CATEGORY_PRIORITY = [
28498
+ ...V2_MEMORY_CATEGORIES,
28499
+ "USER_DIRECTIVES",
28500
+ "USER_PREFERENCES",
28501
+ "CONFIG_DEFAULTS",
28502
+ "ARCHITECTURE_DECISIONS",
28503
+ "ENVIRONMENT",
28504
+ "WORKFLOW_RULES",
28505
+ "KNOWN_ISSUES"
28506
+ ];
28507
+ MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
28508
+ acc[category] = index;
28509
+ return acc;
28510
+ }, {});
28511
+ MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
28512
+ CATEGORY_DEFAULT_TTL = {
28513
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
28514
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
28515
+ };
28516
+ });
28517
+
28099
28518
  // ../plugin/src/hooks/magic-context/compartment-parser.ts
28100
28519
  function makeTierOpenRegex(n) {
28101
28520
  return new RegExp(`<p${n}\\s*(/?)>`);
@@ -28125,6 +28544,8 @@ function extractTiersFromInner(inner) {
28125
28544
  function parseCompartmentOutput(text) {
28126
28545
  const compartments = [];
28127
28546
  const facts = [];
28547
+ let droppedFactBlocks = 0;
28548
+ let droppedFacts = 0;
28128
28549
  for (const match of text.matchAll(COMPARTMENT_REGEX)) {
28129
28550
  const attrs = match[1];
28130
28551
  const inner = match[2];
@@ -28174,16 +28595,21 @@ function parseCompartmentOutput(text) {
28174
28595
  }
28175
28596
  }
28176
28597
  const factsBlockMatch = text.match(FACTS_BLOCK_REGEX);
28177
- const factsScope = factsBlockMatch ? factsBlockMatch[1] : text.replace(EVENTS_BLOCK_REGEX, "").replace(/<compartment\s+[^>]*?\s*>.*?<\/compartment>/gs, "");
28598
+ const factsScope = factsBlockMatch ? factsBlockMatch[1] : text.replace(EVENTS_BLOCK_REGEX, "").replace(/<compartment\s+[^>]*?\s*>.*?<\/compartment>/gs, "").replace(/<(meta|user_observations|primer_candidates)>.*?<\/\1>/gs, "").replace(/<\/?(?:output|compartments)>/g, "");
28178
28599
  for (const categoryMatch of factsScope.matchAll(CATEGORY_BLOCK_REGEX)) {
28179
28600
  const category = categoryMatch[1];
28180
28601
  const blockContent = categoryMatch[2];
28181
- for (const itemMatch of blockContent.matchAll(FACT_ITEM_REGEX)) {
28182
- const content = unescapeXml(itemMatch[1].trim());
28183
- if (content) {
28184
- facts.push({ category, content });
28185
- }
28602
+ const items = [...blockContent.matchAll(FACT_ITEM_REGEX)].map((match) => unescapeXml(match[1].trim())).filter(Boolean);
28603
+ if (!HISTORIAN_CATEGORIES.has(category)) {
28604
+ if (items.length === 0)
28605
+ continue;
28606
+ droppedFactBlocks++;
28607
+ droppedFacts += items.length;
28608
+ log3(`[historian] Dropped <facts> category ${category} (${items.length} facts)`);
28609
+ continue;
28186
28610
  }
28611
+ for (const content of items)
28612
+ facts.push({ category, content });
28187
28613
  }
28188
28614
  const unprocessedMatch = text.match(UNPROCESSED_REGEX);
28189
28615
  const unprocessedFrom = unprocessedMatch ? parseInt(unprocessedMatch[1], 10) : null;
@@ -28221,7 +28647,16 @@ function parseCompartmentOutput(text) {
28221
28647
  }
28222
28648
  const events = parseEvents(text);
28223
28649
  compartments.sort((a, b) => a.startMessage - b.startMessage);
28224
- return { compartments, facts, events, unprocessedFrom, userObservations, primerCandidates };
28650
+ return {
28651
+ compartments,
28652
+ facts,
28653
+ droppedFactBlocks,
28654
+ droppedFacts,
28655
+ events,
28656
+ unprocessedFrom,
28657
+ userObservations,
28658
+ primerCandidates
28659
+ };
28225
28660
  }
28226
28661
  function parseEvents(text) {
28227
28662
  const blockMatch = text.match(EVENTS_BLOCK_REGEX);
@@ -28247,8 +28682,10 @@ function parseEvents(text) {
28247
28682
  function unescapeXml(s) {
28248
28683
  return s.replace(/&amp;/g, "&").replace(/&apos;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">");
28249
28684
  }
28250
- var COMPARTMENT_REGEX, ATTR_START_REGEX, ATTR_END_REGEX, ATTR_TITLE_REGEX, ATTR_EPISODE_REGEX, ATTR_IMPORTANCE_REGEX, TIER_OPEN_REGEXES, TIER_CLOSE_ANY_REGEX, TIER_OPEN_ANY_REGEX, CATEGORY_BLOCK_REGEX, FACT_ITEM_REGEX, UNPROCESSED_REGEX, USER_OBSERVATIONS_REGEX, USER_OBS_ITEM_REGEX, PRIMER_CANDIDATES_REGEX, PRIMER_ELEMENT_REGEX, PRIMER_ITEM_REGEX, FACTS_BLOCK_REGEX, EVENTS_BLOCK_REGEX, EVENT_ELEMENT_REGEX, EVENT_FIELD_REGEX;
28685
+ var COMPARTMENT_REGEX, ATTR_START_REGEX, ATTR_END_REGEX, ATTR_TITLE_REGEX, ATTR_EPISODE_REGEX, ATTR_IMPORTANCE_REGEX, TIER_OPEN_REGEXES, TIER_CLOSE_ANY_REGEX, TIER_OPEN_ANY_REGEX, CATEGORY_BLOCK_REGEX, HISTORIAN_CATEGORIES, FACT_ITEM_REGEX, UNPROCESSED_REGEX, USER_OBSERVATIONS_REGEX, USER_OBS_ITEM_REGEX, PRIMER_CANDIDATES_REGEX, PRIMER_ELEMENT_REGEX, PRIMER_ITEM_REGEX, FACTS_BLOCK_REGEX, EVENTS_BLOCK_REGEX, EVENT_ELEMENT_REGEX, EVENT_FIELD_REGEX;
28251
28686
  var init_compartment_parser = __esm(() => {
28687
+ init_constants();
28688
+ init_logger();
28252
28689
  COMPARTMENT_REGEX = /<compartment\s+([^>]*?)\s*>(.*?)<\/compartment>/gs;
28253
28690
  ATTR_START_REGEX = /\bstart="(\d+)"/;
28254
28691
  ATTR_END_REGEX = /\bend="(\d+)"/;
@@ -28263,7 +28700,8 @@ var init_compartment_parser = __esm(() => {
28263
28700
  ];
28264
28701
  TIER_CLOSE_ANY_REGEX = /<\/p\d/;
28265
28702
  TIER_OPEN_ANY_REGEX = /<p\d/;
28266
- CATEGORY_BLOCK_REGEX = /<(PROJECT_RULES|ARCHITECTURE|CONSTRAINTS|CONFIG_VALUES|NAMING)>(.*?)<\/\1>/gs;
28703
+ CATEGORY_BLOCK_REGEX = /<([A-Za-z_][A-Za-z0-9_-]*)>(.*?)<\/\1>/gs;
28704
+ HISTORIAN_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
28267
28705
  FACT_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
28268
28706
  UNPROCESSED_REGEX = /<unprocessed_from>(\d+)<\/unprocessed_from>/;
28269
28707
  USER_OBSERVATIONS_REGEX = /<user_observations>(.*?)<\/user_observations>/s;
@@ -28617,53 +29055,18 @@ function healAllNullColumns(db) {
28617
29055
  const nullPredicate = presentFallbacks.map(([column]) => `${column} IS NULL`).join(" OR ");
28618
29056
  db.prepare(`UPDATE session_meta SET ${assignments} WHERE ${nullPredicate}`).run(...presentFallbacks.map(([, fallback]) => fallback));
28619
29057
  }
28620
- healMissingMemoryBlockIds(db, existingColumns);
28621
- }
28622
- function getSessionMetaColumns(db) {
28623
- const rows = db.prepare("PRAGMA table_info(session_meta)").all();
28624
- return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : []));
28625
- }
28626
- function healMissingMemoryBlockIds(db, columns) {
28627
- if (!columns.has("memory_block_cache") || !columns.has("memory_block_ids") || !columns.has("memory_block_count")) {
28628
- return;
28629
- }
28630
- db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != '' AND (memory_block_ids IS NULL OR memory_block_ids = '') AND memory_block_count > 0").run();
28631
- }
28632
-
28633
- // ../plugin/src/features/magic-context/memory/constants.ts
28634
- var V2_MEMORY_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
28635
- var init_constants = __esm(() => {
28636
- V2_MEMORY_CATEGORIES = [
28637
- "PROJECT_RULES",
28638
- "ARCHITECTURE",
28639
- "CONSTRAINTS",
28640
- "CONFIG_VALUES",
28641
- "NAMING"
28642
- ];
28643
- CATEGORY_PRIORITY = [
28644
- "PROJECT_RULES",
28645
- "ARCHITECTURE",
28646
- "CONSTRAINTS",
28647
- "CONFIG_VALUES",
28648
- "NAMING",
28649
- "USER_DIRECTIVES",
28650
- "USER_PREFERENCES",
28651
- "CONFIG_DEFAULTS",
28652
- "ARCHITECTURE_DECISIONS",
28653
- "ENVIRONMENT",
28654
- "WORKFLOW_RULES",
28655
- "KNOWN_ISSUES"
28656
- ];
28657
- MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
28658
- acc[category] = index;
28659
- return acc;
28660
- }, {});
28661
- MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
28662
- CATEGORY_DEFAULT_TTL = {
28663
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
28664
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
28665
- };
28666
- });
29058
+ healMissingMemoryBlockIds(db, existingColumns);
29059
+ }
29060
+ function getSessionMetaColumns(db) {
29061
+ const rows = db.prepare("PRAGMA table_info(session_meta)").all();
29062
+ return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : []));
29063
+ }
29064
+ function healMissingMemoryBlockIds(db, columns) {
29065
+ if (!columns.has("memory_block_cache") || !columns.has("memory_block_ids") || !columns.has("memory_block_count")) {
29066
+ return;
29067
+ }
29068
+ db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != '' AND (memory_block_ids IS NULL OR memory_block_ids = '') AND memory_block_count > 0").run();
29069
+ }
28667
29070
 
28668
29071
  // ../plugin/src/features/magic-context/project-identity.ts
28669
29072
  var init_project_identity2 = __esm(() => {
@@ -31144,6 +31547,20 @@ var init_migrations = __esm(async () => {
31144
31547
  return;
31145
31548
  ensureColumn(db, "compartment_state_lease", "owner_pid", "INTEGER");
31146
31549
  }
31550
+ },
31551
+ {
31552
+ version: 91,
31553
+ description: "per-project embedding high-water mark for memories written outside this host",
31554
+ up(db) {
31555
+ db.exec(`
31556
+ CREATE TABLE IF NOT EXISTS memory_embedding_watermarks (
31557
+ project_path TEXT PRIMARY KEY,
31558
+ written_memory_id INTEGER NOT NULL DEFAULT 0,
31559
+ embedded_memory_id INTEGER NOT NULL DEFAULT 0,
31560
+ updated_at INTEGER NOT NULL DEFAULT 0
31561
+ );
31562
+ `);
31563
+ }
31147
31564
  }
31148
31565
  ];
31149
31566
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -31375,6 +31792,8 @@ function inspectRpcServerDiscovery(storageDir) {
31375
31792
  return junk;
31376
31793
  continue;
31377
31794
  }
31795
+ if (isOwnRpcServerRecord(record))
31796
+ continue;
31378
31797
  const liveness = isPidAlive(record.pid);
31379
31798
  if (liveness === "dead") {
31380
31799
  staleFiles.push(portFile);
@@ -31996,6 +32415,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
31996
32415
  (id, cursor_session_id, cursor_ordinal, completed, updated_at)
31997
32416
  VALUES (1, '', 0, 0, 0);
31998
32417
 
32418
+ -- Highest memory id another writer (the Rust module in single-store mode) put
32419
+ -- into memories for a project, and how far this host has embedded. Migration v91.
32420
+ CREATE TABLE IF NOT EXISTS memory_embedding_watermarks (
32421
+ project_path TEXT PRIMARY KEY,
32422
+ written_memory_id INTEGER NOT NULL DEFAULT 0,
32423
+ embedded_memory_id INTEGER NOT NULL DEFAULT 0,
32424
+ updated_at INTEGER NOT NULL DEFAULT 0
32425
+ );
32426
+
31999
32427
  CREATE TABLE IF NOT EXISTS message_history_index (
32000
32428
  session_id TEXT PRIMARY KEY,
32001
32429
  last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
@@ -32655,7 +33083,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
32655
33083
  `);
32656
33084
  ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
32657
33085
  }
32658
- var databases, pendingAsyncOpens, persistenceByDatabase, persistenceErrorByDatabase, pathByDatabase, LATEST_SUPPORTED_VERSION = 90, BOOT_SQLITE_BUSY_TIMEOUT_MS = 5000, PERMISSIONS_ENFORCEABLE, RPC_DISCOVERY_PARSE_GRACE_MS, defaultRpcDiscoveryFs, rpcDiscoveryFs, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS;
33086
+ var databases, pendingAsyncOpens, persistenceByDatabase, persistenceErrorByDatabase, pathByDatabase, LATEST_SUPPORTED_VERSION = 91, BOOT_SQLITE_BUSY_TIMEOUT_MS = 5000, PERMISSIONS_ENFORCEABLE, RPC_DISCOVERY_PARSE_GRACE_MS, defaultRpcDiscoveryFs, rpcDiscoveryFs, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS;
32659
33087
  var init_storage_db = __esm(async () => {
32660
33088
  init_data_path();
32661
33089
  init_logger();
@@ -35440,7 +35868,7 @@ async function doctorRetryV22Backfill(db) {
35440
35868
  bumpProjectMemoryEpochInTransaction(db, identity, now);
35441
35869
  succeeded += 1;
35442
35870
  }
35443
- })();
35871
+ }).immediate();
35444
35872
  } catch (error) {
35445
35873
  const classified = classifyBackfillError(error);
35446
35874
  db.prepare(`UPDATE v22_backfill_failures
@@ -35473,7 +35901,7 @@ async function doctorRekeyV22DirIdentity(db, rawProjectPath) {
35473
35901
  if (changedRows > 0) {
35474
35902
  bumpProjectMemoryEpochInTransaction(db, newIdentity, now);
35475
35903
  }
35476
- })();
35904
+ }).immediate();
35477
35905
  return { oldIdentity, newIdentity, changedRows };
35478
35906
  }
35479
35907
  var BACKFILL_META_KEY = "v22_legacy_memory_backfill", BACKFILL_CURSOR_META_KEY = "v22_legacy_memory_backfill_cursor", MEMORIES_TABLE = "memories";
@@ -35546,236 +35974,6 @@ var init_v22_backfill_commands = __esm(async () => {
35546
35974
  await init_storage_db();
35547
35975
  });
35548
35976
 
35549
- // ../plugin/src/shared/user-facing-codes.ts
35550
- function userFacingFailureCode(key) {
35551
- return USER_FACING_FAILURES[key].code;
35552
- }
35553
- var USER_FACING_FAILURES;
35554
- var init_user_facing_codes = __esm(() => {
35555
- USER_FACING_FAILURES = {
35556
- historian_unavailable: {
35557
- code: "MC-H01",
35558
- sentence: "History compression could not finish this turn.",
35559
- action: "It will retry automatically."
35560
- },
35561
- hidden_cleanup_unbound: {
35562
- code: "MC-H02",
35563
- sentence: "Some finished background sessions could not be removed from this host.",
35564
- action: "They are removed automatically the next time Magic Context runs inside a registered OpenCode service."
35565
- },
35566
- historian_saved_history_misaligned: {
35567
- code: "MC-H03",
35568
- sentence: "History compression is paused because this session's saved summaries no longer line up with its messages.",
35569
- action: "Run /ctx-recomp to rebuild them."
35570
- },
35571
- recomp_unavailable: {
35572
- code: "MC-R01",
35573
- sentence: "History compression could not be rebuilt.",
35574
- action: "Run /ctx-recomp again."
35575
- },
35576
- dream_provider_timeout: {
35577
- code: "MC-D01",
35578
- sentence: "Memory maintenance took too long to respond.",
35579
- action: "Run /ctx-dream again."
35580
- },
35581
- dream_provider_error: {
35582
- code: "MC-D02",
35583
- sentence: "Memory maintenance could not reach its model.",
35584
- action: "Check the model connection, then run /ctx-dream again."
35585
- },
35586
- dream_empty_completion: {
35587
- code: "MC-D03",
35588
- sentence: "Memory maintenance received no usable response.",
35589
- action: "Run /ctx-dream again."
35590
- },
35591
- dream_no_models: {
35592
- code: "MC-D04",
35593
- sentence: "Memory maintenance has no model available.",
35594
- action: "Check the model settings, then run /ctx-dream again."
35595
- },
35596
- dream_child_aborted: {
35597
- code: "MC-D05",
35598
- sentence: "Memory maintenance was interrupted.",
35599
- action: "Run /ctx-dream again."
35600
- },
35601
- dream_parse_failed: {
35602
- code: "MC-D06",
35603
- sentence: "Memory maintenance could not use the model response.",
35604
- action: "Run /ctx-dream again."
35605
- },
35606
- dream_unknown: {
35607
- code: "MC-D07",
35608
- sentence: "Memory maintenance could not finish.",
35609
- action: "Run /ctx-dream again."
35610
- },
35611
- dreamer_tick_blocked: {
35612
- code: "MC-D09",
35613
- sentence: "Background maintenance is not running: its last pass stopped before it reached the scheduled tasks.",
35614
- action: "It is retried automatically; if it keeps happening, check the Magic Context log for the stage that stopped and run `npx @cortexkit/magic-context doctor`."
35615
- },
35616
- dream_task_needs_tool_loop: {
35617
- code: "MC-D08",
35618
- sentence: "Some memory maintenance tasks need a tool loop this host does not provide.",
35619
- action: "The remaining tasks still run; the listed ones are skipped on this host."
35620
- },
35621
- embedding_substitution_rejected: {
35622
- code: "MC-E01",
35623
- sentence: "Search indexing could not use the selected model.",
35624
- action: "Check the embedding model setting, then run /ctx-embed start again."
35625
- },
35626
- embedding_http_error: {
35627
- code: "MC-E02",
35628
- sentence: "The search indexing provider refused the request.",
35629
- action: "Check the provider connection and credentials, then run /ctx-embed start again."
35630
- },
35631
- embedding_transport_error: {
35632
- code: "MC-E03",
35633
- sentence: "Search indexing could not reach its provider.",
35634
- action: "Check the connection, then run /ctx-embed start again."
35635
- },
35636
- embedding_invalid_envelope: {
35637
- code: "MC-E04",
35638
- sentence: "Search indexing received an unsupported response.",
35639
- action: "Check the embedding endpoint, then run /ctx-embed start again."
35640
- },
35641
- embedding_empty_result: {
35642
- code: "MC-E05",
35643
- sentence: "Search indexing received no usable result.",
35644
- action: "Run /ctx-embed start again."
35645
- },
35646
- embedding_certification_refusal: {
35647
- code: "MC-E06",
35648
- sentence: "Search indexing is not ready for this provider.",
35649
- action: "Finish the provider setup, or set a fallback provider in the embedding settings, then run /ctx-embed start again."
35650
- },
35651
- embedding_credential_required: {
35652
- code: "MC-E07",
35653
- sentence: "Search indexing needs provider credentials.",
35654
- action: "Sign in to the provider, then run /ctx-embed start again."
35655
- },
35656
- embedding_local_binding_missing: {
35657
- code: "MC-E08",
35658
- sentence: "Local search indexing is unavailable on this system.",
35659
- action: "Run `npx @cortexkit/magic-context doctor`, then retry."
35660
- },
35661
- embedding_local_fs_unavailable: {
35662
- code: "MC-E09",
35663
- sentence: "Local search indexing cannot save its model files.",
35664
- action: "Update or reinstall Magic Context, then retry."
35665
- },
35666
- embedding_local_download_failure: {
35667
- code: "MC-E10",
35668
- sentence: "Local search indexing could not download its model.",
35669
- action: "Check the network connection, then retry."
35670
- },
35671
- embedding_local_runtime_error: {
35672
- code: "MC-E11",
35673
- sentence: "Local search indexing could not start.",
35674
- action: "Run `npx @cortexkit/magic-context doctor`, then retry."
35675
- },
35676
- embedding_unavailable: {
35677
- code: "MC-E12",
35678
- sentence: "Search indexing could not finish.",
35679
- action: "Run /ctx-embed start again."
35680
- },
35681
- status_unavailable: {
35682
- code: "MC-S01",
35683
- sentence: "Magic Context status is temporarily unavailable.",
35684
- action: "Retry /ctx-status in a moment."
35685
- },
35686
- transform_update_failed: {
35687
- code: "MC-S02",
35688
- sentence: "The last context update did not finish.",
35689
- action: "Send another message to retry."
35690
- },
35691
- configuration_warning: {
35692
- code: "MC-S03",
35693
- sentence: "Some configuration settings could not be applied.",
35694
- action: "Fix the configuration warning shown in /ctx-status, then restart."
35695
- },
35696
- status_log_unavailable: {
35697
- code: "MC-S04",
35698
- sentence: "Some diagnostic details could not be saved.",
35699
- action: "Retry /ctx-status in a moment."
35700
- },
35701
- memory_writes_paused: {
35702
- code: "MC-C01",
35703
- sentence: "Memory writes are paused while the engine syncs.",
35704
- action: "Retry in a moment."
35705
- },
35706
- memory_access_unavailable: {
35707
- code: "MC-C02",
35708
- sentence: "Memory access is temporarily unavailable.",
35709
- action: "Retry in a moment."
35710
- },
35711
- note_changes_paused: {
35712
- code: "MC-C03",
35713
- sentence: "Note changes are paused while the engine syncs.",
35714
- action: "Retry in a moment."
35715
- },
35716
- note_access_unavailable: {
35717
- code: "MC-C04",
35718
- sentence: "Notes are temporarily unavailable.",
35719
- action: "Retry in a moment."
35720
- },
35721
- context_cleanup_paused: {
35722
- code: "MC-C05",
35723
- sentence: "Context cleanup is paused while the engine syncs.",
35724
- action: "Retry in a moment."
35725
- },
35726
- partial_history_unavailable: {
35727
- code: "MC-C06",
35728
- sentence: "Partial history compression is not available in the current mode.",
35729
- action: "Run /ctx-recomp without a range."
35730
- },
35731
- smart_note_conditions_unavailable: {
35732
- code: "MC-C08",
35733
- sentence: "Conditional notes are not available in the current mode.",
35734
- action: "Save a regular note without a condition."
35735
- },
35736
- history_compression_paused: {
35737
- code: "MC-C09",
35738
- sentence: "History compression is paused while the engine syncs.",
35739
- action: "Retry in a moment."
35740
- },
35741
- history_compression_needs_message: {
35742
- code: "MC-C12",
35743
- sentence: "History compression has not seen this session since Magic Context reconnected.",
35744
- action: "Send a message in this session first, then run /ctx-wrapup again."
35745
- },
35746
- context_service_unavailable: {
35747
- code: "MC-C10",
35748
- sentence: "Magic Context is temporarily unavailable.",
35749
- action: "Retry in a moment."
35750
- },
35751
- compaction_marker_missing: {
35752
- code: "MC-C11",
35753
- sentence: "The history boundary marker is missing from the OpenCode store, so requests carry the full session.",
35754
- action: "It is retried on every message; if this persists, run `/ctx-flush`."
35755
- },
35756
- memory_mirror_stalled: {
35757
- code: "MC-M01",
35758
- sentence: "Memory synchronization stopped before the host mirror caught up.",
35759
- action: "Send another message to resume it, or run `ck doctor drain-authority`."
35760
- },
35761
- memory_authority_mismatch: {
35762
- code: "MC-M02",
35763
- sentence: "Memory authority is inconsistent between the host and module.",
35764
- action: "Run `ck doctor drain-authority` before changing Rust mode."
35765
- },
35766
- rust_mode_unsupported: {
35767
- code: "MC-S06",
35768
- sentence: "Experimental Rust transform mode is not available on this OpenCode host.",
35769
- action: 'Magic Context is running its TypeScript mode instead; set `transform_mode` to "ts" to stop requesting Rust.'
35770
- },
35771
- dreamer_task_failing: {
35772
- code: "MC-S05",
35773
- sentence: "A background maintenance task keeps failing on its schedule.",
35774
- action: "Check the Magic Context log for the failing task and its error."
35775
- }
35776
- };
35777
- });
35778
-
35779
35977
  // ../plugin/src/features/magic-context/dreamer/tick-failure.ts
35780
35978
  function parseFailure(value) {
35781
35979
  if (!value)
@@ -38519,6 +38717,9 @@ async function collectHistorianRuns(storageDirPath) {
38519
38717
  } catch {}
38520
38718
  }
38521
38719
  }
38720
+ function openCodeIssueLogHarnesses(hostGeneration) {
38721
+ return hostGeneration === "v2" ? ["opencode2", "opencode"] : ["opencode"];
38722
+ }
38522
38723
  async function collectDiagnostics() {
38523
38724
  const pluginVersion = getSelfVersion();
38524
38725
  const configPaths = detectConfigPaths();
@@ -38528,8 +38729,6 @@ async function collectDiagnostics() {
38528
38729
  const storageResolution = getStorageResolution();
38529
38730
  const storageDirPath = storageResolution.path;
38530
38731
  const contextDbPath = join26(storageDirPath, "context.db");
38531
- const logFiles = inspectMagicContextLogs("opencode");
38532
- const primaryLog = logFiles.find((file) => file.exists) ?? logFiles[0];
38533
38732
  let compactionEnabled = false;
38534
38733
  try {
38535
38734
  compactionEnabled = isCompactionEnabled(loadPluginConfig(process.cwd()));
@@ -38539,6 +38738,8 @@ async function collectDiagnostics() {
38539
38738
  const opencodeInstallations = describeOpenCodeInstallations(detectOpenCodeInstallations());
38540
38739
  const activeInstallation = opencodeInstallations[0];
38541
38740
  const hostGeneration = openCodeHostGenerationFromVersion(activeInstallation?.version);
38741
+ const logFiles = openCodeIssueLogHarnesses(hostGeneration).flatMap((harness) => inspectMagicContextLogs(harness)).filter((file, index, all) => all.findIndex((other) => other.path === file.path) === index);
38742
+ const primaryLog = logFiles.find((file) => file.exists) ?? logFiles[0];
38542
38743
  const conflictResult = detectConflicts(process.cwd(), { compactionEnabled, hostGeneration });
38543
38744
  const openCodeDatabaseResolution = resolveOpenCodeDbPath(hostGeneration);
38544
38745
  const recentSessions = await collectRecentSessions(openCodeDatabaseResolution, hostGeneration);
@@ -40046,6 +40247,179 @@ var init_storage_versions = __esm(async () => {
40046
40247
  await init_storage_db();
40047
40248
  });
40048
40249
 
40250
+ // src/commands/doctor-opencode-cache.ts
40251
+ import { existsSync as existsSync36, readFileSync as readFileSync25, rmSync as rmSync4 } from "node:fs";
40252
+ function readCachedPluginVersion(pluginCacheDir) {
40253
+ try {
40254
+ const installedPkgPath = getOpenCodePluginPackageJsonPath(pluginCacheDir);
40255
+ if (!existsSync36(installedPkgPath))
40256
+ return;
40257
+ const pkg = JSON.parse(readFileSync25(installedPkgPath, "utf-8"));
40258
+ return typeof pkg.version === "string" ? pkg.version : undefined;
40259
+ } catch {
40260
+ return;
40261
+ }
40262
+ }
40263
+ async function clearPluginCache(options = {}, deps = {}) {
40264
+ const remove = deps.remove ?? ((path) => rmSync4(path, { recursive: true, force: true }));
40265
+ const pluginCacheRoots = getOpenCodePluginCacheRoots();
40266
+ const existingRoots = pluginCacheRoots.filter((root) => existsSync36(root));
40267
+ if (existingRoots.length === 0) {
40268
+ return { action: "not_found", path: pluginCacheRoots[0] ?? "" };
40269
+ }
40270
+ const latestVersion = options.latestVersion ?? undefined;
40271
+ const cacheEntries = existingRoots.map((path) => ({
40272
+ path,
40273
+ cached: readCachedPluginVersion(path)
40274
+ }));
40275
+ if (options.force !== true && latestVersion === undefined) {
40276
+ const firstEntry = cacheEntries[0];
40277
+ return {
40278
+ action: "check_unavailable",
40279
+ path: firstEntry?.path ?? pluginCacheRoots[0] ?? "",
40280
+ paths: cacheEntries.map((entry) => entry.path),
40281
+ cached: firstEntry?.cached
40282
+ };
40283
+ }
40284
+ const clearTargets = cacheEntries.filter((entry) => options.force === true || entry.cached === undefined || entry.cached !== latestVersion);
40285
+ if (clearTargets.length === 0) {
40286
+ const firstEntry = cacheEntries[0];
40287
+ return {
40288
+ action: "up_to_date",
40289
+ path: firstEntry?.path ?? pluginCacheRoots[0] ?? "",
40290
+ paths: cacheEntries.map((entry) => entry.path),
40291
+ cached: firstEntry?.cached,
40292
+ latest: latestVersion
40293
+ };
40294
+ }
40295
+ const cleared = [];
40296
+ const failed = [];
40297
+ for (const entry of clearTargets) {
40298
+ try {
40299
+ remove(entry.path);
40300
+ cleared.push(entry);
40301
+ } catch (err) {
40302
+ failed.push({
40303
+ path: entry.path,
40304
+ error: err instanceof Error ? err.message : String(err)
40305
+ });
40306
+ }
40307
+ }
40308
+ if (failed.length > 0) {
40309
+ const firstFailure = failed[0];
40310
+ return {
40311
+ action: "error",
40312
+ path: firstFailure?.path ?? clearTargets[0]?.path ?? existingRoots[0] ?? "",
40313
+ paths: failed.map((entry) => entry.path),
40314
+ clearedPaths: cleared.map((entry) => entry.path),
40315
+ failedPaths: failed.map((entry) => entry.path),
40316
+ error: firstFailure?.error
40317
+ };
40318
+ }
40319
+ const firstTarget = cleared[0];
40320
+ return {
40321
+ action: "cleared",
40322
+ path: firstTarget?.path ?? pluginCacheRoots[0] ?? "",
40323
+ paths: cleared.map((entry) => entry.path),
40324
+ cached: firstTarget?.cached,
40325
+ latest: latestVersion
40326
+ };
40327
+ }
40328
+ var init_doctor_opencode_cache = __esm(() => {
40329
+ init_opencode_plugin_cache();
40330
+ });
40331
+
40332
+ // src/commands/doctor-cached-plugin-fence.ts
40333
+ import { existsSync as existsSync37 } from "node:fs";
40334
+ import { dirname as dirname18 } from "node:path";
40335
+ function listCachedOpenCodePluginFences(sources = {}) {
40336
+ const fences = [];
40337
+ for (const root of sources.openCodeV1Roots ?? getOpenCodePluginCacheRoots()) {
40338
+ const packageJson = getOpenCodePluginPackageJsonPath(root);
40339
+ if (!existsSync37(packageJson))
40340
+ continue;
40341
+ fences.push({
40342
+ host: "opencode1",
40343
+ directory: root,
40344
+ version: readCachedPluginVersion(root),
40345
+ supportedVersion: readFenceFromDist(dirname18(packageJson))
40346
+ });
40347
+ }
40348
+ for (const slot of listOpenCodeV2PluginCacheSlots(sources.openCodeV2NpmCacheDir)) {
40349
+ if (!isOpenCodePluginDistTag(slot.spec))
40350
+ continue;
40351
+ const generation = getOpenCodeV2ActiveGeneration(slot.slot);
40352
+ if (!generation)
40353
+ continue;
40354
+ fences.push({
40355
+ host: "opencode2",
40356
+ directory: slot.slot,
40357
+ spec: slot.spec,
40358
+ version: slot.version,
40359
+ supportedVersion: readFenceFromDist(dirname18(getOpenCodePluginPackageJsonPath(generation)))
40360
+ });
40361
+ }
40362
+ return fences;
40363
+ }
40364
+ function readContextDbSchemaVersion(path) {
40365
+ let db = null;
40366
+ try {
40367
+ db = openExistingDatabase(path, { readonly: true });
40368
+ return db === null ? null : getPersistedSchemaVersion(db);
40369
+ } catch {
40370
+ return null;
40371
+ } finally {
40372
+ db?.close();
40373
+ }
40374
+ }
40375
+ function compareCachedPluginFences(fences, databaseVersion) {
40376
+ return fences.map((fence) => ({
40377
+ ...fence,
40378
+ databaseVersion,
40379
+ status: fence.supportedVersion === null ? "unknown" : fence.supportedVersion < databaseVersion ? "behind" : "supported"
40380
+ }));
40381
+ }
40382
+ function hostLabel(fence) {
40383
+ if (fence.host === "opencode1")
40384
+ return "OpenCode 1";
40385
+ return fence.spec && fence.spec !== "latest" ? `OpenCode 2 (@${fence.spec})` : "OpenCode 2";
40386
+ }
40387
+ function reportCachedPluginFences(findings, report) {
40388
+ const supported = [];
40389
+ let behind = 0;
40390
+ for (const finding of findings) {
40391
+ const label = hostLabel(finding);
40392
+ const version = finding.version ?? "unknown version";
40393
+ if (finding.status === "supported") {
40394
+ supported.push(`${label} ${version} (fence v${finding.supportedVersion})`);
40395
+ continue;
40396
+ }
40397
+ if (finding.status === "unknown") {
40398
+ report.info(`${label} plugin cache: could not read the schema fence of Magic Context ${version}, so doctor cannot tell whether it supports context.db v${finding.databaseVersion}`);
40399
+ report.info(` ${finding.directory}`);
40400
+ continue;
40401
+ }
40402
+ behind++;
40403
+ report.fail(`${label} has Magic Context ${version} cached, which supports context.db only through schema v${finding.supportedVersion}, but the shared context.db is at v${finding.databaseVersion}. A newer Magic Context (usually the other OpenCode host) migrated it, so ${label} fails closed on every prompt; an @latest entry does not refresh this cached copy.`);
40404
+ report.info(` ${finding.directory}`);
40405
+ report.info(finding.host === "opencode1" ? " To fix: quit OpenCode 1 and delete that directory; OpenCode 1 installs the current release on its next start." : ` To fix, ${OPENCODE_V2_PLUGIN_UPDATE_HINT}, then restart OpenCode 2.`);
40406
+ }
40407
+ if (supported.length > 0) {
40408
+ report.pass(`Cached Magic Context copies support context.db v${findings[0]?.databaseVersion}: ${supported.join(", ")}`);
40409
+ }
40410
+ return { behind };
40411
+ }
40412
+ var init_doctor_cached_plugin_fence = __esm(async () => {
40413
+ init_opencode_plugin_cache();
40414
+ init_opencode_plugin_schema_fence();
40415
+ init_doctor_opencode_cache();
40416
+ init_doctor_opencode2_cache();
40417
+ await __promiseAll([
40418
+ init_storage_db(),
40419
+ init_database_access()
40420
+ ]);
40421
+ });
40422
+
40049
40423
  // src/commands/doctor-compaction-markers.ts
40050
40424
  function safeData(column = "data") {
40051
40425
  return `CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END`;
@@ -40101,6 +40475,21 @@ function countUnmatchedConvertedMarkers(db, migratedV2Schema) {
40101
40475
  )`).get();
40102
40476
  return Number(row?.count ?? 0);
40103
40477
  }
40478
+ function countPostConversionMessages(db, migratedV2Schema) {
40479
+ if (!migratedV2Schema || !tableExists4(db, "message") || !tableExists4(db, "session")) {
40480
+ return { messages: 0, sessions: 0 };
40481
+ }
40482
+ const row = db.prepare(`SELECT COUNT(*) AS messages, COUNT(DISTINCT converted.session_id) AS sessions
40483
+ FROM session_message converted
40484
+ WHERE EXISTS (SELECT 1 FROM session legacy WHERE legacy.id = converted.session_id)
40485
+ AND NOT EXISTS (
40486
+ SELECT 1
40487
+ FROM message source
40488
+ WHERE source.session_id = converted.session_id
40489
+ AND source.time_created = converted.time_created
40490
+ )`).get();
40491
+ return { messages: Number(row?.messages ?? 0), sessions: Number(row?.sessions ?? 0) };
40492
+ }
40104
40493
  function checkOpenCodeCompactionMarkerConversion(db, options = {}) {
40105
40494
  const missingBefore = countMissingCompleted(db);
40106
40495
  let repaired = 0;
@@ -40121,6 +40510,7 @@ function checkOpenCodeCompactionMarkerConversion(db, options = {}) {
40121
40510
  const migratedV2Schema = tableExists4(db, "session_v2") && tableExists4(db, "session_message");
40122
40511
  const migrationCompleted = migrationV1ToV2Completed(db);
40123
40512
  const unmatchedConvertedMarkers = countUnmatchedConvertedMarkers(db, migratedV2Schema);
40513
+ const postConversion = countPostConversionMessages(db, migratedV2Schema);
40124
40514
  return {
40125
40515
  missingBefore,
40126
40516
  missingAfter,
@@ -40128,21 +40518,23 @@ function checkOpenCodeCompactionMarkerConversion(db, options = {}) {
40128
40518
  migrationCompleted,
40129
40519
  migratedV2Schema,
40130
40520
  unmatchedConvertedMarkers,
40131
- recoveryRequired: migrationCompleted && migratedV2Schema && unmatchedConvertedMarkers > 0
40521
+ postConversionMessages: postConversion.messages,
40522
+ postConversionSessions: postConversion.sessions
40132
40523
  };
40133
40524
  }
40134
40525
  function formatOpenCodeCompactionMarkerConversion(report) {
40135
40526
  return `OpenCode compaction markers are convertible to OpenCode 2: before=${report.missingBefore} missing time.completed; after=${report.missingAfter}`;
40136
40527
  }
40137
- function formatOpenCodeV2ReconversionRecipe(databasePath) {
40138
- return [
40139
- "OpenCode 2 already completed its v1→v2 conversion, but one or more Magic Context markers are absent from session_message.",
40140
- "Recovery (with every OpenCode host stopped): clear only the kv.migration.v1-v2 marker, then restart the host once so it reconverts the v1 rows. Do not edit session_v2 or session_message by hand.",
40141
- `Database: ${databasePath}`,
40142
- "In sqlite3, run: DELETE FROM kv WHERE key = 'migration.v1-v2';",
40143
- "Large-store conversion can take several minutes. `opencode service start` may kill the server as unresponsive while it runs; start `opencode serve --port N` by hand instead.",
40144
- 'Wait until `SELECT value FROM kv WHERE key = \'migration.v1-v2\';` reads `{"phase":"completed"}` before stopping that manual server'
40528
+ function formatOpenCodeV2MissingMarkerNotice(report) {
40529
+ if (!report.migrationCompleted || !report.migratedV2Schema || report.unmatchedConvertedMarkers === 0) {
40530
+ return null;
40531
+ }
40532
+ const lines = [
40533
+ `OpenCode 2's conversion did not carry over ${report.unmatchedConvertedMarkers} Magic Context compaction marker(s). No action is needed.`,
40534
+ "Magic Context on OpenCode 2 does not use host compaction rows: it keeps its own boundary and re-derives compartment positions from the messages that survived. OpenCode reads these sessions' full history on each turn, as it does for any Magic Context session started on OpenCode 2."
40145
40535
  ];
40536
+ lines.push(report.postConversionMessages > 0 ? `Do not clear kv.migration.v1-v2 to reconvert: OpenCode 2 would rebuild every converted session from its OpenCode 1 rows and delete the ${report.postConversionMessages} message(s) it added after the conversion, in ${report.postConversionSessions} session(s).` : "Do not clear kv.migration.v1-v2 to reconvert: OpenCode 2 would rebuild every converted session from its OpenCode 1 rows, deleting anything added on OpenCode 2 since.", "If you already reconverted, restore opencode.db (with its -wal and -shm files) from a backup taken before the reconversion.");
40537
+ return lines;
40146
40538
  }
40147
40539
 
40148
40540
  // src/commands/doctor-compartment-boundaries.ts
@@ -40226,88 +40618,6 @@ var init_doctor_harness_relabel = __esm(async () => {
40226
40618
  await init_opencode2_relabel();
40227
40619
  });
40228
40620
 
40229
- // src/commands/doctor-opencode-cache.ts
40230
- import { existsSync as existsSync36, readFileSync as readFileSync25, rmSync as rmSync4 } from "node:fs";
40231
- function readCachedPluginVersion(pluginCacheDir) {
40232
- try {
40233
- const installedPkgPath = getOpenCodePluginPackageJsonPath(pluginCacheDir);
40234
- if (!existsSync36(installedPkgPath))
40235
- return;
40236
- const pkg = JSON.parse(readFileSync25(installedPkgPath, "utf-8"));
40237
- return typeof pkg.version === "string" ? pkg.version : undefined;
40238
- } catch {
40239
- return;
40240
- }
40241
- }
40242
- async function clearPluginCache(options = {}, deps = {}) {
40243
- const remove = deps.remove ?? ((path) => rmSync4(path, { recursive: true, force: true }));
40244
- const pluginCacheRoots = getOpenCodePluginCacheRoots();
40245
- const existingRoots = pluginCacheRoots.filter((root) => existsSync36(root));
40246
- if (existingRoots.length === 0) {
40247
- return { action: "not_found", path: pluginCacheRoots[0] ?? "" };
40248
- }
40249
- const latestVersion = options.latestVersion ?? undefined;
40250
- const cacheEntries = existingRoots.map((path) => ({
40251
- path,
40252
- cached: readCachedPluginVersion(path)
40253
- }));
40254
- if (options.force !== true && latestVersion === undefined) {
40255
- const firstEntry = cacheEntries[0];
40256
- return {
40257
- action: "check_unavailable",
40258
- path: firstEntry?.path ?? pluginCacheRoots[0] ?? "",
40259
- paths: cacheEntries.map((entry) => entry.path),
40260
- cached: firstEntry?.cached
40261
- };
40262
- }
40263
- const clearTargets = cacheEntries.filter((entry) => options.force === true || entry.cached === undefined || entry.cached !== latestVersion);
40264
- if (clearTargets.length === 0) {
40265
- const firstEntry = cacheEntries[0];
40266
- return {
40267
- action: "up_to_date",
40268
- path: firstEntry?.path ?? pluginCacheRoots[0] ?? "",
40269
- paths: cacheEntries.map((entry) => entry.path),
40270
- cached: firstEntry?.cached,
40271
- latest: latestVersion
40272
- };
40273
- }
40274
- const cleared = [];
40275
- const failed = [];
40276
- for (const entry of clearTargets) {
40277
- try {
40278
- remove(entry.path);
40279
- cleared.push(entry);
40280
- } catch (err) {
40281
- failed.push({
40282
- path: entry.path,
40283
- error: err instanceof Error ? err.message : String(err)
40284
- });
40285
- }
40286
- }
40287
- if (failed.length > 0) {
40288
- const firstFailure = failed[0];
40289
- return {
40290
- action: "error",
40291
- path: firstFailure?.path ?? clearTargets[0]?.path ?? existingRoots[0] ?? "",
40292
- paths: failed.map((entry) => entry.path),
40293
- clearedPaths: cleared.map((entry) => entry.path),
40294
- failedPaths: failed.map((entry) => entry.path),
40295
- error: firstFailure?.error
40296
- };
40297
- }
40298
- const firstTarget = cleared[0];
40299
- return {
40300
- action: "cleared",
40301
- path: firstTarget?.path ?? pluginCacheRoots[0] ?? "",
40302
- paths: cleared.map((entry) => entry.path),
40303
- cached: firstTarget?.cached,
40304
- latest: latestVersion
40305
- };
40306
- }
40307
- var init_doctor_opencode_cache = __esm(() => {
40308
- init_opencode_plugin_cache();
40309
- });
40310
-
40311
40621
  // src/commands/doctor-opencode-plugin-duplicates.ts
40312
40622
  import { isDeepStrictEqual } from "node:util";
40313
40623
  function entryOptions(entry) {
@@ -40535,10 +40845,21 @@ function formatUnresolvedCompartmentSession(session) {
40535
40845
 
40536
40846
  // src/commands/doctor-opencode.ts
40537
40847
  import { spawnSync as spawnSync8 } from "node:child_process";
40538
- import { existsSync as existsSync37, mkdtempSync, readFileSync as readFileSync26, rmSync as rmSync5 } from "node:fs";
40848
+ import { existsSync as existsSync38, mkdtempSync, readFileSync as readFileSync26, rmSync as rmSync5 } from "node:fs";
40539
40849
  import { createRequire as createRequire6 } from "node:module";
40540
40850
  import { homedir as homedir16, tmpdir as tmpdir3 } from "node:os";
40541
40851
  import { join as join32 } from "node:path";
40852
+ function formatSharedDbRowCounts(db) {
40853
+ return SHARED_DB_ROW_COUNT_TABLES.map((table) => {
40854
+ try {
40855
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get();
40856
+ return `${table}=${typeof row?.c === "number" ? row.c : "n/a"}`;
40857
+ } catch (error) {
40858
+ const reason = error instanceof Error ? error.message : String(error);
40859
+ return `${table}=n/a (${sanitizeDiagnosticText(reason)})`;
40860
+ }
40861
+ }).join(", ");
40862
+ }
40542
40863
  function findUndeclaredConfiguredVariants(configured, catalog) {
40543
40864
  return configured.filter((entry) => {
40544
40865
  const [providerID, id] = entry.model.split("/", 2);
@@ -40546,6 +40867,18 @@ function findUndeclaredConfiguredVariants(configured, catalog) {
40546
40867
  return model !== undefined && !Object.hasOwn(model.variants, entry.variant);
40547
40868
  });
40548
40869
  }
40870
+ function findUnknownConfiguredModels(configured, catalog) {
40871
+ return configured.flatMap((entry) => {
40872
+ const [providerID, id] = entry.model.split("/", 2);
40873
+ if (!catalog.some((item) => item.providerID === providerID)) {
40874
+ return [{ ...entry, unknown: "provider" }];
40875
+ }
40876
+ if (!catalog.some((item) => item.providerID === providerID && item.id === id)) {
40877
+ return [{ ...entry, unknown: "model" }];
40878
+ }
40879
+ return [];
40880
+ });
40881
+ }
40549
40882
  function checkConfiguredVariantCatalog(config, hostGeneration, warn, run = (args, cwd) => spawnSync8("opencode", args, {
40550
40883
  cwd,
40551
40884
  encoding: "utf8",
@@ -40553,6 +40886,7 @@ function checkConfiguredVariantCatalog(config, hostGeneration, warn, run = (args
40553
40886
  maxBuffer: 16 * 1024 * 1024
40554
40887
  }), projectDir = process.cwd()) {
40555
40888
  const configured = [];
40889
+ const models = [];
40556
40890
  const root = config && typeof config === "object" ? config : {};
40557
40891
  for (const agent of ["historian", "dreamer"]) {
40558
40892
  const section = root[agent];
@@ -40562,21 +40896,25 @@ function checkConfiguredVariantCatalog(config, hostGeneration, warn, run = (args
40562
40896
  if (!block || typeof block !== "object")
40563
40897
  continue;
40564
40898
  const record = block;
40565
- const add = (entry, defaultVariant) => {
40899
+ const add = (entry, defaultVariant, fallback) => {
40566
40900
  const value = entry && typeof entry === "object" ? entry : {};
40567
40901
  const model = typeof entry === "string" ? entry : value.model;
40568
40902
  const variant = value.variant ?? defaultVariant;
40569
- if (typeof model === "string" && typeof variant === "string" && model.includes("/")) {
40570
- configured.push({ agent, model, variant });
40903
+ if (typeof model !== "string" || !model.includes("/"))
40904
+ return;
40905
+ if (!models.some((known) => known.agent === agent && known.model === model)) {
40906
+ models.push({ agent, model, fallback });
40571
40907
  }
40908
+ if (typeof variant === "string")
40909
+ configured.push({ agent, model, variant });
40572
40910
  };
40573
- add(record.model, record.variant);
40911
+ add(record.model, record.variant, false);
40574
40912
  if (Array.isArray(record.fallback_models)) {
40575
40913
  for (const entry of record.fallback_models)
40576
- add(entry, undefined);
40914
+ add(entry, undefined, true);
40577
40915
  }
40578
40916
  }
40579
- if (configured.length === 0)
40917
+ if (models.length === 0)
40580
40918
  return;
40581
40919
  const tempRoot = mkdtempSync(join32(tmpdir3(), "magic-context-opencode-catalog-"));
40582
40920
  try {
@@ -40585,9 +40923,14 @@ function checkConfiguredVariantCatalog(config, hostGeneration, warn, run = (args
40585
40923
  const result = run(command, tempRoot);
40586
40924
  const catalog = hostGeneration === "v2" ? parseOpenCodeV2ModelCatalog(result.stdout ?? "") : parseOpenCodeModelCatalog(result.stdout ?? "");
40587
40925
  if (result.error || result.status !== 0 || catalog.length === 0) {
40588
- warn(`Could not verify configured hidden-agent variants: this OpenCode host did not provide a readable model catalog. ${hostGeneration === "v2" ? "Start the background service with opencode service start, then check" : "Check"} ${guidance}.`);
40926
+ warn(`Could not verify configured hidden-agent models and variants: this OpenCode host did not provide a readable model catalog. ${hostGeneration === "v2" ? "Start the background service with opencode service start, then check" : "Check"} ${guidance}.`);
40589
40927
  return;
40590
40928
  }
40929
+ for (const entry of findUnknownConfiguredModels(models, catalog)) {
40930
+ const [providerID] = entry.model.split("/", 1);
40931
+ const role = `${entry.agent}${entry.fallback ? " fallback" : ""} model ${entry.model}`;
40932
+ warn(entry.unknown === "provider" ? `${role} names provider '${providerID}', which this OpenCode host does not have. The ${entry.agent} cannot run on it; configure that provider in OpenCode or choose a model listed by ${guidance}.` : `${role} is not offered by provider '${providerID}' on this OpenCode host. The ${entry.agent} cannot run on it; choose a model listed by ${guidance}.`);
40933
+ }
40591
40934
  for (const entry of findUndeclaredConfiguredVariants(configured, catalog)) {
40592
40935
  warn(`${entry.agent} model ${entry.model} requests variant '${entry.variant}', which this host does not offer. Remove the variant or choose one listed by ${guidance}.`);
40593
40936
  }
@@ -40763,7 +41106,7 @@ function getUserNpmrcPath() {
40763
41106
  function collectNpmReleaseAgeWarnings() {
40764
41107
  const ageWarnings = [];
40765
41108
  const npmrcPath = getUserNpmrcPath();
40766
- if (!existsSync37(npmrcPath))
41109
+ if (!existsSync38(npmrcPath))
40767
41110
  return ageWarnings;
40768
41111
  try {
40769
41112
  const npmrc = readFileSync26(npmrcPath, "utf-8");
@@ -40900,7 +41243,7 @@ function checkLocalEmbeddingRuntimeForDoctor(runtimePreference = "auto") {
40900
41243
  return { issues: 0 };
40901
41244
  }
40902
41245
  async function checkEmbeddingConfig(magicContextConfigPath) {
40903
- if (!existsSync37(magicContextConfigPath)) {
41246
+ if (!existsSync38(magicContextConfigPath)) {
40904
41247
  return checkLocalEmbeddingRuntimeForDoctor();
40905
41248
  }
40906
41249
  let rawText;
@@ -41108,7 +41451,7 @@ async function runDoctor2(options = {}) {
41108
41451
  const activeInstallation = installationReports[0];
41109
41452
  if (!activeInstallation) {
41110
41453
  fail("OpenCode is not installed or not in PATH");
41111
- log2.info("Doctor checked ~/.opencode/bin/opencode and each entry in $PATH.");
41454
+ log2.info("Doctor checked ~/.opencode/bin/opencode, each entry in $PATH, and the OpenCode CLI bundled in OpenChamber.app.");
41112
41455
  log2.info("If `which opencode` succeeds outside doctor, your wrapper or shim may not be readable by Node — please share that wrapper in the issue.");
41113
41456
  outro2("Doctor failed — install OpenCode first");
41114
41457
  return 1;
@@ -41124,7 +41467,19 @@ async function runDoctor2(options = {}) {
41124
41467
  pass(installationReports.length > 1 ? `OpenCode ${activeInstallation.version} installed (active install marked above)` : `OpenCode ${activeInstallation.version} installed`);
41125
41468
  }
41126
41469
  const hostGeneration = openCodeHostGenerationFromVersion(activeInstallation.version);
41127
- const openCodeDbResolution = resolveOpenCodeDbPath(hostGeneration);
41470
+ const storeHostSelection = selectOpenCodeStoreHost(installationReports, openCodeHostGenerationFromVersion);
41471
+ const storeHost = storeHostSelection?.host ?? activeInstallation;
41472
+ const storeGeneration = openCodeHostGenerationFromVersion(storeHost.version);
41473
+ const openCodeDbResolution = resolveOpenCodeDbPath(storeGeneration);
41474
+ if (storeHostSelection?.shadowed) {
41475
+ const activeStore = resolveOpenCodeDbPath(hostGeneration).path;
41476
+ const shared = activeStore === openCodeDbResolution.path;
41477
+ const versionLabel = (version) => version.replace(/^opencode\s+v?/i, "");
41478
+ const activeVersion = versionLabel(activeInstallation.version);
41479
+ const storeVersion = versionLabel(storeHost.version);
41480
+ warn(`OpenCode ${activeVersion} (${activeInstallation.path}) is first on PATH, and OpenCode ${storeVersion} is also installed (${storeHost.path})${shared ? `; both use ${openCodeDbResolution.path}` : ""}.`);
41481
+ log2.warn(` Store and conversion checks below use OpenCode ${storeVersion}. Plugin configuration checks use OpenCode ${activeVersion}; to check OpenCode ${storeVersion}'s configuration instead, put its binary first on PATH and run doctor again.`);
41482
+ }
41128
41483
  const openCodeDbCheck = describeOpenCodeDatabaseDoctorCheck(openCodeDbResolution);
41129
41484
  if (openCodeDbCheck.ok)
41130
41485
  pass(openCodeDbCheck.message);
@@ -41148,14 +41503,16 @@ async function runDoctor2(options = {}) {
41148
41503
  fixed += report.repaired;
41149
41504
  } else if (options.fix) {
41150
41505
  warn(`${summary}; repaired=${report.repaired}, but some rows remain unconvertible`);
41506
+ } else if (report.migrationCompleted) {
41507
+ log2.info(`${summary}; OpenCode 2 already converted this store`);
41151
41508
  } else {
41152
41509
  warn(`${summary}; run \`magic-context doctor --fix\` before upgrading OpenCode`);
41153
41510
  }
41154
- if (report.recoveryRequired) {
41155
- const [heading, ...details] = formatOpenCodeV2ReconversionRecipe(openCodeDbResolution.path);
41156
- warn(heading ?? "OpenCode 2 conversion recovery is required");
41157
- for (const detail of details)
41158
- log2.warn(` ${detail}`);
41511
+ const notice = formatOpenCodeV2MissingMarkerNotice(report);
41512
+ if (notice) {
41513
+ for (const [index, line] of notice.entries()) {
41514
+ log2.info(index === 0 ? line : ` ${line}`);
41515
+ }
41159
41516
  }
41160
41517
  } catch (error) {
41161
41518
  warn(`OpenCode compaction marker conversion check unavailable: ${error instanceof Error ? error.message : String(error)}`);
@@ -41171,9 +41528,14 @@ async function runDoctor2(options = {}) {
41171
41528
  readonly: true,
41172
41529
  fileMustExist: true
41173
41530
  });
41174
- const dangling = listDanglingCompartmentBoundaries(contextDb, sessionDb, /\d/.test(activeInstallation.version) ? hostGeneration : undefined, (line) => log2.info(line));
41531
+ const dangling = listDanglingCompartmentBoundaries(contextDb, sessionDb, /\d/.test(storeHost.version) ? storeGeneration : undefined, (line) => log2.info(line));
41175
41532
  if (dangling.length === 0) {
41176
41533
  pass("Compartment boundary ids resolve in the OpenCode session store");
41534
+ } else if (storeGeneration === "v2" && /\d/.test(storeHost.version)) {
41535
+ log2.info(`${dangling.length} compartment(s) point at OpenCode message ids that are not in the OpenCode 2 store. Magic Context places these from the neighbouring compartments; any it cannot place are listed as excluded from range recovery below.`);
41536
+ for (const boundary of dangling) {
41537
+ log2.info(` ${formatDanglingCompartmentBoundary(boundary)}`);
41538
+ }
41177
41539
  } else {
41178
41540
  warn(`${dangling.length} compartment(s) have dangling OpenCode boundary ids`);
41179
41541
  for (const boundary of dangling) {
@@ -41182,11 +41544,11 @@ async function runDoctor2(options = {}) {
41182
41544
  }
41183
41545
  if (!supportsCoordinateGenerationReporting(contextDb)) {
41184
41546
  log2.info("Store projection check: this context database predates the coordinate columns");
41185
- } else if (!/\d/.test(activeInstallation.version)) {
41547
+ } else if (!/\d/.test(storeHost.version)) {
41186
41548
  log2.info("Store projection check: OpenCode reported no version, so the running projection is unknown");
41187
41549
  } else {
41188
- const pendingRebases = countPendingCoordinateRebases(contextDb, hostGeneration);
41189
- const pendingLine = formatPendingCoordinateRebases(pendingRebases, hostGeneration);
41550
+ const pendingRebases = countPendingCoordinateRebases(contextDb, storeGeneration);
41551
+ const pendingLine = formatPendingCoordinateRebases(pendingRebases, storeGeneration);
41190
41552
  if (pendingRebases.changed + pendingRebases.unrecorded === 0) {
41191
41553
  pass(pendingLine);
41192
41554
  } else {
@@ -41231,7 +41593,7 @@ async function runDoctor2(options = {}) {
41231
41593
  pass(`OpenCode config: ${paths.opencodeConfig}`);
41232
41594
  }
41233
41595
  let autoUpdateEnabled = true;
41234
- if (existsSync37(paths.magicContextConfig)) {
41596
+ if (existsSync38(paths.magicContextConfig)) {
41235
41597
  pass(`Magic Context config: ${paths.magicContextConfig}`);
41236
41598
  try {
41237
41599
  const raw = loadRawConfigFile({ configPath: paths.magicContextConfig, tier: "user" });
@@ -41267,7 +41629,7 @@ async function runDoctor2(options = {}) {
41267
41629
  warn(`No magic-context.jsonc found — using defaults`);
41268
41630
  log2.info(" Run 'setup' to create one with model recommendations");
41269
41631
  }
41270
- if (existsSync37(paths.magicContextConfig)) {
41632
+ if (existsSync38(paths.magicContextConfig)) {
41271
41633
  try {
41272
41634
  const raw = loadRawConfigFile({ configPath: paths.magicContextConfig, tier: "user" });
41273
41635
  if (!raw)
@@ -41466,7 +41828,7 @@ async function runDoctor2(options = {}) {
41466
41828
  pass("Added TUI sidebar plugin to tui.json");
41467
41829
  warn("Restart OpenCode to see the sidebar");
41468
41830
  fixed++;
41469
- } else if (existsSync37(paths.tuiConfig)) {
41831
+ } else if (existsSync38(paths.tuiConfig)) {
41470
41832
  try {
41471
41833
  const tuiRaw = readFileSync26(paths.tuiConfig, "utf-8");
41472
41834
  const tuiConfig = import_comment_json11.parse(tuiRaw);
@@ -41506,7 +41868,7 @@ async function runDoctor2(options = {}) {
41506
41868
  fail("Could not create or verify the TUI sidebar config");
41507
41869
  }
41508
41870
  }
41509
- if (existsSync37(paths.magicContextConfig)) {
41871
+ if (existsSync38(paths.magicContextConfig)) {
41510
41872
  try {
41511
41873
  const raw = loadRawConfigFile({ configPath: paths.magicContextConfig, tier: "user" });
41512
41874
  if (!raw)
@@ -41530,7 +41892,7 @@ async function runDoctor2(options = {}) {
41530
41892
  const storage = getMagicContextStorageResolution();
41531
41893
  const dbPath = join32(storage.path, "context.db");
41532
41894
  log2.info(`Shared storage: ${storage.path} (source: ${storage.source})`);
41533
- if (!existsSync37(dbPath)) {
41895
+ if (!existsSync38(dbPath)) {
41534
41896
  log2.info(`Shared context DB not yet created at ${dbPath} (will be created on first run)`);
41535
41897
  } else {
41536
41898
  log2.info(`Shared context DB exists at ${dbPath}`);
@@ -41577,25 +41939,7 @@ ${formatDatabaseRepairGuidance(dbPath)}`);
41577
41939
  fail(`SQLite integrity_check failed: ${err instanceof Error ? err.message : String(err)}
41578
41940
  ${formatDatabaseRepairGuidance(dbPath)}`);
41579
41941
  }
41580
- try {
41581
- const counts = {};
41582
- for (const table of [
41583
- "tags",
41584
- "compartments",
41585
- "memories",
41586
- "notes",
41587
- "dream_runs"
41588
- ]) {
41589
- try {
41590
- const row = db.prepare(`SELECT COUNT(*) as c FROM ${table}`).get();
41591
- counts[table] = row?.c ?? 0;
41592
- } catch {
41593
- counts[table] = 0;
41594
- }
41595
- }
41596
- const summary = Object.entries(counts).map(([k, v]) => `${k}=${v}`).join(", ");
41597
- log2.info(`Shared DB row counts: ${summary}`);
41598
- } catch {}
41942
+ log2.info(`Shared DB row counts: ${formatSharedDbRowCounts(db)}`);
41599
41943
  for (const stall of listShadowBackfillStalls(db)) {
41600
41944
  warn(formatShadowBackfillStall(stall));
41601
41945
  }
@@ -41664,6 +42008,10 @@ ${formatDatabaseRepairGuidance(dbPath)}`);
41664
42008
  fixed++;
41665
42009
  if (v2Cache.issue)
41666
42010
  issues++;
42011
+ const sharedDbVersion = readContextDbSchemaVersion(dbPath);
42012
+ if (sharedDbVersion !== null) {
42013
+ reportCachedPluginFences(compareCachedPluginFences(listCachedOpenCodePluginFences(), sharedDbVersion), { pass, fail, info: (message) => log2.info(message) });
42014
+ }
41667
42015
  {
41668
42016
  const ageWarnings = collectNpmReleaseAgeWarnings();
41669
42017
  if (ageWarnings.length > 0) {
@@ -41676,7 +42024,11 @@ ${formatDatabaseRepairGuidance(dbPath)}`);
41676
42024
  issues++;
41677
42025
  }
41678
42026
  }
41679
- const logFiles = inspectMagicContextLogs("opencode");
42027
+ const logHarnesses = [
42028
+ ...hostGeneration === "v1" ? ["opencode"] : [],
42029
+ ...hostGeneration === "v2" || storeGeneration === "v2" ? ["opencode2"] : []
42030
+ ];
42031
+ const logFiles = logHarnesses.flatMap((harness) => inspectMagicContextLogs(harness)).filter((file, index, all) => all.findIndex((other) => other.path === file.path) === index);
41680
42032
  const existingLogFiles = logFiles.filter((file) => file.exists);
41681
42033
  if (existingLogFiles.length === 0) {
41682
42034
  log2.info(`No plugin log file yet; checked: ${logFiles.map((file) => file.path).join(", ")}`);
@@ -41724,7 +42076,7 @@ ${formatDatabaseRepairGuidance(dbPath)}`);
41724
42076
  }
41725
42077
  return 0;
41726
42078
  }
41727
- var import_comment_json11, CLI_PACKAGE_NAME = "@cortexkit/magic-context";
42079
+ var import_comment_json11, CLI_PACKAGE_NAME = "@cortexkit/magic-context", SHARED_DB_ROW_COUNT_TABLES;
41728
42080
  var init_doctor_opencode = __esm(async () => {
41729
42081
  init_config();
41730
42082
  init_agent_disable();
@@ -41768,13 +42120,15 @@ var init_doctor_opencode = __esm(async () => {
41768
42120
  init_storage_versions(),
41769
42121
  init_v22_backfill_commands(),
41770
42122
  init_doctor_authority(),
42123
+ init_doctor_cached_plugin_fence(),
41771
42124
  init_doctor_harness_relabel()
41772
42125
  ]);
41773
42126
  import_comment_json11 = __toESM(require_src3(), 1);
42127
+ SHARED_DB_ROW_COUNT_TABLES = ["tags", "compartments", "memories", "notes", "dream_runs"];
41774
42128
  });
41775
42129
 
41776
42130
  // src/lib/diagnostics-pi.ts
41777
- import { existsSync as existsSync38, readdirSync as readdirSync7, readFileSync as readFileSync27, statSync as statSync16 } from "node:fs";
42131
+ import { existsSync as existsSync39, readdirSync as readdirSync7, readFileSync as readFileSync27, statSync as statSync16 } from "node:fs";
41778
42132
  import { createRequire as createRequire7 } from "node:module";
41779
42133
  import { join as join33 } from "node:path";
41780
42134
  function getSelfVersion3() {
@@ -41791,7 +42145,7 @@ function getSelfVersion3() {
41791
42145
  }
41792
42146
  function fileSize2(path) {
41793
42147
  try {
41794
- return existsSync38(path) ? statSync16(path).size : 0;
42148
+ return existsSync39(path) ? statSync16(path).size : 0;
41795
42149
  } catch {
41796
42150
  return 0;
41797
42151
  }
@@ -41803,7 +42157,7 @@ function sanitizeValue2(value) {
41803
42157
  return sanitizeConfigValue(value);
41804
42158
  }
41805
42159
  function readJsonc(path) {
41806
- if (!existsSync38(path))
42160
+ if (!existsSync39(path))
41807
42161
  return { value: {} };
41808
42162
  try {
41809
42163
  return {
@@ -41823,7 +42177,7 @@ function readConfigDiagnostic(path) {
41823
42177
  const parsed = readJsonc(path);
41824
42178
  return {
41825
42179
  path,
41826
- exists: existsSync38(path),
42180
+ exists: existsSync39(path),
41827
42181
  ...parsed.parseError ? { parseError: parsed.parseError } : {},
41828
42182
  flags: sanitizeValue2(parsed.value)
41829
42183
  };
@@ -41841,7 +42195,7 @@ function reverseSlugToDirectory(slug) {
41841
42195
  }
41842
42196
  function collectPiRecentSessions() {
41843
42197
  const sessionsRoot = getPiSessionsRoot();
41844
- if (!existsSync38(sessionsRoot))
42198
+ if (!existsSync39(sessionsRoot))
41845
42199
  return [];
41846
42200
  try {
41847
42201
  const slugs = readdirSync7(sessionsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -41910,7 +42264,7 @@ function parseHistorianDumpMeta2(path) {
41910
42264
  }
41911
42265
  }
41912
42266
  function listDumpsInDir2(dir, limit) {
41913
- if (!existsSync38(dir))
42267
+ if (!existsSync39(dir))
41914
42268
  return { count: 0, recent: [] };
41915
42269
  try {
41916
42270
  const entries = readdirSync7(dir).filter((name) => name.endsWith(".xml")).map((name) => {
@@ -42004,7 +42358,7 @@ async function collectDiagnostics2(cwd = process.cwd()) {
42004
42358
  piVersion: pi ? getPiVersion(pi.path) : null,
42005
42359
  settings: {
42006
42360
  path: settingsPath,
42007
- exists: existsSync38(settingsPath),
42361
+ exists: existsSync39(settingsPath),
42008
42362
  ...settingsParsed.parseError ? { parseError: settingsParsed.parseError } : {},
42009
42363
  hasMagicContextPackage: hasPiMagicContextPackage(packages),
42010
42364
  packages: sanitizeValue2(packages)
@@ -42021,7 +42375,7 @@ async function collectDiagnostics2(cwd = process.cwd()) {
42021
42375
  storageDir: {
42022
42376
  path: storageDirPath,
42023
42377
  source: storageResolution.source,
42024
- exists: existsSync38(storageDirPath),
42378
+ exists: existsSync39(storageDirPath),
42025
42379
  contextDbSizeBytes: fileSize2(dbPath)
42026
42380
  },
42027
42381
  conflicts: {
@@ -42127,7 +42481,7 @@ var init_diagnostics_pi = __esm(() => {
42127
42481
 
42128
42482
  // src/lib/logs-pi.ts
42129
42483
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
42130
- import { dirname as dirname18, join as join34 } from "node:path";
42484
+ import { dirname as dirname19, join as join34 } from "node:path";
42131
42485
  function sanitizeLogContent2(content) {
42132
42486
  return sanitizeString2(content);
42133
42487
  }
@@ -42198,7 +42552,7 @@ async function bundleIssueReport2(report, description, title, options = {}) {
42198
42552
  const cwd = options.cwd ?? process.cwd();
42199
42553
  const timestamp = formatTimestamp2(options.now ?? new Date);
42200
42554
  const path = options.outputPath ?? join34(cwd, `magic-context-pi-issue-${timestamp}.md`);
42201
- mkdirSync10(dirname18(path), { recursive: true });
42555
+ mkdirSync10(dirname19(path), { recursive: true });
42202
42556
  writeFileSync9(path, `${bodyMarkdown}
42203
42557
  `);
42204
42558
  const fullPath = truncated ? options.outputPath ? `${options.outputPath}.full.md` : join34(cwd, `magic-context-pi-issue-${timestamp}-full.md`) : undefined;
@@ -42215,10 +42569,10 @@ var init_logs_pi = __esm(() => {
42215
42569
 
42216
42570
  // src/commands/doctor-pi.ts
42217
42571
  import { execFileSync as execFileSync6, spawnSync as spawnSync9 } from "node:child_process";
42218
- import { existsSync as existsSync39, mkdirSync as mkdirSync11, readdirSync as readdirSync8, readFileSync as readFileSync28, rmSync as rmSync6, statSync as statSync17 } from "node:fs";
42572
+ import { existsSync as existsSync40, mkdirSync as mkdirSync11, readdirSync as readdirSync8, readFileSync as readFileSync28, rmSync as rmSync6, statSync as statSync17 } from "node:fs";
42219
42573
  import { createRequire as createRequire8 } from "node:module";
42220
42574
  import { homedir as homedir17 } from "node:os";
42221
- import { dirname as dirname19, isAbsolute as isAbsolute8, join as join35 } from "node:path";
42575
+ import { dirname as dirname20, isAbsolute as isAbsolute8, join as join35 } from "node:path";
42222
42576
  function depsFrom(options) {
42223
42577
  return {
42224
42578
  ...DEFAULT_DEPS5,
@@ -42361,13 +42715,13 @@ function piPluginDirCandidates(packages, cwd) {
42361
42715
  }
42362
42716
  dirs.push(join35(agentDir, "npm", "node_modules", PACKAGE_NAME2));
42363
42717
  dirs.push(join35(cwd, ".pi", "npm", "node_modules", PACKAGE_NAME2));
42364
- return dirs.filter((dir) => existsSync39(join35(dir, "package.json")));
42718
+ return dirs.filter((dir) => existsSync40(join35(dir, "package.json")));
42365
42719
  }
42366
42720
  function projectConfigPath(cwd) {
42367
42721
  return resolveCortexKitProjectConfigPath(cwd);
42368
42722
  }
42369
42723
  function readConfigForEmbedding(path, isProjectConfig) {
42370
- if (!existsSync39(path))
42724
+ if (!existsSync40(path))
42371
42725
  return null;
42372
42726
  try {
42373
42727
  const raw = loadRawConfigFile({
@@ -42468,7 +42822,7 @@ function cacheRoots(cwd) {
42468
42822
  function findPiMagicContextCacheDirs(cwd, expectedVersion, force = false) {
42469
42823
  const found = new Map;
42470
42824
  const visit = (path, depth) => {
42471
- if (depth < 0 || !existsSync39(path))
42825
+ if (depth < 0 || !existsSync40(path))
42472
42826
  return;
42473
42827
  let stat;
42474
42828
  try {
@@ -42479,7 +42833,7 @@ function findPiMagicContextCacheDirs(cwd, expectedVersion, force = false) {
42479
42833
  if (!stat.isDirectory())
42480
42834
  return;
42481
42835
  const packageJson = join35(path, "package.json");
42482
- if (existsSync39(packageJson)) {
42836
+ if (existsSync40(packageJson)) {
42483
42837
  try {
42484
42838
  const pkg = JSON.parse(readFileSync28(packageJson, "utf-8"));
42485
42839
  const name = typeof pkg.name === "string" ? pkg.name : "";
@@ -42546,7 +42900,7 @@ async function runHealthChecks2(options) {
42546
42900
  }
42547
42901
  const settingsPath = getPiUserExtensionsPath();
42548
42902
  let packages = [];
42549
- if (!existsSync39(settingsPath)) {
42903
+ if (!existsSync40(settingsPath)) {
42550
42904
  add2(results, "fail", `Pi settings not found at ${settingsPath}`);
42551
42905
  repairPlan.addPackageEntry = true;
42552
42906
  } else {
@@ -42570,7 +42924,7 @@ async function runHealthChecks2(options) {
42570
42924
  ["user", userConfigPath, true],
42571
42925
  ["project", projectPath, false]
42572
42926
  ]) {
42573
- if (!existsSync39(path)) {
42927
+ if (!existsSync40(path)) {
42574
42928
  if (required) {
42575
42929
  add2(results, "warn", `No ${label} magic-context.jsonc found at ${path}`);
42576
42930
  if (userConfigMigrationRefused) {
@@ -42612,7 +42966,7 @@ async function runHealthChecks2(options) {
42612
42966
  const storageDir = storage.path;
42613
42967
  const dbPath = join35(storageDir, "context.db");
42614
42968
  add2(results, "info", `Shared storage: ${storageDir} (source: ${storage.source})`);
42615
- const existedBeforeOpen = existsSync39(dbPath);
42969
+ const existedBeforeOpen = existsSync40(dbPath);
42616
42970
  if (existedBeforeOpen)
42617
42971
  add2(results, "pass", `Shared context DB exists at ${dbPath}`);
42618
42972
  else
@@ -42830,7 +43184,7 @@ ${formatDatabaseRepairGuidance(dbPath)}`);
42830
43184
  return { results, repairPlan, ...summarize(results) };
42831
43185
  }
42832
43186
  function writeDefaultMagicContextConfig(path) {
42833
- mkdirSync11(dirname19(path), { recursive: true });
43187
+ mkdirSync11(dirname20(path), { recursive: true });
42834
43188
  const config = {
42835
43189
  $schema: "https://raw.githubusercontent.com/cortexkit/magic-context/master/assets/magic-context.schema.json",
42836
43190
  ...MagicContextConfigSchema.parse({})
@@ -42852,7 +43206,7 @@ function repair2(plan, prompts) {
42852
43206
  }
42853
43207
  if (plan.writeUserConfig) {
42854
43208
  const configPath = getPiUserConfigPath();
42855
- if (!existsSync39(configPath)) {
43209
+ if (!existsSync40(configPath)) {
42856
43210
  try {
42857
43211
  writeDefaultMagicContextConfig(configPath);
42858
43212
  prompts.log.success(`Wrote default Magic Context config to ${configPath}`);
@@ -43104,7 +43458,7 @@ var init_doctor_pi = __esm(async () => {
43104
43458
  });
43105
43459
 
43106
43460
  // src/commands/doctor.ts
43107
- import { existsSync as existsSync40, rmSync as rmSync7 } from "node:fs";
43461
+ import { existsSync as existsSync41, rmSync as rmSync7 } from "node:fs";
43108
43462
  import { join as join36 } from "node:path";
43109
43463
  async function runDoctor4(options) {
43110
43464
  if (options.clear)
@@ -43251,7 +43605,7 @@ async function runClear() {
43251
43605
  failed += 1;
43252
43606
  continue;
43253
43607
  }
43254
- } else if (existsSync40(item.path)) {
43608
+ } else if (existsSync41(item.path)) {
43255
43609
  rmSync7(item.path, { recursive: true, force: true });
43256
43610
  }
43257
43611
  s.stop(`Cleared ${item.path}`);
@@ -43288,9 +43642,121 @@ var init_doctor = __esm(async () => {
43288
43642
  });
43289
43643
 
43290
43644
  // src/index.ts
43291
- init_prompts();
43292
43645
  import { createRequire as createRequire9 } from "node:module";
43293
43646
 
43647
+ // src/lib/cli-help.ts
43648
+ var HELP_FLAGS = new Set(["--help", "-h"]);
43649
+ function hasHelpFlag(args) {
43650
+ return args.some((arg) => HELP_FLAGS.has(arg));
43651
+ }
43652
+ var DOCTOR_SUBCOMMANDS_WITH_OWN_HELP = new Set([
43653
+ "migrate",
43654
+ "migrate-session",
43655
+ "repair-db"
43656
+ ]);
43657
+ var HARNESS_LINES = [
43658
+ " Harness selection:",
43659
+ " --harness opencode Target OpenCode only",
43660
+ " --harness pi Target Pi only",
43661
+ " --harness omp Target Oh My Pi (OMP) only",
43662
+ " (default: auto-detect, prompt if multiple installed)"
43663
+ ];
43664
+ var SETUP_HELP = [
43665
+ "",
43666
+ " Usage: magic-context setup [--harness opencode|pi|omp] [--dry-run]",
43667
+ "",
43668
+ " Interactive setup wizard: registers the Magic Context plugin with the",
43669
+ " selected harness and writes its Magic Context config.",
43670
+ "",
43671
+ " Options:",
43672
+ " --dry-run Preview the wizard without writing any files",
43673
+ ...HARNESS_LINES,
43674
+ ""
43675
+ ].join(`
43676
+ `);
43677
+ var DOCTOR_HELP = [
43678
+ "",
43679
+ " Usage: magic-context doctor [options]",
43680
+ " magic-context doctor <subcommand> [options]",
43681
+ "",
43682
+ " Check the installed harnesses and fix configuration issues.",
43683
+ "",
43684
+ " Options:",
43685
+ " --fix Repair safe, Magic Context-owned store rows and clear an outdated OpenCode 2 plugin cache",
43686
+ " --force Force-clear the plugin cache",
43687
+ " --clear Interactive cache cleanup picker",
43688
+ " --issue Collect diagnostics and open a GitHub issue",
43689
+ " --report <path> Write the issue diagnostics to <path> without prompting",
43690
+ " --check-v22-backfill Show v22 memory backfill status",
43691
+ " --retry-v22-backfill Retry failed v22 memory backfill rows",
43692
+ " --rekey-v22-dir-identity <path> Re-key legacy dir identity rows",
43693
+ ...HARNESS_LINES,
43694
+ "",
43695
+ " Subcommands (each accepts --help):",
43696
+ " drain-authority <project> Drain module memory/note authority back to TypeScript",
43697
+ " migrate Migrate an OpenCode session to Pi or OMP JSONL",
43698
+ " migrate-session Re-home an OpenCode session to another directory",
43699
+ " merge-identity Merge project rows between identities",
43700
+ " repair-db Back up and salvage a corrupted shared database",
43701
+ " list-hidden-sessions List Magic Context OpenCode 2 roots",
43702
+ ""
43703
+ ].join(`
43704
+ `);
43705
+ var DRAIN_AUTHORITY_HELP = [
43706
+ "",
43707
+ " Usage: magic-context doctor drain-authority <project>",
43708
+ "",
43709
+ " Drain the module's memory and note authority for <project> back to TypeScript.",
43710
+ ""
43711
+ ].join(`
43712
+ `);
43713
+ var MERGE_IDENTITY_HELP = [
43714
+ "",
43715
+ " Usage: magic-context doctor merge-identity --from <identity> --to <identity> [--dry-run] [--yes] [--db <path>]",
43716
+ "",
43717
+ " Merge every project-scoped row from one project identity into another.",
43718
+ "",
43719
+ " Options:",
43720
+ " --from <identity> Identity whose rows are moved",
43721
+ " --to <identity> Identity that receives them",
43722
+ " --dry-run Report what would change without writing",
43723
+ " --yes Confirm the write (required unless --dry-run)",
43724
+ " --db <path> Use this context.db instead of the shared one",
43725
+ ""
43726
+ ].join(`
43727
+ `);
43728
+ var LIST_HIDDEN_SESSIONS_HELP = [
43729
+ "",
43730
+ " Usage: magic-context doctor list-hidden-sessions",
43731
+ "",
43732
+ " List the hidden OpenCode 2 root sessions Magic Context reuses for its",
43733
+ " historian and dreamer runs. Read-only.",
43734
+ ""
43735
+ ].join(`
43736
+ `);
43737
+ function subcommandHelp(argv) {
43738
+ const [command, ...rest] = argv;
43739
+ if (!hasHelpFlag(rest))
43740
+ return null;
43741
+ if (command === "setup")
43742
+ return SETUP_HELP;
43743
+ if (command !== "doctor")
43744
+ return null;
43745
+ const subcommand = rest[0];
43746
+ if (subcommand === "drain-authority")
43747
+ return DRAIN_AUTHORITY_HELP;
43748
+ if (subcommand === "merge-identity")
43749
+ return MERGE_IDENTITY_HELP;
43750
+ if (subcommand === "list-hidden-sessions")
43751
+ return LIST_HIDDEN_SESSIONS_HELP;
43752
+ if (subcommand !== undefined && DOCTOR_SUBCOMMANDS_WITH_OWN_HELP.has(subcommand))
43753
+ return null;
43754
+ return DOCTOR_HELP;
43755
+ }
43756
+
43757
+ // src/index.ts
43758
+ init_prompts();
43759
+
43294
43760
  // src/lib/sqlite-preflight.ts
43295
43761
  async function probeSqliteBackend() {
43296
43762
  await init_sqlite().then(() => ({}));
@@ -43300,7 +43766,7 @@ function formatSqlitePreflightFailure(error) {
43300
43766
  return [
43301
43767
  "Magic Context doctor cannot use SQLite in this runtime.",
43302
43768
  `SQLite probe: ${detail}`,
43303
- "Remediation: install Node.js >= 24 or use a Bun build with node:sqlite.",
43769
+ "Remediation: install Node.js 22.16+ (22.x) or >= 24, or use a Bun build with node:sqlite.",
43304
43770
  "For Docker, use node:24-slim or a two-runtime image."
43305
43771
  ].join(`
43306
43772
  `);
@@ -43367,6 +43833,7 @@ function printUsage() {
43367
43833
  console.log(" (default: auto-detect, prompt if multiple installed)");
43368
43834
  console.log("");
43369
43835
  console.log(" Usage:");
43836
+ console.log(" npx @cortexkit/magic-context@latest <command> --help # help for one command");
43370
43837
  console.log(" npx @cortexkit/magic-context@latest setup");
43371
43838
  console.log(" # add --dry-run to preview the wizard without writing any files");
43372
43839
  console.log(" npx @cortexkit/magic-context@latest doctor");
@@ -43386,6 +43853,11 @@ async function main(argv = process.argv.slice(2)) {
43386
43853
  }
43387
43854
  const command = argv[0];
43388
43855
  const rest = argv.slice(1);
43856
+ const help = subcommandHelp(argv);
43857
+ if (help !== null) {
43858
+ console.log(help);
43859
+ return 0;
43860
+ }
43389
43861
  try {
43390
43862
  if (command === "setup") {
43391
43863
  await Promise.resolve().then(() => init_setup());