@cortexkit/magic-context 0.43.2 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1162 -692
  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."),
@@ -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)"),
@@ -22015,6 +22059,11 @@ function stripUnsafeProjectConfigFields(projectRaw) {
22015
22059
  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
22060
  }
22017
22061
  }
22062
+ const dreamer = projectRaw.dreamer;
22063
+ if (isPlainObject3(dreamer) && "runner" in dreamer) {
22064
+ delete dreamer.runner;
22065
+ warnings.push("Ignoring dreamer.runner from project config (security: which process and provider account run dreamer completions is a user-level setting).");
22066
+ }
22018
22067
  const mural = projectRaw.mural;
22019
22068
  if (isPlainObject3(mural) && "model" in mural) {
22020
22069
  delete mural.model;
@@ -22196,7 +22245,11 @@ var init_project_security = __esm(() => {
22196
22245
  init_magic_context();
22197
22246
  HIDDEN_AGENT_KEYS = ["historian", "dreamer"];
22198
22247
  HARNESS_KEYS = PER_HARNESS_MODEL_KEYS;
22199
- HISTORIAN_USER_ONLY_FIELDS = PER_HARNESS_MIGRATION_INVENTORY.historian.migrated_execution;
22248
+ HISTORIAN_USER_ONLY_FIELDS = [
22249
+ ...PER_HARNESS_MIGRATION_INVENTORY.historian.migrated_execution,
22250
+ "runner",
22251
+ "host_runner"
22252
+ ];
22200
22253
  PROMPT_SURFACE_USER_ONLY_FIELDS = ["guidance_override_path", "tool_descriptions"];
22201
22254
  AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools"];
22202
22255
  EMBEDDING_USER_ONLY_FIELDS = [
@@ -22511,6 +22564,25 @@ function redactConfigValue(value) {
22511
22564
  function formatProtectedTokensBelowMinWarning(value) {
22512
22565
  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
22566
  }
22567
+ function misplacedAgentModelWarnings(rawConfig) {
22568
+ const warnings = [];
22569
+ for (const agent of ["historian", "dreamer"]) {
22570
+ const block = rawConfig[agent];
22571
+ if (!block || typeof block !== "object" || Array.isArray(block))
22572
+ continue;
22573
+ const record = block;
22574
+ if (!Object.hasOwn(record, "model"))
22575
+ continue;
22576
+ const harnessWithModel = MODEL_HARNESS_BLOCKS.find((harness) => {
22577
+ const sub = record[harness];
22578
+ return sub !== null && typeof sub === "object" && !Array.isArray(sub) && Object.hasOwn(sub, "model");
22579
+ });
22580
+ if (harnessWithModel)
22581
+ continue;
22582
+ 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.`);
22583
+ }
22584
+ return warnings;
22585
+ }
22514
22586
  function warnProtectedTagsDeprecationOnce() {
22515
22587
  if (!warnedProtectedTagsDeprecation) {
22516
22588
  warnedProtectedTagsDeprecation = true;
@@ -22524,6 +22596,7 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
22524
22596
  warnProtectedTagsDeprecationOnce();
22525
22597
  preMigrationWarnings.push("protected_tags is deprecated and ignored; use protected_tokens instead.");
22526
22598
  }
22599
+ preMigrationWarnings.push(...misplacedAgentModelWarnings(rawConfig));
22527
22600
  const migratedExperimental = migrateLegacyExperimental(configWithoutRemovedAgent, preMigrationWarnings);
22528
22601
  const migratedDreamer = migrateDreamerV2(migratedExperimental, preMigrationWarnings);
22529
22602
  const migrated = migrateLegacyAgentEnabledInMemory(migratedDreamer, preMigrationWarnings);
@@ -22804,7 +22877,7 @@ function loadPluginConfigDetailed(directory, applyRuntimeGlobals = true) {
22804
22877
  cacheTtlConfigured
22805
22878
  };
22806
22879
  }
22807
- var warnedProtectedTagsDeprecation = false;
22880
+ var warnedProtectedTagsDeprecation = false, MODEL_HARNESS_BLOCKS;
22808
22881
  var init_config = __esm(() => {
22809
22882
  init_config_diagnostics();
22810
22883
  init_jsonc_parser();
@@ -22819,6 +22892,7 @@ var init_config = __esm(() => {
22819
22892
  init_removed_agent_config();
22820
22893
  init_magic_context();
22821
22894
  init_variable();
22895
+ MODEL_HARNESS_BLOCKS = ["opencode", "pi", "omp"];
22822
22896
  });
22823
22897
 
22824
22898
  // ../plugin/src/shared/opencode-config-dir.ts
@@ -26747,232 +26821,523 @@ function isRecord3(value) {
26747
26821
  return typeof value === "object" && value !== null && !Array.isArray(value);
26748
26822
  }
26749
26823
 
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
- });
26824
+ // ../plugin/src/shared/user-facing-codes.ts
26825
+ function userFacingFailureCode(key) {
26826
+ return USER_FACING_FAILURES[key].code;
26814
26827
  }
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);
26828
+ var USER_FACING_FAILURES;
26829
+ var init_user_facing_codes = __esm(() => {
26830
+ USER_FACING_FAILURES = {
26831
+ historian_unavailable: {
26832
+ code: "MC-H01",
26833
+ sentence: "History compression could not finish this turn.",
26834
+ action: "It will retry automatically."
26835
+ },
26836
+ hidden_cleanup_unbound: {
26837
+ code: "MC-H02",
26838
+ sentence: "Some finished background sessions could not be removed from this host.",
26839
+ action: "They are removed automatically the next time Magic Context runs inside a registered OpenCode service."
26840
+ },
26841
+ historian_saved_history_misaligned: {
26842
+ code: "MC-H03",
26843
+ sentence: "History compression is paused because this session's saved summaries no longer line up with its messages.",
26844
+ action: "Run /ctx-recomp to rebuild them."
26845
+ },
26846
+ recomp_unavailable: {
26847
+ code: "MC-R01",
26848
+ sentence: "History compression could not be rebuilt.",
26849
+ action: "Run /ctx-recomp again."
26850
+ },
26851
+ dream_provider_timeout: {
26852
+ code: "MC-D01",
26853
+ sentence: "Memory maintenance took too long to respond.",
26854
+ action: "Run /ctx-dream again."
26855
+ },
26856
+ dream_provider_error: {
26857
+ code: "MC-D02",
26858
+ sentence: "Memory maintenance could not reach its model.",
26859
+ action: "Check the model connection, then run /ctx-dream again."
26860
+ },
26861
+ dream_step_limit: {
26862
+ code: "MC-D10",
26863
+ sentence: "Memory maintenance stopped at its hidden agent step limit.",
26864
+ action: "This task needs less work per run; changing the model connection will not help."
26865
+ },
26866
+ dream_empty_completion: {
26867
+ code: "MC-D03",
26868
+ sentence: "Memory maintenance received no usable response.",
26869
+ action: "Run /ctx-dream again."
26870
+ },
26871
+ dream_no_models: {
26872
+ code: "MC-D04",
26873
+ sentence: "Memory maintenance has no model available.",
26874
+ action: "Check the model settings, then run /ctx-dream again."
26875
+ },
26876
+ dream_child_aborted: {
26877
+ code: "MC-D05",
26878
+ sentence: "Memory maintenance was interrupted.",
26879
+ action: "Run /ctx-dream again."
26880
+ },
26881
+ dream_parse_failed: {
26882
+ code: "MC-D06",
26883
+ sentence: "Memory maintenance could not use the model response.",
26884
+ action: "Run /ctx-dream again."
26885
+ },
26886
+ dream_unknown: {
26887
+ code: "MC-D07",
26888
+ sentence: "Memory maintenance could not finish.",
26889
+ action: "Run /ctx-dream again."
26890
+ },
26891
+ dreamer_tick_blocked: {
26892
+ code: "MC-D09",
26893
+ sentence: "Background maintenance is not running: its last pass stopped before it reached the scheduled tasks.",
26894
+ 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`."
26895
+ },
26896
+ dream_task_needs_tool_loop: {
26897
+ code: "MC-D08",
26898
+ sentence: "Some memory maintenance tasks need a tool loop this host does not provide.",
26899
+ action: "The remaining tasks still run; the listed ones are skipped on this host."
26900
+ },
26901
+ embedding_substitution_rejected: {
26902
+ code: "MC-E01",
26903
+ sentence: "Search indexing could not use the selected model.",
26904
+ action: "Check the embedding model setting, then run /ctx-embed start again."
26905
+ },
26906
+ embedding_http_error: {
26907
+ code: "MC-E02",
26908
+ sentence: "The search indexing provider refused the request.",
26909
+ action: "Check the provider connection and credentials, then run /ctx-embed start again."
26910
+ },
26911
+ embedding_transport_error: {
26912
+ code: "MC-E03",
26913
+ sentence: "Search indexing could not reach its provider.",
26914
+ action: "Check the connection, then run /ctx-embed start again."
26915
+ },
26916
+ embedding_invalid_envelope: {
26917
+ code: "MC-E04",
26918
+ sentence: "Search indexing received an unsupported response.",
26919
+ action: "Check the embedding endpoint, then run /ctx-embed start again."
26920
+ },
26921
+ embedding_empty_result: {
26922
+ code: "MC-E05",
26923
+ sentence: "Search indexing received no usable result.",
26924
+ action: "Run /ctx-embed start again."
26925
+ },
26926
+ embedding_certification_refusal: {
26927
+ code: "MC-E06",
26928
+ sentence: "Search indexing is not ready for this provider.",
26929
+ action: "Finish the provider setup, or set a fallback provider in the embedding settings, then run /ctx-embed start again."
26930
+ },
26931
+ embedding_credential_required: {
26932
+ code: "MC-E07",
26933
+ sentence: "Search indexing needs provider credentials.",
26934
+ action: "Sign in to the provider, then run /ctx-embed start again."
26935
+ },
26936
+ embedding_local_binding_missing: {
26937
+ code: "MC-E08",
26938
+ sentence: "Local search indexing is unavailable on this system.",
26939
+ action: "Run `npx @cortexkit/magic-context doctor`, then retry."
26940
+ },
26941
+ embedding_local_fs_unavailable: {
26942
+ code: "MC-E09",
26943
+ sentence: "Local search indexing cannot save its model files.",
26944
+ action: "Update or reinstall Magic Context, then retry."
26945
+ },
26946
+ embedding_local_download_failure: {
26947
+ code: "MC-E10",
26948
+ sentence: "Local search indexing could not download its model.",
26949
+ action: "Check the network connection, then retry."
26950
+ },
26951
+ embedding_local_runtime_error: {
26952
+ code: "MC-E11",
26953
+ sentence: "Local search indexing could not start.",
26954
+ action: "Run `npx @cortexkit/magic-context doctor`, then retry."
26955
+ },
26956
+ embedding_unavailable: {
26957
+ code: "MC-E12",
26958
+ sentence: "Search indexing could not finish.",
26959
+ action: "Run /ctx-embed start again."
26960
+ },
26961
+ status_unavailable: {
26962
+ code: "MC-S01",
26963
+ sentence: "Magic Context status is temporarily unavailable.",
26964
+ action: "Retry /ctx-status in a moment."
26965
+ },
26966
+ transform_update_failed: {
26967
+ code: "MC-S02",
26968
+ sentence: "The last context update did not finish.",
26969
+ action: "Send another message to retry."
26970
+ },
26971
+ configuration_warning: {
26972
+ code: "MC-S03",
26973
+ sentence: "Some configuration settings could not be applied.",
26974
+ action: "Fix the configuration warning shown in /ctx-status, then restart."
26975
+ },
26976
+ status_log_unavailable: {
26977
+ code: "MC-S04",
26978
+ sentence: "Some diagnostic details could not be saved.",
26979
+ action: "Retry /ctx-status in a moment."
26980
+ },
26981
+ memory_writes_paused: {
26982
+ code: "MC-C01",
26983
+ sentence: "Memory writes are paused while the engine syncs.",
26984
+ action: "Retry in a moment."
26985
+ },
26986
+ memory_access_unavailable: {
26987
+ code: "MC-C02",
26988
+ sentence: "Memory access is temporarily unavailable.",
26989
+ action: "Retry in a moment."
26990
+ },
26991
+ note_changes_paused: {
26992
+ code: "MC-C03",
26993
+ sentence: "Note changes are paused while the engine syncs.",
26994
+ action: "Retry in a moment."
26995
+ },
26996
+ note_access_unavailable: {
26997
+ code: "MC-C04",
26998
+ sentence: "Notes are temporarily unavailable.",
26999
+ action: "Retry in a moment."
27000
+ },
27001
+ context_cleanup_paused: {
27002
+ code: "MC-C05",
27003
+ sentence: "Context cleanup is paused while the engine syncs.",
27004
+ action: "Retry in a moment."
27005
+ },
27006
+ partial_history_unavailable: {
27007
+ code: "MC-C06",
27008
+ sentence: "Partial history compression is not available in the current mode.",
27009
+ action: "Run /ctx-recomp without a range."
27010
+ },
27011
+ smart_note_conditions_unavailable: {
27012
+ code: "MC-C08",
27013
+ sentence: "Conditional notes are not available in the current mode.",
27014
+ action: "Save a regular note without a condition."
27015
+ },
27016
+ history_compression_paused: {
27017
+ code: "MC-C09",
27018
+ sentence: "History compression is paused while the engine syncs.",
27019
+ action: "Retry in a moment."
27020
+ },
27021
+ history_compression_needs_message: {
27022
+ code: "MC-C12",
27023
+ sentence: "History compression has not seen this session since Magic Context reconnected.",
27024
+ action: "Send a message in this session first, then run /ctx-wrapup again."
27025
+ },
27026
+ context_service_unavailable: {
27027
+ code: "MC-C10",
27028
+ sentence: "Magic Context is temporarily unavailable.",
27029
+ action: "Retry in a moment."
27030
+ },
27031
+ store_ahead_of_binary: {
27032
+ code: "MC-C13",
27033
+ sentence: "Magic Context refused to start: its store (store.db) was migrated by a newer ck-mc build than the one running.",
27034
+ action: "Update ck-mc, or roll back by restoring ck-mc together with context.db and store.db from the same backup."
27035
+ },
27036
+ compaction_marker_missing: {
27037
+ code: "MC-C11",
27038
+ sentence: "The history boundary marker is missing from the OpenCode store, so requests carry the full session.",
27039
+ action: "It is retried on every message; if this persists, run `/ctx-flush`."
27040
+ },
27041
+ memory_mirror_stalled: {
27042
+ code: "MC-M01",
27043
+ sentence: "Memory synchronization stopped before the host mirror caught up.",
27044
+ action: "Send another message to resume it, or run `ck doctor drain-authority`."
27045
+ },
27046
+ memory_authority_mismatch: {
27047
+ code: "MC-M02",
27048
+ sentence: "Memory authority is inconsistent between the host and module.",
27049
+ action: "Run `ck doctor drain-authority` before changing Rust mode."
27050
+ },
27051
+ dreamer_task_failing: {
27052
+ code: "MC-S05",
27053
+ sentence: "A background maintenance task keeps failing on its schedule.",
27054
+ action: "Check the Magic Context log for the failing task and its error."
27055
+ }
27056
+ };
27057
+ });
27058
+
27059
+ // ../plugin/src/hooks/magic-context/store-ahead-refusal.ts
27060
+ function renderStoreAheadOfBinaryRefusal(versions) {
27061
+ const failure = USER_FACING_FAILURES.store_ahead_of_binary;
27062
+ 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;
27063
+ return `${sentence} ${failure.action} (${failure.code})`;
27064
+ }
27065
+ function isRecord4(value) {
27066
+ return value !== null && typeof value === "object";
27067
+ }
27068
+ function nonNegativeInteger(value) {
27069
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
27070
+ }
27071
+ function versionsFromDetail(detail) {
27072
+ if (!isRecord4(detail))
27073
+ return null;
27074
+ const dbVersion = nonNegativeInteger(detail.db_version);
27075
+ const binaryMax = nonNegativeInteger(detail.binary_max);
27076
+ return dbVersion !== null && binaryMax !== null ? { dbVersion, binaryMax } : null;
27077
+ }
27078
+ function versionsFromMessage(message) {
27079
+ if (typeof message !== "string")
27080
+ return null;
27081
+ 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);
27082
+ if (!match)
27083
+ return null;
27084
+ return { dbVersion: Number(match[1]), binaryMax: Number(match[2]) };
27085
+ }
27086
+ function storeAheadOfBinaryFailure(error) {
27087
+ let current = error;
27088
+ const seen = new Set;
27089
+ while (isRecord4(current) && !seen.has(current)) {
27090
+ seen.add(current);
27091
+ if (current instanceof StoreAheadOfBinaryError)
27092
+ return current;
27093
+ if (current.code === STORE_AHEAD_OF_BINARY_CODE) {
27094
+ const versions = versionsFromDetail(current.detail) ?? versionsFromMessage(current.message);
27095
+ return new StoreAheadOfBinaryError(versions, { cause: error });
27096
+ }
27097
+ current = current.cause;
27098
+ }
27099
+ return null;
27100
+ }
27101
+ var STORE_AHEAD_OF_BINARY_CODE = "store_ahead_of_binary", StoreAheadOfBinaryError;
27102
+ var init_store_ahead_refusal = __esm(() => {
27103
+ init_user_facing_codes();
27104
+ StoreAheadOfBinaryError = class StoreAheadOfBinaryError extends Error {
27105
+ code = STORE_AHEAD_OF_BINARY_CODE;
27106
+ versions;
27107
+ constructor(versions, options) {
27108
+ super(renderStoreAheadOfBinaryRefusal(versions), options);
27109
+ this.name = "StoreAheadOfBinaryError";
27110
+ this.versions = versions;
27111
+ }
27112
+ };
27113
+ });
27114
+
27115
+ // ../plugin/src/hooks/magic-context/module-transport.ts
27116
+ import { realpathSync as realpathSync3 } from "node:fs";
27117
+ import { join as join17 } from "node:path";
27118
+ function getDefaultSubcConnectionFile() {
27119
+ return join17(getDataDir(), "cortexkit", "run", "subc-connection.json");
27120
+ }
27121
+ function errorChainSome(error, predicate) {
27122
+ let current = error;
27123
+ const seen = new Set;
27124
+ while (isRecord3(current) && !seen.has(current)) {
27125
+ seen.add(current);
27126
+ if (predicate(current))
27127
+ return true;
27128
+ current = current.cause;
27129
+ }
27130
+ return false;
27131
+ }
27132
+ function isStaleOrDeadRouteFailure(error) {
27133
+ return errorChainSome(error, (current) => {
27134
+ const code = typeof current.code === "string" ? current.code : "";
27135
+ const name = typeof current.name === "string" ? current.name : "";
27136
+ const message = typeof current.message === "string" ? current.message : "";
27137
+ return [
27138
+ "stale_route_handle",
27139
+ "route_closed",
27140
+ "unknown_channel",
27141
+ "unrecognized_channel",
27142
+ "route_gone"
27143
+ ].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);
27144
+ });
27145
+ }
27146
+ function isDeadlineFailure(error) {
27147
+ if (error instanceof SocketTimeoutError)
27148
+ return true;
27149
+ return errorChainSome(error, (current) => {
27150
+ const code = typeof current.code === "string" ? current.code : "";
27151
+ return ["ETIMEDOUT", "request_deadline", "deadline_exceeded_no_drop_observed"].includes(code);
27152
+ });
27153
+ }
27154
+ function isConnectionFailure(error) {
27155
+ if (error instanceof SocketClosedError || error instanceof SocketTimeoutError || error instanceof StaleRouteHandleError || isConsumerReconnectTransient(error) || isStaleOrDeadRouteFailure(error)) {
27156
+ return true;
27157
+ }
27158
+ return errorChainSome(error, (current) => {
27159
+ const code = typeof current.code === "string" ? current.code : "";
27160
+ const message = typeof current.message === "string" ? current.message : "";
27161
+ return [
27162
+ "ENOENT",
27163
+ "ECONNREFUSED",
27164
+ "ECONNRESET",
27165
+ "EPIPE",
27166
+ "ETIMEDOUT",
27167
+ "request_deadline",
27168
+ "deadline_exceeded_no_drop_observed",
27169
+ "connection_dropped",
27170
+ "SUBC_CONNECTION_BACKOFF"
27171
+ ].includes(code) || /\bclient closed\b|\bconnection closed\b|\bclosed the connection\b/i.test(message);
27172
+ });
27173
+ }
27174
+ function routeOpenWithoutAmbientConsumerIdentity(client, target, identity, options = {}) {
27175
+ return client.routeOpen(target, identity, {
27176
+ ...options,
27177
+ consumerIdentity: null
27178
+ });
27179
+ }
27180
+
27181
+ class SubcModuleTransport {
27182
+ connectionFile;
27183
+ moduleId;
27184
+ requestTimeoutMs;
27185
+ routeSessionPrefix;
27186
+ client = null;
27187
+ routes = new Map;
27188
+ routeOpenings = new Map;
27189
+ canonicalRootCache = new Map;
27190
+ sessionLanes = new Map;
27191
+ queuedLaneWaiters = 0;
27192
+ wrapupSessions = new Map;
27193
+ nextProbeMs = 0;
27194
+ connectionPromise = null;
27195
+ authorityProjectRoot = "";
27196
+ authorityBindRoot = "";
27197
+ backoffMs = CONNECT_BACKOFF_INITIAL_MS;
27198
+ connectionGeneration = 0;
27199
+ stateSyncCapabilityCache = null;
27200
+ getCachedStateSyncCapabilities() {
27201
+ const cached = this.stateSyncCapabilityCache;
27202
+ if (!cached || cached.generation !== this.connectionGeneration)
27203
+ return;
27204
+ return cached.capabilities;
27205
+ }
27206
+ invalidateStateSyncCapabilities() {
27207
+ this.stateSyncCapabilityCache = null;
27208
+ }
27209
+ async stateSyncCapabilities(args) {
27210
+ const cached = this.getCachedStateSyncCapabilities();
27211
+ if (cached)
27212
+ return cached;
27213
+ const response = await this.call({
27214
+ sessionId: args.sessionId,
27215
+ projectRoot: args.projectRoot,
27216
+ method: "session.status",
27217
+ body: { method: "session.status", v: 1, session_id: args.sessionId }
27218
+ });
27219
+ const raw = isRecord3(response) ? response : {};
27220
+ const value = isRecord3(raw.result) ? raw.result : raw;
27221
+ const epochs = isRecord3(value.epochs) ? value.epochs : {};
27222
+ const capabilities = {
27223
+ state_sync_deltas: epochs.state_sync_deltas === true,
27224
+ ...epochs.state_sync_resume === true ? { state_sync_resume: true } : {}
27225
+ };
27226
+ this.stateSyncCapabilityCache = { generation: this.connectionGeneration, capabilities };
27227
+ return capabilities;
27228
+ }
27229
+ constructor(connectionFile, moduleId = DEFAULT_MODULE_ID, requestTimeoutMs = MODULE_SEND_TIMEOUT_MS, routeSessionPrefix = "") {
27230
+ this.connectionFile = connectionFile;
27231
+ this.moduleId = moduleId;
27232
+ this.requestTimeoutMs = requestTimeoutMs;
27233
+ this.routeSessionPrefix = routeSessionPrefix;
27234
+ }
27235
+ deadlineError(detail) {
27236
+ const error = new Error(`module transport deadline expired ${detail}`);
27237
+ error.code = "ETIMEDOUT";
27238
+ return error;
27239
+ }
27240
+ laneTimeoutError() {
27241
+ return this.deadlineError("while queued");
27242
+ }
27243
+ connectionChangedError(detail) {
27244
+ const error = new Error(detail);
27245
+ error.code = "ECONNRESET";
27246
+ return error;
27247
+ }
27248
+ async beforeDeadline(operation, deadlineMs, detail) {
27249
+ operation.catch(() => {});
27250
+ const remainingMs = deadlineMs - Date.now();
27251
+ if (remainingMs <= 0)
27252
+ throw this.deadlineError(detail);
27253
+ let timer;
27254
+ try {
27255
+ return await Promise.race([
27256
+ operation,
27257
+ new Promise((_resolve, reject) => {
27258
+ timer = setTimeout(() => reject(this.deadlineError(detail)), remainingMs);
27259
+ })
27260
+ ]);
27261
+ } finally {
27262
+ if (timer)
27263
+ clearTimeout(timer);
27264
+ }
27265
+ }
27266
+ cleanupLane(sessionId, lane) {
27267
+ if (!lane.active && lane.waiters.length === 0 && this.sessionLanes.get(sessionId) === lane) {
27268
+ this.sessionLanes.delete(sessionId);
27269
+ }
27270
+ }
27271
+ laneRelease(sessionId, lane) {
27272
+ let released = false;
27273
+ return () => {
27274
+ if (released)
27275
+ return;
27276
+ released = true;
27277
+ lane.active = false;
27278
+ this.dispatchNextLaneWaiter(sessionId, lane);
27279
+ };
27280
+ }
27281
+ dispatchNextLaneWaiter(sessionId, lane) {
27282
+ if (lane.active)
27283
+ return;
27284
+ while (lane.waiters.length > 0) {
27285
+ const waiter = lane.waiters.shift();
27286
+ if (!waiter)
27287
+ continue;
27288
+ this.queuedLaneWaiters = Math.max(0, this.queuedLaneWaiters - 1);
27289
+ if (waiter.settled)
27290
+ continue;
27291
+ waiter.settled = true;
27292
+ clearTimeout(waiter.timer);
27293
+ waiter.signal?.removeEventListener("abort", waiter.onAbort);
27294
+ if (waiter.signal?.aborted) {
27295
+ waiter.reject(waiter.signal.reason ?? new Error("module transport call aborted"));
27296
+ continue;
27297
+ }
27298
+ if (waiter.deadlineMs - Date.now() < SERIAL_LANE_MIN_REMAINING_MS) {
27299
+ waiter.reject(this.laneTimeoutError());
27300
+ continue;
27301
+ }
27302
+ lane.active = true;
27303
+ waiter.resolve(this.laneRelease(sessionId, lane));
27304
+ return;
27305
+ }
27306
+ this.cleanupLane(sessionId, lane);
27307
+ }
27308
+ queueFullError() {
27309
+ const error = new Error("module transport queue is full");
27310
+ error.code = "EBUSY";
27311
+ return error;
27312
+ }
27313
+ acquireCorrectnessLane(sessionId, signal, deadlineMs) {
27314
+ if (signal?.aborted) {
27315
+ return Promise.reject(signal.reason ?? new Error("module transport call aborted"));
27316
+ }
27317
+ if (deadlineMs - Date.now() < SERIAL_LANE_MIN_REMAINING_MS) {
27318
+ return Promise.reject(this.laneTimeoutError());
27319
+ }
27320
+ const lane = this.sessionLanes.get(sessionId) ?? { active: false, waiters: [] };
27321
+ this.sessionLanes.set(sessionId, lane);
27322
+ if (!lane.active && lane.waiters.length === 0) {
27323
+ lane.active = true;
27324
+ return Promise.resolve(this.laneRelease(sessionId, lane));
27325
+ }
27326
+ if (this.queuedLaneWaiters >= SERIAL_LANE_MAX_WAITERS || lane.waiters.length >= SERIAL_LANE_MAX_WAITERS_PER_SESSION) {
27327
+ return Promise.reject(this.queueFullError());
27328
+ }
27329
+ return new Promise((resolve, reject) => {
27330
+ const waiter = {};
27331
+ const removeAndReject = (error) => {
27332
+ if (waiter.settled)
27333
+ return;
27334
+ waiter.settled = true;
27335
+ clearTimeout(waiter.timer);
27336
+ signal?.removeEventListener("abort", waiter.onAbort);
27337
+ const index = lane.waiters.indexOf(waiter);
27338
+ if (index >= 0) {
27339
+ lane.waiters.splice(index, 1);
27340
+ this.queuedLaneWaiters = Math.max(0, this.queuedLaneWaiters - 1);
26976
27341
  }
26977
27342
  reject(error);
26978
27343
  this.cleanupLane(sessionId, lane);
@@ -27017,7 +27382,8 @@ class SubcModuleTransport {
27017
27382
  const laneDeadlineMs = Date.now() + attemptTimeoutMs;
27018
27383
  let releaseLane = () => {};
27019
27384
  try {
27020
- if (!args.bypassSessionLane) {
27385
+ const outsideSessionOrder = args.method.startsWith("historian.");
27386
+ if (!args.bypassSessionLane && !outsideSessionOrder) {
27021
27387
  releaseLane = await this.acquireCorrectnessLane(args.sessionId, args.signal, laneDeadlineMs);
27022
27388
  }
27023
27389
  } catch (error) {
@@ -27077,6 +27443,9 @@ class SubcModuleTransport {
27077
27443
  } catch (error) {
27078
27444
  if (args.signal?.aborted)
27079
27445
  throw args.signal.reason ?? error;
27446
+ const storeAhead = storeAheadOfBinaryFailure(error);
27447
+ if (storeAhead)
27448
+ throw storeAhead;
27080
27449
  if (args.method === "state_sync" && isDeadlineFailure(error)) {
27081
27450
  const body = isRecord3(args.body) ? args.body : {};
27082
27451
  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 +27759,7 @@ var DEFAULT_MODULE_ID = "magic-context", CONNECT_BACKOFF_INITIAL_MS = 1000, CONN
27390
27759
  var init_module_transport = __esm(() => {
27391
27760
  init_dist5();
27392
27761
  init_data_path();
27762
+ init_store_ahead_refusal();
27393
27763
  });
27394
27764
  // ../plugin/src/shared/pi-executable.ts
27395
27765
  function piHarnessKindFromExecutable(value) {
@@ -27406,6 +27776,9 @@ var init_pi_executable = __esm(() => {
27406
27776
  // ../plugin/src/shared/rpc-utils.ts
27407
27777
  import { execFileSync as execFileSync4 } from "node:child_process";
27408
27778
  import { readFileSync as readFileSync14 } from "node:fs";
27779
+ function isOwnRpcServerRecord(record) {
27780
+ return record.pid === process.pid && record.instance_id !== undefined && ownRpcServerInstanceIds.has(record.instance_id);
27781
+ }
27409
27782
  function isPidAlive(pid) {
27410
27783
  if (!Number.isInteger(pid) || pid <= 0)
27411
27784
  return "dead";
@@ -27928,10 +28301,11 @@ function parseRpcPortFile(content, fallbackPid = 0) {
27928
28301
  function isValidPort(port) {
27929
28302
  return Number.isInteger(port) && port > 0 && port <= 65535;
27930
28303
  }
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;
28304
+ 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
28305
  var init_rpc_utils = __esm(() => {
27933
28306
  init_logger();
27934
28307
  init_pi_executable();
28308
+ ownRpcServerInstanceIds = new Set;
27935
28309
  OPEN_CODE_COMMAND_MARKERS = ["opencode", "node", "bun", "electron"];
27936
28310
  TASKLIST_NO_TASKS_PATTERN = /^INFO:\s+No tasks are running which match the specified criteria\.?$/im;
27937
28311
  PI_HARNESS_ARC_MARKERS = [
@@ -28061,7 +28435,9 @@ function formatFailClosedBlockingMessage(reason) {
28061
28435
  }
28062
28436
  return [
28063
28437
  `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.`,
28438
+ `The database is at upstream migration v${reason.persistedVersion}; this build needs v${reason.supportedVersion}.`,
28064
28439
  "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.",
28440
+ "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
28441
  `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
28066
28442
  ].join(" ");
28067
28443
  }
@@ -28096,6 +28472,47 @@ var init_message_fts_rowid_map = __esm(() => {
28096
28472
  activeBackfills = new WeakMap;
28097
28473
  });
28098
28474
 
28475
+ // ../plugin/src/features/magic-context/memory/constants.ts
28476
+ var V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
28477
+ var init_constants = __esm(() => {
28478
+ V2_MEMORY_CATEGORIES = [
28479
+ "PROJECT_RULES",
28480
+ "ARCHITECTURE",
28481
+ "CONSTRAINTS",
28482
+ "CONFIG_VALUES",
28483
+ "NAMING"
28484
+ ];
28485
+ PROMOTABLE_CATEGORIES = [
28486
+ ...V2_MEMORY_CATEGORIES,
28487
+ "ARCHITECTURE_DECISIONS",
28488
+ "CONFIG_DEFAULTS",
28489
+ "USER_PREFERENCES",
28490
+ "USER_DIRECTIVES",
28491
+ "ENVIRONMENT",
28492
+ "WORKFLOW_RULES",
28493
+ "KNOWN_ISSUES"
28494
+ ];
28495
+ CATEGORY_PRIORITY = [
28496
+ ...V2_MEMORY_CATEGORIES,
28497
+ "USER_DIRECTIVES",
28498
+ "USER_PREFERENCES",
28499
+ "CONFIG_DEFAULTS",
28500
+ "ARCHITECTURE_DECISIONS",
28501
+ "ENVIRONMENT",
28502
+ "WORKFLOW_RULES",
28503
+ "KNOWN_ISSUES"
28504
+ ];
28505
+ MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
28506
+ acc[category] = index;
28507
+ return acc;
28508
+ }, {});
28509
+ MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
28510
+ CATEGORY_DEFAULT_TTL = {
28511
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
28512
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
28513
+ };
28514
+ });
28515
+
28099
28516
  // ../plugin/src/hooks/magic-context/compartment-parser.ts
28100
28517
  function makeTierOpenRegex(n) {
28101
28518
  return new RegExp(`<p${n}\\s*(/?)>`);
@@ -28125,6 +28542,8 @@ function extractTiersFromInner(inner) {
28125
28542
  function parseCompartmentOutput(text) {
28126
28543
  const compartments = [];
28127
28544
  const facts = [];
28545
+ let droppedFactBlocks = 0;
28546
+ let droppedFacts = 0;
28128
28547
  for (const match of text.matchAll(COMPARTMENT_REGEX)) {
28129
28548
  const attrs = match[1];
28130
28549
  const inner = match[2];
@@ -28174,16 +28593,21 @@ function parseCompartmentOutput(text) {
28174
28593
  }
28175
28594
  }
28176
28595
  const factsBlockMatch = text.match(FACTS_BLOCK_REGEX);
28177
- const factsScope = factsBlockMatch ? factsBlockMatch[1] : text.replace(EVENTS_BLOCK_REGEX, "").replace(/<compartment\s+[^>]*?\s*>.*?<\/compartment>/gs, "");
28596
+ 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
28597
  for (const categoryMatch of factsScope.matchAll(CATEGORY_BLOCK_REGEX)) {
28179
28598
  const category = categoryMatch[1];
28180
28599
  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
- }
28600
+ const items = [...blockContent.matchAll(FACT_ITEM_REGEX)].map((match) => unescapeXml(match[1].trim())).filter(Boolean);
28601
+ if (!HISTORIAN_CATEGORIES.has(category)) {
28602
+ if (items.length === 0)
28603
+ continue;
28604
+ droppedFactBlocks++;
28605
+ droppedFacts += items.length;
28606
+ log3(`[historian] Dropped <facts> category ${category} (${items.length} facts)`);
28607
+ continue;
28186
28608
  }
28609
+ for (const content of items)
28610
+ facts.push({ category, content });
28187
28611
  }
28188
28612
  const unprocessedMatch = text.match(UNPROCESSED_REGEX);
28189
28613
  const unprocessedFrom = unprocessedMatch ? parseInt(unprocessedMatch[1], 10) : null;
@@ -28221,7 +28645,16 @@ function parseCompartmentOutput(text) {
28221
28645
  }
28222
28646
  const events = parseEvents(text);
28223
28647
  compartments.sort((a, b) => a.startMessage - b.startMessage);
28224
- return { compartments, facts, events, unprocessedFrom, userObservations, primerCandidates };
28648
+ return {
28649
+ compartments,
28650
+ facts,
28651
+ droppedFactBlocks,
28652
+ droppedFacts,
28653
+ events,
28654
+ unprocessedFrom,
28655
+ userObservations,
28656
+ primerCandidates
28657
+ };
28225
28658
  }
28226
28659
  function parseEvents(text) {
28227
28660
  const blockMatch = text.match(EVENTS_BLOCK_REGEX);
@@ -28247,8 +28680,10 @@ function parseEvents(text) {
28247
28680
  function unescapeXml(s) {
28248
28681
  return s.replace(/&amp;/g, "&").replace(/&apos;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">");
28249
28682
  }
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;
28683
+ 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
28684
  var init_compartment_parser = __esm(() => {
28685
+ init_constants();
28686
+ init_logger();
28252
28687
  COMPARTMENT_REGEX = /<compartment\s+([^>]*?)\s*>(.*?)<\/compartment>/gs;
28253
28688
  ATTR_START_REGEX = /\bstart="(\d+)"/;
28254
28689
  ATTR_END_REGEX = /\bend="(\d+)"/;
@@ -28263,7 +28698,8 @@ var init_compartment_parser = __esm(() => {
28263
28698
  ];
28264
28699
  TIER_CLOSE_ANY_REGEX = /<\/p\d/;
28265
28700
  TIER_OPEN_ANY_REGEX = /<p\d/;
28266
- CATEGORY_BLOCK_REGEX = /<(PROJECT_RULES|ARCHITECTURE|CONSTRAINTS|CONFIG_VALUES|NAMING)>(.*?)<\/\1>/gs;
28701
+ CATEGORY_BLOCK_REGEX = /<([A-Za-z_][A-Za-z0-9_-]*)>(.*?)<\/\1>/gs;
28702
+ HISTORIAN_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
28267
28703
  FACT_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
28268
28704
  UNPROCESSED_REGEX = /<unprocessed_from>(\d+)<\/unprocessed_from>/;
28269
28705
  USER_OBSERVATIONS_REGEX = /<user_observations>(.*?)<\/user_observations>/s;
@@ -28617,53 +29053,18 @@ function healAllNullColumns(db) {
28617
29053
  const nullPredicate = presentFallbacks.map(([column]) => `${column} IS NULL`).join(" OR ");
28618
29054
  db.prepare(`UPDATE session_meta SET ${assignments} WHERE ${nullPredicate}`).run(...presentFallbacks.map(([, fallback]) => fallback));
28619
29055
  }
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
- });
29056
+ healMissingMemoryBlockIds(db, existingColumns);
29057
+ }
29058
+ function getSessionMetaColumns(db) {
29059
+ const rows = db.prepare("PRAGMA table_info(session_meta)").all();
29060
+ return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : []));
29061
+ }
29062
+ function healMissingMemoryBlockIds(db, columns) {
29063
+ if (!columns.has("memory_block_cache") || !columns.has("memory_block_ids") || !columns.has("memory_block_count")) {
29064
+ return;
29065
+ }
29066
+ 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();
29067
+ }
28667
29068
 
28668
29069
  // ../plugin/src/features/magic-context/project-identity.ts
28669
29070
  var init_project_identity2 = __esm(() => {
@@ -31144,6 +31545,20 @@ var init_migrations = __esm(async () => {
31144
31545
  return;
31145
31546
  ensureColumn(db, "compartment_state_lease", "owner_pid", "INTEGER");
31146
31547
  }
31548
+ },
31549
+ {
31550
+ version: 91,
31551
+ description: "per-project embedding high-water mark for memories written outside this host",
31552
+ up(db) {
31553
+ db.exec(`
31554
+ CREATE TABLE IF NOT EXISTS memory_embedding_watermarks (
31555
+ project_path TEXT PRIMARY KEY,
31556
+ written_memory_id INTEGER NOT NULL DEFAULT 0,
31557
+ embedded_memory_id INTEGER NOT NULL DEFAULT 0,
31558
+ updated_at INTEGER NOT NULL DEFAULT 0
31559
+ );
31560
+ `);
31561
+ }
31147
31562
  }
31148
31563
  ];
31149
31564
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -31375,6 +31790,8 @@ function inspectRpcServerDiscovery(storageDir) {
31375
31790
  return junk;
31376
31791
  continue;
31377
31792
  }
31793
+ if (isOwnRpcServerRecord(record))
31794
+ continue;
31378
31795
  const liveness = isPidAlive(record.pid);
31379
31796
  if (liveness === "dead") {
31380
31797
  staleFiles.push(portFile);
@@ -31996,6 +32413,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
31996
32413
  (id, cursor_session_id, cursor_ordinal, completed, updated_at)
31997
32414
  VALUES (1, '', 0, 0, 0);
31998
32415
 
32416
+ -- Highest memory id another writer (the Rust module in single-store mode) put
32417
+ -- into memories for a project, and how far this host has embedded. Migration v91.
32418
+ CREATE TABLE IF NOT EXISTS memory_embedding_watermarks (
32419
+ project_path TEXT PRIMARY KEY,
32420
+ written_memory_id INTEGER NOT NULL DEFAULT 0,
32421
+ embedded_memory_id INTEGER NOT NULL DEFAULT 0,
32422
+ updated_at INTEGER NOT NULL DEFAULT 0
32423
+ );
32424
+
31999
32425
  CREATE TABLE IF NOT EXISTS message_history_index (
32000
32426
  session_id TEXT PRIMARY KEY,
32001
32427
  last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
@@ -32655,7 +33081,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
32655
33081
  `);
32656
33082
  ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
32657
33083
  }
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;
33084
+ 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
33085
  var init_storage_db = __esm(async () => {
32660
33086
  init_data_path();
32661
33087
  init_logger();
@@ -35440,7 +35866,7 @@ async function doctorRetryV22Backfill(db) {
35440
35866
  bumpProjectMemoryEpochInTransaction(db, identity, now);
35441
35867
  succeeded += 1;
35442
35868
  }
35443
- })();
35869
+ }).immediate();
35444
35870
  } catch (error) {
35445
35871
  const classified = classifyBackfillError(error);
35446
35872
  db.prepare(`UPDATE v22_backfill_failures
@@ -35473,7 +35899,7 @@ async function doctorRekeyV22DirIdentity(db, rawProjectPath) {
35473
35899
  if (changedRows > 0) {
35474
35900
  bumpProjectMemoryEpochInTransaction(db, newIdentity, now);
35475
35901
  }
35476
- })();
35902
+ }).immediate();
35477
35903
  return { oldIdentity, newIdentity, changedRows };
35478
35904
  }
35479
35905
  var BACKFILL_META_KEY = "v22_legacy_memory_backfill", BACKFILL_CURSOR_META_KEY = "v22_legacy_memory_backfill_cursor", MEMORIES_TABLE = "memories";
@@ -35546,236 +35972,6 @@ var init_v22_backfill_commands = __esm(async () => {
35546
35972
  await init_storage_db();
35547
35973
  });
35548
35974
 
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
35975
  // ../plugin/src/features/magic-context/dreamer/tick-failure.ts
35780
35976
  function parseFailure(value) {
35781
35977
  if (!value)
@@ -38519,6 +38715,9 @@ async function collectHistorianRuns(storageDirPath) {
38519
38715
  } catch {}
38520
38716
  }
38521
38717
  }
38718
+ function openCodeIssueLogHarnesses(hostGeneration) {
38719
+ return hostGeneration === "v2" ? ["opencode2", "opencode"] : ["opencode"];
38720
+ }
38522
38721
  async function collectDiagnostics() {
38523
38722
  const pluginVersion = getSelfVersion();
38524
38723
  const configPaths = detectConfigPaths();
@@ -38528,8 +38727,6 @@ async function collectDiagnostics() {
38528
38727
  const storageResolution = getStorageResolution();
38529
38728
  const storageDirPath = storageResolution.path;
38530
38729
  const contextDbPath = join26(storageDirPath, "context.db");
38531
- const logFiles = inspectMagicContextLogs("opencode");
38532
- const primaryLog = logFiles.find((file) => file.exists) ?? logFiles[0];
38533
38730
  let compactionEnabled = false;
38534
38731
  try {
38535
38732
  compactionEnabled = isCompactionEnabled(loadPluginConfig(process.cwd()));
@@ -38539,6 +38736,8 @@ async function collectDiagnostics() {
38539
38736
  const opencodeInstallations = describeOpenCodeInstallations(detectOpenCodeInstallations());
38540
38737
  const activeInstallation = opencodeInstallations[0];
38541
38738
  const hostGeneration = openCodeHostGenerationFromVersion(activeInstallation?.version);
38739
+ const logFiles = openCodeIssueLogHarnesses(hostGeneration).flatMap((harness) => inspectMagicContextLogs(harness)).filter((file, index, all) => all.findIndex((other) => other.path === file.path) === index);
38740
+ const primaryLog = logFiles.find((file) => file.exists) ?? logFiles[0];
38542
38741
  const conflictResult = detectConflicts(process.cwd(), { compactionEnabled, hostGeneration });
38543
38742
  const openCodeDatabaseResolution = resolveOpenCodeDbPath(hostGeneration);
38544
38743
  const recentSessions = await collectRecentSessions(openCodeDatabaseResolution, hostGeneration);
@@ -40046,6 +40245,179 @@ var init_storage_versions = __esm(async () => {
40046
40245
  await init_storage_db();
40047
40246
  });
40048
40247
 
40248
+ // src/commands/doctor-opencode-cache.ts
40249
+ import { existsSync as existsSync36, readFileSync as readFileSync25, rmSync as rmSync4 } from "node:fs";
40250
+ function readCachedPluginVersion(pluginCacheDir) {
40251
+ try {
40252
+ const installedPkgPath = getOpenCodePluginPackageJsonPath(pluginCacheDir);
40253
+ if (!existsSync36(installedPkgPath))
40254
+ return;
40255
+ const pkg = JSON.parse(readFileSync25(installedPkgPath, "utf-8"));
40256
+ return typeof pkg.version === "string" ? pkg.version : undefined;
40257
+ } catch {
40258
+ return;
40259
+ }
40260
+ }
40261
+ async function clearPluginCache(options = {}, deps = {}) {
40262
+ const remove = deps.remove ?? ((path) => rmSync4(path, { recursive: true, force: true }));
40263
+ const pluginCacheRoots = getOpenCodePluginCacheRoots();
40264
+ const existingRoots = pluginCacheRoots.filter((root) => existsSync36(root));
40265
+ if (existingRoots.length === 0) {
40266
+ return { action: "not_found", path: pluginCacheRoots[0] ?? "" };
40267
+ }
40268
+ const latestVersion = options.latestVersion ?? undefined;
40269
+ const cacheEntries = existingRoots.map((path) => ({
40270
+ path,
40271
+ cached: readCachedPluginVersion(path)
40272
+ }));
40273
+ if (options.force !== true && latestVersion === undefined) {
40274
+ const firstEntry = cacheEntries[0];
40275
+ return {
40276
+ action: "check_unavailable",
40277
+ path: firstEntry?.path ?? pluginCacheRoots[0] ?? "",
40278
+ paths: cacheEntries.map((entry) => entry.path),
40279
+ cached: firstEntry?.cached
40280
+ };
40281
+ }
40282
+ const clearTargets = cacheEntries.filter((entry) => options.force === true || entry.cached === undefined || entry.cached !== latestVersion);
40283
+ if (clearTargets.length === 0) {
40284
+ const firstEntry = cacheEntries[0];
40285
+ return {
40286
+ action: "up_to_date",
40287
+ path: firstEntry?.path ?? pluginCacheRoots[0] ?? "",
40288
+ paths: cacheEntries.map((entry) => entry.path),
40289
+ cached: firstEntry?.cached,
40290
+ latest: latestVersion
40291
+ };
40292
+ }
40293
+ const cleared = [];
40294
+ const failed = [];
40295
+ for (const entry of clearTargets) {
40296
+ try {
40297
+ remove(entry.path);
40298
+ cleared.push(entry);
40299
+ } catch (err) {
40300
+ failed.push({
40301
+ path: entry.path,
40302
+ error: err instanceof Error ? err.message : String(err)
40303
+ });
40304
+ }
40305
+ }
40306
+ if (failed.length > 0) {
40307
+ const firstFailure = failed[0];
40308
+ return {
40309
+ action: "error",
40310
+ path: firstFailure?.path ?? clearTargets[0]?.path ?? existingRoots[0] ?? "",
40311
+ paths: failed.map((entry) => entry.path),
40312
+ clearedPaths: cleared.map((entry) => entry.path),
40313
+ failedPaths: failed.map((entry) => entry.path),
40314
+ error: firstFailure?.error
40315
+ };
40316
+ }
40317
+ const firstTarget = cleared[0];
40318
+ return {
40319
+ action: "cleared",
40320
+ path: firstTarget?.path ?? pluginCacheRoots[0] ?? "",
40321
+ paths: cleared.map((entry) => entry.path),
40322
+ cached: firstTarget?.cached,
40323
+ latest: latestVersion
40324
+ };
40325
+ }
40326
+ var init_doctor_opencode_cache = __esm(() => {
40327
+ init_opencode_plugin_cache();
40328
+ });
40329
+
40330
+ // src/commands/doctor-cached-plugin-fence.ts
40331
+ import { existsSync as existsSync37 } from "node:fs";
40332
+ import { dirname as dirname18 } from "node:path";
40333
+ function listCachedOpenCodePluginFences(sources = {}) {
40334
+ const fences = [];
40335
+ for (const root of sources.openCodeV1Roots ?? getOpenCodePluginCacheRoots()) {
40336
+ const packageJson = getOpenCodePluginPackageJsonPath(root);
40337
+ if (!existsSync37(packageJson))
40338
+ continue;
40339
+ fences.push({
40340
+ host: "opencode1",
40341
+ directory: root,
40342
+ version: readCachedPluginVersion(root),
40343
+ supportedVersion: readFenceFromDist(dirname18(packageJson))
40344
+ });
40345
+ }
40346
+ for (const slot of listOpenCodeV2PluginCacheSlots(sources.openCodeV2NpmCacheDir)) {
40347
+ if (!isOpenCodePluginDistTag(slot.spec))
40348
+ continue;
40349
+ const generation = getOpenCodeV2ActiveGeneration(slot.slot);
40350
+ if (!generation)
40351
+ continue;
40352
+ fences.push({
40353
+ host: "opencode2",
40354
+ directory: slot.slot,
40355
+ spec: slot.spec,
40356
+ version: slot.version,
40357
+ supportedVersion: readFenceFromDist(dirname18(getOpenCodePluginPackageJsonPath(generation)))
40358
+ });
40359
+ }
40360
+ return fences;
40361
+ }
40362
+ function readContextDbSchemaVersion(path) {
40363
+ let db = null;
40364
+ try {
40365
+ db = openExistingDatabase(path, { readonly: true });
40366
+ return db === null ? null : getPersistedSchemaVersion(db);
40367
+ } catch {
40368
+ return null;
40369
+ } finally {
40370
+ db?.close();
40371
+ }
40372
+ }
40373
+ function compareCachedPluginFences(fences, databaseVersion) {
40374
+ return fences.map((fence) => ({
40375
+ ...fence,
40376
+ databaseVersion,
40377
+ status: fence.supportedVersion === null ? "unknown" : fence.supportedVersion < databaseVersion ? "behind" : "supported"
40378
+ }));
40379
+ }
40380
+ function hostLabel(fence) {
40381
+ if (fence.host === "opencode1")
40382
+ return "OpenCode 1";
40383
+ return fence.spec && fence.spec !== "latest" ? `OpenCode 2 (@${fence.spec})` : "OpenCode 2";
40384
+ }
40385
+ function reportCachedPluginFences(findings, report) {
40386
+ const supported = [];
40387
+ let behind = 0;
40388
+ for (const finding of findings) {
40389
+ const label = hostLabel(finding);
40390
+ const version = finding.version ?? "unknown version";
40391
+ if (finding.status === "supported") {
40392
+ supported.push(`${label} ${version} (fence v${finding.supportedVersion})`);
40393
+ continue;
40394
+ }
40395
+ if (finding.status === "unknown") {
40396
+ 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}`);
40397
+ report.info(` ${finding.directory}`);
40398
+ continue;
40399
+ }
40400
+ behind++;
40401
+ 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.`);
40402
+ report.info(` ${finding.directory}`);
40403
+ 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.`);
40404
+ }
40405
+ if (supported.length > 0) {
40406
+ report.pass(`Cached Magic Context copies support context.db v${findings[0]?.databaseVersion}: ${supported.join(", ")}`);
40407
+ }
40408
+ return { behind };
40409
+ }
40410
+ var init_doctor_cached_plugin_fence = __esm(async () => {
40411
+ init_opencode_plugin_cache();
40412
+ init_opencode_plugin_schema_fence();
40413
+ init_doctor_opencode_cache();
40414
+ init_doctor_opencode2_cache();
40415
+ await __promiseAll([
40416
+ init_storage_db(),
40417
+ init_database_access()
40418
+ ]);
40419
+ });
40420
+
40049
40421
  // src/commands/doctor-compaction-markers.ts
40050
40422
  function safeData(column = "data") {
40051
40423
  return `CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END`;
@@ -40101,6 +40473,21 @@ function countUnmatchedConvertedMarkers(db, migratedV2Schema) {
40101
40473
  )`).get();
40102
40474
  return Number(row?.count ?? 0);
40103
40475
  }
40476
+ function countPostConversionMessages(db, migratedV2Schema) {
40477
+ if (!migratedV2Schema || !tableExists4(db, "message") || !tableExists4(db, "session")) {
40478
+ return { messages: 0, sessions: 0 };
40479
+ }
40480
+ const row = db.prepare(`SELECT COUNT(*) AS messages, COUNT(DISTINCT converted.session_id) AS sessions
40481
+ FROM session_message converted
40482
+ WHERE EXISTS (SELECT 1 FROM session legacy WHERE legacy.id = converted.session_id)
40483
+ AND NOT EXISTS (
40484
+ SELECT 1
40485
+ FROM message source
40486
+ WHERE source.session_id = converted.session_id
40487
+ AND source.time_created = converted.time_created
40488
+ )`).get();
40489
+ return { messages: Number(row?.messages ?? 0), sessions: Number(row?.sessions ?? 0) };
40490
+ }
40104
40491
  function checkOpenCodeCompactionMarkerConversion(db, options = {}) {
40105
40492
  const missingBefore = countMissingCompleted(db);
40106
40493
  let repaired = 0;
@@ -40121,6 +40508,7 @@ function checkOpenCodeCompactionMarkerConversion(db, options = {}) {
40121
40508
  const migratedV2Schema = tableExists4(db, "session_v2") && tableExists4(db, "session_message");
40122
40509
  const migrationCompleted = migrationV1ToV2Completed(db);
40123
40510
  const unmatchedConvertedMarkers = countUnmatchedConvertedMarkers(db, migratedV2Schema);
40511
+ const postConversion = countPostConversionMessages(db, migratedV2Schema);
40124
40512
  return {
40125
40513
  missingBefore,
40126
40514
  missingAfter,
@@ -40128,21 +40516,23 @@ function checkOpenCodeCompactionMarkerConversion(db, options = {}) {
40128
40516
  migrationCompleted,
40129
40517
  migratedV2Schema,
40130
40518
  unmatchedConvertedMarkers,
40131
- recoveryRequired: migrationCompleted && migratedV2Schema && unmatchedConvertedMarkers > 0
40519
+ postConversionMessages: postConversion.messages,
40520
+ postConversionSessions: postConversion.sessions
40132
40521
  };
40133
40522
  }
40134
40523
  function formatOpenCodeCompactionMarkerConversion(report) {
40135
40524
  return `OpenCode compaction markers are convertible to OpenCode 2: before=${report.missingBefore} missing time.completed; after=${report.missingAfter}`;
40136
40525
  }
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'
40526
+ function formatOpenCodeV2MissingMarkerNotice(report) {
40527
+ if (!report.migrationCompleted || !report.migratedV2Schema || report.unmatchedConvertedMarkers === 0) {
40528
+ return null;
40529
+ }
40530
+ const lines = [
40531
+ `OpenCode 2's conversion did not carry over ${report.unmatchedConvertedMarkers} Magic Context compaction marker(s). No action is needed.`,
40532
+ "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
40533
  ];
40534
+ 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.");
40535
+ return lines;
40146
40536
  }
40147
40537
 
40148
40538
  // src/commands/doctor-compartment-boundaries.ts
@@ -40226,88 +40616,6 @@ var init_doctor_harness_relabel = __esm(async () => {
40226
40616
  await init_opencode2_relabel();
40227
40617
  });
40228
40618
 
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
40619
  // src/commands/doctor-opencode-plugin-duplicates.ts
40312
40620
  import { isDeepStrictEqual } from "node:util";
40313
40621
  function entryOptions(entry) {
@@ -40535,10 +40843,21 @@ function formatUnresolvedCompartmentSession(session) {
40535
40843
 
40536
40844
  // src/commands/doctor-opencode.ts
40537
40845
  import { spawnSync as spawnSync8 } from "node:child_process";
40538
- import { existsSync as existsSync37, mkdtempSync, readFileSync as readFileSync26, rmSync as rmSync5 } from "node:fs";
40846
+ import { existsSync as existsSync38, mkdtempSync, readFileSync as readFileSync26, rmSync as rmSync5 } from "node:fs";
40539
40847
  import { createRequire as createRequire6 } from "node:module";
40540
40848
  import { homedir as homedir16, tmpdir as tmpdir3 } from "node:os";
40541
40849
  import { join as join32 } from "node:path";
40850
+ function formatSharedDbRowCounts(db) {
40851
+ return SHARED_DB_ROW_COUNT_TABLES.map((table) => {
40852
+ try {
40853
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get();
40854
+ return `${table}=${typeof row?.c === "number" ? row.c : "n/a"}`;
40855
+ } catch (error) {
40856
+ const reason = error instanceof Error ? error.message : String(error);
40857
+ return `${table}=n/a (${sanitizeDiagnosticText(reason)})`;
40858
+ }
40859
+ }).join(", ");
40860
+ }
40542
40861
  function findUndeclaredConfiguredVariants(configured, catalog) {
40543
40862
  return configured.filter((entry) => {
40544
40863
  const [providerID, id] = entry.model.split("/", 2);
@@ -40546,6 +40865,18 @@ function findUndeclaredConfiguredVariants(configured, catalog) {
40546
40865
  return model !== undefined && !Object.hasOwn(model.variants, entry.variant);
40547
40866
  });
40548
40867
  }
40868
+ function findUnknownConfiguredModels(configured, catalog) {
40869
+ return configured.flatMap((entry) => {
40870
+ const [providerID, id] = entry.model.split("/", 2);
40871
+ if (!catalog.some((item) => item.providerID === providerID)) {
40872
+ return [{ ...entry, unknown: "provider" }];
40873
+ }
40874
+ if (!catalog.some((item) => item.providerID === providerID && item.id === id)) {
40875
+ return [{ ...entry, unknown: "model" }];
40876
+ }
40877
+ return [];
40878
+ });
40879
+ }
40549
40880
  function checkConfiguredVariantCatalog(config, hostGeneration, warn, run = (args, cwd) => spawnSync8("opencode", args, {
40550
40881
  cwd,
40551
40882
  encoding: "utf8",
@@ -40553,6 +40884,7 @@ function checkConfiguredVariantCatalog(config, hostGeneration, warn, run = (args
40553
40884
  maxBuffer: 16 * 1024 * 1024
40554
40885
  }), projectDir = process.cwd()) {
40555
40886
  const configured = [];
40887
+ const models = [];
40556
40888
  const root = config && typeof config === "object" ? config : {};
40557
40889
  for (const agent of ["historian", "dreamer"]) {
40558
40890
  const section = root[agent];
@@ -40562,21 +40894,25 @@ function checkConfiguredVariantCatalog(config, hostGeneration, warn, run = (args
40562
40894
  if (!block || typeof block !== "object")
40563
40895
  continue;
40564
40896
  const record = block;
40565
- const add = (entry, defaultVariant) => {
40897
+ const add = (entry, defaultVariant, fallback) => {
40566
40898
  const value = entry && typeof entry === "object" ? entry : {};
40567
40899
  const model = typeof entry === "string" ? entry : value.model;
40568
40900
  const variant = value.variant ?? defaultVariant;
40569
- if (typeof model === "string" && typeof variant === "string" && model.includes("/")) {
40570
- configured.push({ agent, model, variant });
40901
+ if (typeof model !== "string" || !model.includes("/"))
40902
+ return;
40903
+ if (!models.some((known) => known.agent === agent && known.model === model)) {
40904
+ models.push({ agent, model, fallback });
40571
40905
  }
40906
+ if (typeof variant === "string")
40907
+ configured.push({ agent, model, variant });
40572
40908
  };
40573
- add(record.model, record.variant);
40909
+ add(record.model, record.variant, false);
40574
40910
  if (Array.isArray(record.fallback_models)) {
40575
40911
  for (const entry of record.fallback_models)
40576
- add(entry, undefined);
40912
+ add(entry, undefined, true);
40577
40913
  }
40578
40914
  }
40579
- if (configured.length === 0)
40915
+ if (models.length === 0)
40580
40916
  return;
40581
40917
  const tempRoot = mkdtempSync(join32(tmpdir3(), "magic-context-opencode-catalog-"));
40582
40918
  try {
@@ -40585,9 +40921,14 @@ function checkConfiguredVariantCatalog(config, hostGeneration, warn, run = (args
40585
40921
  const result = run(command, tempRoot);
40586
40922
  const catalog = hostGeneration === "v2" ? parseOpenCodeV2ModelCatalog(result.stdout ?? "") : parseOpenCodeModelCatalog(result.stdout ?? "");
40587
40923
  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}.`);
40924
+ 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
40925
  return;
40590
40926
  }
40927
+ for (const entry of findUnknownConfiguredModels(models, catalog)) {
40928
+ const [providerID] = entry.model.split("/", 1);
40929
+ const role = `${entry.agent}${entry.fallback ? " fallback" : ""} model ${entry.model}`;
40930
+ 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}.`);
40931
+ }
40591
40932
  for (const entry of findUndeclaredConfiguredVariants(configured, catalog)) {
40592
40933
  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
40934
  }
@@ -40763,7 +41104,7 @@ function getUserNpmrcPath() {
40763
41104
  function collectNpmReleaseAgeWarnings() {
40764
41105
  const ageWarnings = [];
40765
41106
  const npmrcPath = getUserNpmrcPath();
40766
- if (!existsSync37(npmrcPath))
41107
+ if (!existsSync38(npmrcPath))
40767
41108
  return ageWarnings;
40768
41109
  try {
40769
41110
  const npmrc = readFileSync26(npmrcPath, "utf-8");
@@ -40900,7 +41241,7 @@ function checkLocalEmbeddingRuntimeForDoctor(runtimePreference = "auto") {
40900
41241
  return { issues: 0 };
40901
41242
  }
40902
41243
  async function checkEmbeddingConfig(magicContextConfigPath) {
40903
- if (!existsSync37(magicContextConfigPath)) {
41244
+ if (!existsSync38(magicContextConfigPath)) {
40904
41245
  return checkLocalEmbeddingRuntimeForDoctor();
40905
41246
  }
40906
41247
  let rawText;
@@ -41108,7 +41449,7 @@ async function runDoctor2(options = {}) {
41108
41449
  const activeInstallation = installationReports[0];
41109
41450
  if (!activeInstallation) {
41110
41451
  fail("OpenCode is not installed or not in PATH");
41111
- log2.info("Doctor checked ~/.opencode/bin/opencode and each entry in $PATH.");
41452
+ log2.info("Doctor checked ~/.opencode/bin/opencode, each entry in $PATH, and the OpenCode CLI bundled in OpenChamber.app.");
41112
41453
  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
41454
  outro2("Doctor failed — install OpenCode first");
41114
41455
  return 1;
@@ -41124,7 +41465,19 @@ async function runDoctor2(options = {}) {
41124
41465
  pass(installationReports.length > 1 ? `OpenCode ${activeInstallation.version} installed (active install marked above)` : `OpenCode ${activeInstallation.version} installed`);
41125
41466
  }
41126
41467
  const hostGeneration = openCodeHostGenerationFromVersion(activeInstallation.version);
41127
- const openCodeDbResolution = resolveOpenCodeDbPath(hostGeneration);
41468
+ const storeHostSelection = selectOpenCodeStoreHost(installationReports, openCodeHostGenerationFromVersion);
41469
+ const storeHost = storeHostSelection?.host ?? activeInstallation;
41470
+ const storeGeneration = openCodeHostGenerationFromVersion(storeHost.version);
41471
+ const openCodeDbResolution = resolveOpenCodeDbPath(storeGeneration);
41472
+ if (storeHostSelection?.shadowed) {
41473
+ const activeStore = resolveOpenCodeDbPath(hostGeneration).path;
41474
+ const shared = activeStore === openCodeDbResolution.path;
41475
+ const versionLabel = (version) => version.replace(/^opencode\s+v?/i, "");
41476
+ const activeVersion = versionLabel(activeInstallation.version);
41477
+ const storeVersion = versionLabel(storeHost.version);
41478
+ warn(`OpenCode ${activeVersion} (${activeInstallation.path}) is first on PATH, and OpenCode ${storeVersion} is also installed (${storeHost.path})${shared ? `; both use ${openCodeDbResolution.path}` : ""}.`);
41479
+ 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.`);
41480
+ }
41128
41481
  const openCodeDbCheck = describeOpenCodeDatabaseDoctorCheck(openCodeDbResolution);
41129
41482
  if (openCodeDbCheck.ok)
41130
41483
  pass(openCodeDbCheck.message);
@@ -41148,14 +41501,16 @@ async function runDoctor2(options = {}) {
41148
41501
  fixed += report.repaired;
41149
41502
  } else if (options.fix) {
41150
41503
  warn(`${summary}; repaired=${report.repaired}, but some rows remain unconvertible`);
41504
+ } else if (report.migrationCompleted) {
41505
+ log2.info(`${summary}; OpenCode 2 already converted this store`);
41151
41506
  } else {
41152
41507
  warn(`${summary}; run \`magic-context doctor --fix\` before upgrading OpenCode`);
41153
41508
  }
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}`);
41509
+ const notice = formatOpenCodeV2MissingMarkerNotice(report);
41510
+ if (notice) {
41511
+ for (const [index, line] of notice.entries()) {
41512
+ log2.info(index === 0 ? line : ` ${line}`);
41513
+ }
41159
41514
  }
41160
41515
  } catch (error) {
41161
41516
  warn(`OpenCode compaction marker conversion check unavailable: ${error instanceof Error ? error.message : String(error)}`);
@@ -41171,9 +41526,14 @@ async function runDoctor2(options = {}) {
41171
41526
  readonly: true,
41172
41527
  fileMustExist: true
41173
41528
  });
41174
- const dangling = listDanglingCompartmentBoundaries(contextDb, sessionDb, /\d/.test(activeInstallation.version) ? hostGeneration : undefined, (line) => log2.info(line));
41529
+ const dangling = listDanglingCompartmentBoundaries(contextDb, sessionDb, /\d/.test(storeHost.version) ? storeGeneration : undefined, (line) => log2.info(line));
41175
41530
  if (dangling.length === 0) {
41176
41531
  pass("Compartment boundary ids resolve in the OpenCode session store");
41532
+ } else if (storeGeneration === "v2" && /\d/.test(storeHost.version)) {
41533
+ 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.`);
41534
+ for (const boundary of dangling) {
41535
+ log2.info(` ${formatDanglingCompartmentBoundary(boundary)}`);
41536
+ }
41177
41537
  } else {
41178
41538
  warn(`${dangling.length} compartment(s) have dangling OpenCode boundary ids`);
41179
41539
  for (const boundary of dangling) {
@@ -41182,11 +41542,11 @@ async function runDoctor2(options = {}) {
41182
41542
  }
41183
41543
  if (!supportsCoordinateGenerationReporting(contextDb)) {
41184
41544
  log2.info("Store projection check: this context database predates the coordinate columns");
41185
- } else if (!/\d/.test(activeInstallation.version)) {
41545
+ } else if (!/\d/.test(storeHost.version)) {
41186
41546
  log2.info("Store projection check: OpenCode reported no version, so the running projection is unknown");
41187
41547
  } else {
41188
- const pendingRebases = countPendingCoordinateRebases(contextDb, hostGeneration);
41189
- const pendingLine = formatPendingCoordinateRebases(pendingRebases, hostGeneration);
41548
+ const pendingRebases = countPendingCoordinateRebases(contextDb, storeGeneration);
41549
+ const pendingLine = formatPendingCoordinateRebases(pendingRebases, storeGeneration);
41190
41550
  if (pendingRebases.changed + pendingRebases.unrecorded === 0) {
41191
41551
  pass(pendingLine);
41192
41552
  } else {
@@ -41231,7 +41591,7 @@ async function runDoctor2(options = {}) {
41231
41591
  pass(`OpenCode config: ${paths.opencodeConfig}`);
41232
41592
  }
41233
41593
  let autoUpdateEnabled = true;
41234
- if (existsSync37(paths.magicContextConfig)) {
41594
+ if (existsSync38(paths.magicContextConfig)) {
41235
41595
  pass(`Magic Context config: ${paths.magicContextConfig}`);
41236
41596
  try {
41237
41597
  const raw = loadRawConfigFile({ configPath: paths.magicContextConfig, tier: "user" });
@@ -41267,7 +41627,7 @@ async function runDoctor2(options = {}) {
41267
41627
  warn(`No magic-context.jsonc found — using defaults`);
41268
41628
  log2.info(" Run 'setup' to create one with model recommendations");
41269
41629
  }
41270
- if (existsSync37(paths.magicContextConfig)) {
41630
+ if (existsSync38(paths.magicContextConfig)) {
41271
41631
  try {
41272
41632
  const raw = loadRawConfigFile({ configPath: paths.magicContextConfig, tier: "user" });
41273
41633
  if (!raw)
@@ -41466,7 +41826,7 @@ async function runDoctor2(options = {}) {
41466
41826
  pass("Added TUI sidebar plugin to tui.json");
41467
41827
  warn("Restart OpenCode to see the sidebar");
41468
41828
  fixed++;
41469
- } else if (existsSync37(paths.tuiConfig)) {
41829
+ } else if (existsSync38(paths.tuiConfig)) {
41470
41830
  try {
41471
41831
  const tuiRaw = readFileSync26(paths.tuiConfig, "utf-8");
41472
41832
  const tuiConfig = import_comment_json11.parse(tuiRaw);
@@ -41506,7 +41866,7 @@ async function runDoctor2(options = {}) {
41506
41866
  fail("Could not create or verify the TUI sidebar config");
41507
41867
  }
41508
41868
  }
41509
- if (existsSync37(paths.magicContextConfig)) {
41869
+ if (existsSync38(paths.magicContextConfig)) {
41510
41870
  try {
41511
41871
  const raw = loadRawConfigFile({ configPath: paths.magicContextConfig, tier: "user" });
41512
41872
  if (!raw)
@@ -41530,7 +41890,7 @@ async function runDoctor2(options = {}) {
41530
41890
  const storage = getMagicContextStorageResolution();
41531
41891
  const dbPath = join32(storage.path, "context.db");
41532
41892
  log2.info(`Shared storage: ${storage.path} (source: ${storage.source})`);
41533
- if (!existsSync37(dbPath)) {
41893
+ if (!existsSync38(dbPath)) {
41534
41894
  log2.info(`Shared context DB not yet created at ${dbPath} (will be created on first run)`);
41535
41895
  } else {
41536
41896
  log2.info(`Shared context DB exists at ${dbPath}`);
@@ -41577,25 +41937,7 @@ ${formatDatabaseRepairGuidance(dbPath)}`);
41577
41937
  fail(`SQLite integrity_check failed: ${err instanceof Error ? err.message : String(err)}
41578
41938
  ${formatDatabaseRepairGuidance(dbPath)}`);
41579
41939
  }
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 {}
41940
+ log2.info(`Shared DB row counts: ${formatSharedDbRowCounts(db)}`);
41599
41941
  for (const stall of listShadowBackfillStalls(db)) {
41600
41942
  warn(formatShadowBackfillStall(stall));
41601
41943
  }
@@ -41664,6 +42006,10 @@ ${formatDatabaseRepairGuidance(dbPath)}`);
41664
42006
  fixed++;
41665
42007
  if (v2Cache.issue)
41666
42008
  issues++;
42009
+ const sharedDbVersion = readContextDbSchemaVersion(dbPath);
42010
+ if (sharedDbVersion !== null) {
42011
+ reportCachedPluginFences(compareCachedPluginFences(listCachedOpenCodePluginFences(), sharedDbVersion), { pass, fail, info: (message) => log2.info(message) });
42012
+ }
41667
42013
  {
41668
42014
  const ageWarnings = collectNpmReleaseAgeWarnings();
41669
42015
  if (ageWarnings.length > 0) {
@@ -41676,7 +42022,11 @@ ${formatDatabaseRepairGuidance(dbPath)}`);
41676
42022
  issues++;
41677
42023
  }
41678
42024
  }
41679
- const logFiles = inspectMagicContextLogs("opencode");
42025
+ const logHarnesses = [
42026
+ ...hostGeneration === "v1" ? ["opencode"] : [],
42027
+ ...hostGeneration === "v2" || storeGeneration === "v2" ? ["opencode2"] : []
42028
+ ];
42029
+ const logFiles = logHarnesses.flatMap((harness) => inspectMagicContextLogs(harness)).filter((file, index, all) => all.findIndex((other) => other.path === file.path) === index);
41680
42030
  const existingLogFiles = logFiles.filter((file) => file.exists);
41681
42031
  if (existingLogFiles.length === 0) {
41682
42032
  log2.info(`No plugin log file yet; checked: ${logFiles.map((file) => file.path).join(", ")}`);
@@ -41724,7 +42074,7 @@ ${formatDatabaseRepairGuidance(dbPath)}`);
41724
42074
  }
41725
42075
  return 0;
41726
42076
  }
41727
- var import_comment_json11, CLI_PACKAGE_NAME = "@cortexkit/magic-context";
42077
+ var import_comment_json11, CLI_PACKAGE_NAME = "@cortexkit/magic-context", SHARED_DB_ROW_COUNT_TABLES;
41728
42078
  var init_doctor_opencode = __esm(async () => {
41729
42079
  init_config();
41730
42080
  init_agent_disable();
@@ -41768,13 +42118,15 @@ var init_doctor_opencode = __esm(async () => {
41768
42118
  init_storage_versions(),
41769
42119
  init_v22_backfill_commands(),
41770
42120
  init_doctor_authority(),
42121
+ init_doctor_cached_plugin_fence(),
41771
42122
  init_doctor_harness_relabel()
41772
42123
  ]);
41773
42124
  import_comment_json11 = __toESM(require_src3(), 1);
42125
+ SHARED_DB_ROW_COUNT_TABLES = ["tags", "compartments", "memories", "notes", "dream_runs"];
41774
42126
  });
41775
42127
 
41776
42128
  // src/lib/diagnostics-pi.ts
41777
- import { existsSync as existsSync38, readdirSync as readdirSync7, readFileSync as readFileSync27, statSync as statSync16 } from "node:fs";
42129
+ import { existsSync as existsSync39, readdirSync as readdirSync7, readFileSync as readFileSync27, statSync as statSync16 } from "node:fs";
41778
42130
  import { createRequire as createRequire7 } from "node:module";
41779
42131
  import { join as join33 } from "node:path";
41780
42132
  function getSelfVersion3() {
@@ -41791,7 +42143,7 @@ function getSelfVersion3() {
41791
42143
  }
41792
42144
  function fileSize2(path) {
41793
42145
  try {
41794
- return existsSync38(path) ? statSync16(path).size : 0;
42146
+ return existsSync39(path) ? statSync16(path).size : 0;
41795
42147
  } catch {
41796
42148
  return 0;
41797
42149
  }
@@ -41803,7 +42155,7 @@ function sanitizeValue2(value) {
41803
42155
  return sanitizeConfigValue(value);
41804
42156
  }
41805
42157
  function readJsonc(path) {
41806
- if (!existsSync38(path))
42158
+ if (!existsSync39(path))
41807
42159
  return { value: {} };
41808
42160
  try {
41809
42161
  return {
@@ -41823,7 +42175,7 @@ function readConfigDiagnostic(path) {
41823
42175
  const parsed = readJsonc(path);
41824
42176
  return {
41825
42177
  path,
41826
- exists: existsSync38(path),
42178
+ exists: existsSync39(path),
41827
42179
  ...parsed.parseError ? { parseError: parsed.parseError } : {},
41828
42180
  flags: sanitizeValue2(parsed.value)
41829
42181
  };
@@ -41841,7 +42193,7 @@ function reverseSlugToDirectory(slug) {
41841
42193
  }
41842
42194
  function collectPiRecentSessions() {
41843
42195
  const sessionsRoot = getPiSessionsRoot();
41844
- if (!existsSync38(sessionsRoot))
42196
+ if (!existsSync39(sessionsRoot))
41845
42197
  return [];
41846
42198
  try {
41847
42199
  const slugs = readdirSync7(sessionsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -41910,7 +42262,7 @@ function parseHistorianDumpMeta2(path) {
41910
42262
  }
41911
42263
  }
41912
42264
  function listDumpsInDir2(dir, limit) {
41913
- if (!existsSync38(dir))
42265
+ if (!existsSync39(dir))
41914
42266
  return { count: 0, recent: [] };
41915
42267
  try {
41916
42268
  const entries = readdirSync7(dir).filter((name) => name.endsWith(".xml")).map((name) => {
@@ -42004,7 +42356,7 @@ async function collectDiagnostics2(cwd = process.cwd()) {
42004
42356
  piVersion: pi ? getPiVersion(pi.path) : null,
42005
42357
  settings: {
42006
42358
  path: settingsPath,
42007
- exists: existsSync38(settingsPath),
42359
+ exists: existsSync39(settingsPath),
42008
42360
  ...settingsParsed.parseError ? { parseError: settingsParsed.parseError } : {},
42009
42361
  hasMagicContextPackage: hasPiMagicContextPackage(packages),
42010
42362
  packages: sanitizeValue2(packages)
@@ -42021,7 +42373,7 @@ async function collectDiagnostics2(cwd = process.cwd()) {
42021
42373
  storageDir: {
42022
42374
  path: storageDirPath,
42023
42375
  source: storageResolution.source,
42024
- exists: existsSync38(storageDirPath),
42376
+ exists: existsSync39(storageDirPath),
42025
42377
  contextDbSizeBytes: fileSize2(dbPath)
42026
42378
  },
42027
42379
  conflicts: {
@@ -42127,7 +42479,7 @@ var init_diagnostics_pi = __esm(() => {
42127
42479
 
42128
42480
  // src/lib/logs-pi.ts
42129
42481
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
42130
- import { dirname as dirname18, join as join34 } from "node:path";
42482
+ import { dirname as dirname19, join as join34 } from "node:path";
42131
42483
  function sanitizeLogContent2(content) {
42132
42484
  return sanitizeString2(content);
42133
42485
  }
@@ -42198,7 +42550,7 @@ async function bundleIssueReport2(report, description, title, options = {}) {
42198
42550
  const cwd = options.cwd ?? process.cwd();
42199
42551
  const timestamp = formatTimestamp2(options.now ?? new Date);
42200
42552
  const path = options.outputPath ?? join34(cwd, `magic-context-pi-issue-${timestamp}.md`);
42201
- mkdirSync10(dirname18(path), { recursive: true });
42553
+ mkdirSync10(dirname19(path), { recursive: true });
42202
42554
  writeFileSync9(path, `${bodyMarkdown}
42203
42555
  `);
42204
42556
  const fullPath = truncated ? options.outputPath ? `${options.outputPath}.full.md` : join34(cwd, `magic-context-pi-issue-${timestamp}-full.md`) : undefined;
@@ -42215,10 +42567,10 @@ var init_logs_pi = __esm(() => {
42215
42567
 
42216
42568
  // src/commands/doctor-pi.ts
42217
42569
  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";
42570
+ import { existsSync as existsSync40, mkdirSync as mkdirSync11, readdirSync as readdirSync8, readFileSync as readFileSync28, rmSync as rmSync6, statSync as statSync17 } from "node:fs";
42219
42571
  import { createRequire as createRequire8 } from "node:module";
42220
42572
  import { homedir as homedir17 } from "node:os";
42221
- import { dirname as dirname19, isAbsolute as isAbsolute8, join as join35 } from "node:path";
42573
+ import { dirname as dirname20, isAbsolute as isAbsolute8, join as join35 } from "node:path";
42222
42574
  function depsFrom(options) {
42223
42575
  return {
42224
42576
  ...DEFAULT_DEPS5,
@@ -42361,13 +42713,13 @@ function piPluginDirCandidates(packages, cwd) {
42361
42713
  }
42362
42714
  dirs.push(join35(agentDir, "npm", "node_modules", PACKAGE_NAME2));
42363
42715
  dirs.push(join35(cwd, ".pi", "npm", "node_modules", PACKAGE_NAME2));
42364
- return dirs.filter((dir) => existsSync39(join35(dir, "package.json")));
42716
+ return dirs.filter((dir) => existsSync40(join35(dir, "package.json")));
42365
42717
  }
42366
42718
  function projectConfigPath(cwd) {
42367
42719
  return resolveCortexKitProjectConfigPath(cwd);
42368
42720
  }
42369
42721
  function readConfigForEmbedding(path, isProjectConfig) {
42370
- if (!existsSync39(path))
42722
+ if (!existsSync40(path))
42371
42723
  return null;
42372
42724
  try {
42373
42725
  const raw = loadRawConfigFile({
@@ -42468,7 +42820,7 @@ function cacheRoots(cwd) {
42468
42820
  function findPiMagicContextCacheDirs(cwd, expectedVersion, force = false) {
42469
42821
  const found = new Map;
42470
42822
  const visit = (path, depth) => {
42471
- if (depth < 0 || !existsSync39(path))
42823
+ if (depth < 0 || !existsSync40(path))
42472
42824
  return;
42473
42825
  let stat;
42474
42826
  try {
@@ -42479,7 +42831,7 @@ function findPiMagicContextCacheDirs(cwd, expectedVersion, force = false) {
42479
42831
  if (!stat.isDirectory())
42480
42832
  return;
42481
42833
  const packageJson = join35(path, "package.json");
42482
- if (existsSync39(packageJson)) {
42834
+ if (existsSync40(packageJson)) {
42483
42835
  try {
42484
42836
  const pkg = JSON.parse(readFileSync28(packageJson, "utf-8"));
42485
42837
  const name = typeof pkg.name === "string" ? pkg.name : "";
@@ -42546,7 +42898,7 @@ async function runHealthChecks2(options) {
42546
42898
  }
42547
42899
  const settingsPath = getPiUserExtensionsPath();
42548
42900
  let packages = [];
42549
- if (!existsSync39(settingsPath)) {
42901
+ if (!existsSync40(settingsPath)) {
42550
42902
  add2(results, "fail", `Pi settings not found at ${settingsPath}`);
42551
42903
  repairPlan.addPackageEntry = true;
42552
42904
  } else {
@@ -42570,7 +42922,7 @@ async function runHealthChecks2(options) {
42570
42922
  ["user", userConfigPath, true],
42571
42923
  ["project", projectPath, false]
42572
42924
  ]) {
42573
- if (!existsSync39(path)) {
42925
+ if (!existsSync40(path)) {
42574
42926
  if (required) {
42575
42927
  add2(results, "warn", `No ${label} magic-context.jsonc found at ${path}`);
42576
42928
  if (userConfigMigrationRefused) {
@@ -42612,7 +42964,7 @@ async function runHealthChecks2(options) {
42612
42964
  const storageDir = storage.path;
42613
42965
  const dbPath = join35(storageDir, "context.db");
42614
42966
  add2(results, "info", `Shared storage: ${storageDir} (source: ${storage.source})`);
42615
- const existedBeforeOpen = existsSync39(dbPath);
42967
+ const existedBeforeOpen = existsSync40(dbPath);
42616
42968
  if (existedBeforeOpen)
42617
42969
  add2(results, "pass", `Shared context DB exists at ${dbPath}`);
42618
42970
  else
@@ -42830,7 +43182,7 @@ ${formatDatabaseRepairGuidance(dbPath)}`);
42830
43182
  return { results, repairPlan, ...summarize(results) };
42831
43183
  }
42832
43184
  function writeDefaultMagicContextConfig(path) {
42833
- mkdirSync11(dirname19(path), { recursive: true });
43185
+ mkdirSync11(dirname20(path), { recursive: true });
42834
43186
  const config = {
42835
43187
  $schema: "https://raw.githubusercontent.com/cortexkit/magic-context/master/assets/magic-context.schema.json",
42836
43188
  ...MagicContextConfigSchema.parse({})
@@ -42852,7 +43204,7 @@ function repair2(plan, prompts) {
42852
43204
  }
42853
43205
  if (plan.writeUserConfig) {
42854
43206
  const configPath = getPiUserConfigPath();
42855
- if (!existsSync39(configPath)) {
43207
+ if (!existsSync40(configPath)) {
42856
43208
  try {
42857
43209
  writeDefaultMagicContextConfig(configPath);
42858
43210
  prompts.log.success(`Wrote default Magic Context config to ${configPath}`);
@@ -43104,7 +43456,7 @@ var init_doctor_pi = __esm(async () => {
43104
43456
  });
43105
43457
 
43106
43458
  // src/commands/doctor.ts
43107
- import { existsSync as existsSync40, rmSync as rmSync7 } from "node:fs";
43459
+ import { existsSync as existsSync41, rmSync as rmSync7 } from "node:fs";
43108
43460
  import { join as join36 } from "node:path";
43109
43461
  async function runDoctor4(options) {
43110
43462
  if (options.clear)
@@ -43251,7 +43603,7 @@ async function runClear() {
43251
43603
  failed += 1;
43252
43604
  continue;
43253
43605
  }
43254
- } else if (existsSync40(item.path)) {
43606
+ } else if (existsSync41(item.path)) {
43255
43607
  rmSync7(item.path, { recursive: true, force: true });
43256
43608
  }
43257
43609
  s.stop(`Cleared ${item.path}`);
@@ -43288,9 +43640,121 @@ var init_doctor = __esm(async () => {
43288
43640
  });
43289
43641
 
43290
43642
  // src/index.ts
43291
- init_prompts();
43292
43643
  import { createRequire as createRequire9 } from "node:module";
43293
43644
 
43645
+ // src/lib/cli-help.ts
43646
+ var HELP_FLAGS = new Set(["--help", "-h"]);
43647
+ function hasHelpFlag(args) {
43648
+ return args.some((arg) => HELP_FLAGS.has(arg));
43649
+ }
43650
+ var DOCTOR_SUBCOMMANDS_WITH_OWN_HELP = new Set([
43651
+ "migrate",
43652
+ "migrate-session",
43653
+ "repair-db"
43654
+ ]);
43655
+ var HARNESS_LINES = [
43656
+ " Harness selection:",
43657
+ " --harness opencode Target OpenCode only",
43658
+ " --harness pi Target Pi only",
43659
+ " --harness omp Target Oh My Pi (OMP) only",
43660
+ " (default: auto-detect, prompt if multiple installed)"
43661
+ ];
43662
+ var SETUP_HELP = [
43663
+ "",
43664
+ " Usage: magic-context setup [--harness opencode|pi|omp] [--dry-run]",
43665
+ "",
43666
+ " Interactive setup wizard: registers the Magic Context plugin with the",
43667
+ " selected harness and writes its Magic Context config.",
43668
+ "",
43669
+ " Options:",
43670
+ " --dry-run Preview the wizard without writing any files",
43671
+ ...HARNESS_LINES,
43672
+ ""
43673
+ ].join(`
43674
+ `);
43675
+ var DOCTOR_HELP = [
43676
+ "",
43677
+ " Usage: magic-context doctor [options]",
43678
+ " magic-context doctor <subcommand> [options]",
43679
+ "",
43680
+ " Check the installed harnesses and fix configuration issues.",
43681
+ "",
43682
+ " Options:",
43683
+ " --fix Repair safe, Magic Context-owned store rows and clear an outdated OpenCode 2 plugin cache",
43684
+ " --force Force-clear the plugin cache",
43685
+ " --clear Interactive cache cleanup picker",
43686
+ " --issue Collect diagnostics and open a GitHub issue",
43687
+ " --report <path> Write the issue diagnostics to <path> without prompting",
43688
+ " --check-v22-backfill Show v22 memory backfill status",
43689
+ " --retry-v22-backfill Retry failed v22 memory backfill rows",
43690
+ " --rekey-v22-dir-identity <path> Re-key legacy dir identity rows",
43691
+ ...HARNESS_LINES,
43692
+ "",
43693
+ " Subcommands (each accepts --help):",
43694
+ " drain-authority <project> Drain module memory/note authority back to TypeScript",
43695
+ " migrate Migrate an OpenCode session to Pi or OMP JSONL",
43696
+ " migrate-session Re-home an OpenCode session to another directory",
43697
+ " merge-identity Merge project rows between identities",
43698
+ " repair-db Back up and salvage a corrupted shared database",
43699
+ " list-hidden-sessions List Magic Context OpenCode 2 roots",
43700
+ ""
43701
+ ].join(`
43702
+ `);
43703
+ var DRAIN_AUTHORITY_HELP = [
43704
+ "",
43705
+ " Usage: magic-context doctor drain-authority <project>",
43706
+ "",
43707
+ " Drain the module's memory and note authority for <project> back to TypeScript.",
43708
+ ""
43709
+ ].join(`
43710
+ `);
43711
+ var MERGE_IDENTITY_HELP = [
43712
+ "",
43713
+ " Usage: magic-context doctor merge-identity --from <identity> --to <identity> [--dry-run] [--yes] [--db <path>]",
43714
+ "",
43715
+ " Merge every project-scoped row from one project identity into another.",
43716
+ "",
43717
+ " Options:",
43718
+ " --from <identity> Identity whose rows are moved",
43719
+ " --to <identity> Identity that receives them",
43720
+ " --dry-run Report what would change without writing",
43721
+ " --yes Confirm the write (required unless --dry-run)",
43722
+ " --db <path> Use this context.db instead of the shared one",
43723
+ ""
43724
+ ].join(`
43725
+ `);
43726
+ var LIST_HIDDEN_SESSIONS_HELP = [
43727
+ "",
43728
+ " Usage: magic-context doctor list-hidden-sessions",
43729
+ "",
43730
+ " List the hidden OpenCode 2 root sessions Magic Context reuses for its",
43731
+ " historian and dreamer runs. Read-only.",
43732
+ ""
43733
+ ].join(`
43734
+ `);
43735
+ function subcommandHelp(argv) {
43736
+ const [command, ...rest] = argv;
43737
+ if (!hasHelpFlag(rest))
43738
+ return null;
43739
+ if (command === "setup")
43740
+ return SETUP_HELP;
43741
+ if (command !== "doctor")
43742
+ return null;
43743
+ const subcommand = rest[0];
43744
+ if (subcommand === "drain-authority")
43745
+ return DRAIN_AUTHORITY_HELP;
43746
+ if (subcommand === "merge-identity")
43747
+ return MERGE_IDENTITY_HELP;
43748
+ if (subcommand === "list-hidden-sessions")
43749
+ return LIST_HIDDEN_SESSIONS_HELP;
43750
+ if (subcommand !== undefined && DOCTOR_SUBCOMMANDS_WITH_OWN_HELP.has(subcommand))
43751
+ return null;
43752
+ return DOCTOR_HELP;
43753
+ }
43754
+
43755
+ // src/index.ts
43756
+ init_prompts();
43757
+
43294
43758
  // src/lib/sqlite-preflight.ts
43295
43759
  async function probeSqliteBackend() {
43296
43760
  await init_sqlite().then(() => ({}));
@@ -43300,7 +43764,7 @@ function formatSqlitePreflightFailure(error) {
43300
43764
  return [
43301
43765
  "Magic Context doctor cannot use SQLite in this runtime.",
43302
43766
  `SQLite probe: ${detail}`,
43303
- "Remediation: install Node.js >= 24 or use a Bun build with node:sqlite.",
43767
+ "Remediation: install Node.js 22.16+ (22.x) or >= 24, or use a Bun build with node:sqlite.",
43304
43768
  "For Docker, use node:24-slim or a two-runtime image."
43305
43769
  ].join(`
43306
43770
  `);
@@ -43367,6 +43831,7 @@ function printUsage() {
43367
43831
  console.log(" (default: auto-detect, prompt if multiple installed)");
43368
43832
  console.log("");
43369
43833
  console.log(" Usage:");
43834
+ console.log(" npx @cortexkit/magic-context@latest <command> --help # help for one command");
43370
43835
  console.log(" npx @cortexkit/magic-context@latest setup");
43371
43836
  console.log(" # add --dry-run to preview the wizard without writing any files");
43372
43837
  console.log(" npx @cortexkit/magic-context@latest doctor");
@@ -43386,6 +43851,11 @@ async function main(argv = process.argv.slice(2)) {
43386
43851
  }
43387
43852
  const command = argv[0];
43388
43853
  const rest = argv.slice(1);
43854
+ const help = subcommandHelp(argv);
43855
+ if (help !== null) {
43856
+ console.log(help);
43857
+ return 0;
43858
+ }
43389
43859
  try {
43390
43860
  if (command === "setup") {
43391
43861
  await Promise.resolve().then(() => init_setup());