@cortexkit/opencode-magic-context 0.22.0 → 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +10 -0
  2. package/dist/config/agent-disable.d.ts +0 -9
  3. package/dist/config/agent-disable.d.ts.map +1 -1
  4. package/dist/config/index.d.ts.map +1 -1
  5. package/dist/config/schema/agent-overrides.d.ts +0 -3
  6. package/dist/config/schema/agent-overrides.d.ts.map +1 -1
  7. package/dist/config/schema/magic-context.d.ts +15 -0
  8. package/dist/config/schema/magic-context.d.ts.map +1 -1
  9. package/dist/features/builtin-commands/types.d.ts +0 -2
  10. package/dist/features/builtin-commands/types.d.ts.map +1 -1
  11. package/dist/features/magic-context/dreamer/scheduler.d.ts +0 -4
  12. package/dist/features/magic-context/dreamer/scheduler.d.ts.map +1 -1
  13. package/dist/features/magic-context/git-commits/index.d.ts +1 -0
  14. package/dist/features/magic-context/git-commits/index.d.ts.map +1 -1
  15. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -1
  16. package/dist/features/magic-context/git-commits/sweep-coordinator.d.ts +48 -0
  17. package/dist/features/magic-context/git-commits/sweep-coordinator.d.ts.map +1 -0
  18. package/dist/features/magic-context/key-files/storage-key-files.d.ts +0 -5
  19. package/dist/features/magic-context/key-files/storage-key-files.d.ts.map +1 -1
  20. package/dist/features/magic-context/literal-probes.d.ts +24 -0
  21. package/dist/features/magic-context/literal-probes.d.ts.map +1 -0
  22. package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
  23. package/dist/features/magic-context/memory/embedding-openai.d.ts +6 -0
  24. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  25. package/dist/features/magic-context/memory/embedding-probe.d.ts +5 -0
  26. package/dist/features/magic-context/memory/embedding-probe.d.ts.map +1 -1
  27. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  28. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  29. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  30. package/dist/features/magic-context/search.d.ts +7 -0
  31. package/dist/features/magic-context/search.d.ts.map +1 -1
  32. package/dist/features/magic-context/storage-db.d.ts +1 -1
  33. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  34. package/dist/features/magic-context/storage-notes.d.ts +8 -0
  35. package/dist/features/magic-context/storage-notes.d.ts.map +1 -1
  36. package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-runner-types.d.ts +14 -1
  38. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/derive-budgets.d.ts +3 -3
  40. package/dist/hooks/magic-context/event-resolvers.d.ts +1 -0
  41. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +7 -2
  43. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +667 -266
  46. package/dist/plugin/dream-timer.d.ts.map +1 -1
  47. package/dist/plugin/event.d.ts +10 -0
  48. package/dist/plugin/event.d.ts.map +1 -1
  49. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  50. package/dist/shared/announcement.d.ts +16 -0
  51. package/dist/shared/announcement.d.ts.map +1 -1
  52. package/dist/shared/models-dev-cache.d.ts +54 -27
  53. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  54. package/dist/shared/rpc-types.d.ts +3 -1
  55. package/dist/shared/rpc-types.d.ts.map +1 -1
  56. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  57. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  58. package/package.json +4 -4
  59. package/src/shared/announcement.test.ts +23 -7
  60. package/src/shared/announcement.ts +24 -1
  61. package/src/shared/conflict-detector.test.ts +15 -2
  62. package/src/shared/conflict-fixer.test.ts +5 -1
  63. package/src/shared/models-dev-cache.test.ts +200 -300
  64. package/src/shared/models-dev-cache.ts +184 -176
  65. package/src/shared/opencode-compaction-detector.test.ts +10 -2
  66. package/src/shared/rpc-client.test.ts +5 -1
  67. package/src/shared/rpc-types.ts +3 -1
  68. package/src/tui/index.tsx +17 -8
  69. package/src/tui/slots/sidebar-content.tsx +20 -10
package/dist/index.js CHANGED
@@ -14818,25 +14818,25 @@ var init_agent_overrides = __esm(() => {
14818
14818
  external_directory: PermissionValueSchema.optional()
14819
14819
  }).optional();
14820
14820
  AgentOverrideConfigSchema = exports_external.object({
14821
- model: exports_external.string().optional(),
14822
- temperature: exports_external.number().min(0).max(2).optional(),
14823
- top_p: exports_external.number().min(0).max(1).optional(),
14824
- prompt: exports_external.string().optional(),
14825
- tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
14826
- disable: exports_external.boolean().optional(),
14827
- description: exports_external.string().optional(),
14828
- mode: exports_external.enum(["subagent", "primary", "all"]).optional(),
14829
- color: exports_external.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
14830
- maxSteps: exports_external.number().optional(),
14831
- permission: PermissionSchema,
14832
- maxTokens: exports_external.number().optional(),
14833
- variant: exports_external.string().optional(),
14834
- fallback_models: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional()
14821
+ model: exports_external.string().optional().describe("Primary model ID (e.g. 'claude-sonnet-4-6')"),
14822
+ temperature: exports_external.number().min(0).max(2).optional().describe("Sampling temperature (0-2)"),
14823
+ top_p: exports_external.number().min(0).max(1).optional().describe("Nucleus sampling top_p (0-1)"),
14824
+ prompt: exports_external.string().optional().describe("Additional system prompt text"),
14825
+ tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional().describe("Tool enable/disable overrides"),
14826
+ disable: exports_external.boolean().optional().describe("Disable this agent"),
14827
+ description: exports_external.string().optional().describe("Agent description"),
14828
+ mode: exports_external.enum(["subagent", "primary", "all"]).optional().describe("Agent mode (subagent, primary, or all)"),
14829
+ color: exports_external.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Hex color for the agent (e.g. '#a1b2c3')"),
14830
+ maxSteps: exports_external.number().optional().describe("Maximum tool-call steps per invocation"),
14831
+ permission: PermissionSchema.describe("Per-tool permission overrides"),
14832
+ maxTokens: exports_external.number().optional().describe("Maximum output tokens"),
14833
+ variant: exports_external.string().optional().describe("OpenCode reasoning variant (e.g. for extended thinking)"),
14834
+ fallback_models: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional().describe("Fallback model IDs if primary is unavailable")
14835
14835
  });
14836
14836
  });
14837
14837
 
14838
14838
  // src/config/schema/magic-context.ts
14839
- var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DREAMER_TASKS, DreamingTaskSchema, DEFAULT_DREAMER_TASKS, PiThinkingLevelSchema, DreamerConfigSchema, SidekickConfigSchema, HistorianConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
14839
+ var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, EXECUTE_THRESHOLD_CAP_MESSAGE = "execute_threshold is capped at 80% for cache safety: a single large agent step can overflow the context window before Magic Context can compact between turns, forcing OpenCode's native compaction (hard to recover from). 80% also leaves headroom below the 85%/95% emergency bands. Use a value between 20 and 80.", DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DREAMER_TASKS, DreamingTaskSchema, DEFAULT_DREAMER_TASKS, PiThinkingLevelSchema, DreamerConfigSchema, SidekickConfigSchema, HistorianConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
14840
14840
  var init_magic_context = __esm(() => {
14841
14841
  init_zod();
14842
14842
  init_agent_overrides();
@@ -14856,36 +14856,38 @@ var init_magic_context = __esm(() => {
14856
14856
  ];
14857
14857
  PiThinkingLevelSchema = exports_external.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional();
14858
14858
  DreamerConfigSchema = AgentOverrideConfigSchema.merge(exports_external.object({
14859
- schedule: exports_external.string().default("02:00-06:00"),
14860
- max_runtime_minutes: exports_external.number().min(10).default(120),
14861
- tasks: exports_external.array(DreamingTaskSchema).default(DEFAULT_DREAMER_TASKS),
14862
- task_timeout_minutes: exports_external.number().min(5).default(20),
14863
- inject_docs: exports_external.boolean().default(true),
14859
+ schedule: exports_external.string().default("02:00-06:00").describe("Scheduled window for overnight dreaming (e.g. '02:00-06:00')"),
14860
+ max_runtime_minutes: exports_external.number().min(10).default(120).describe("Maximum runtime per dream session in minutes"),
14861
+ tasks: exports_external.array(DreamingTaskSchema).default(DEFAULT_DREAMER_TASKS).describe("Tasks to run during dreaming, in order"),
14862
+ task_timeout_minutes: exports_external.number().min(5).default(20).describe("Minutes allocated per task before moving to next"),
14863
+ inject_docs: exports_external.boolean().default(true).describe("Inject ARCHITECTURE.md and STRUCTURE.md into system prompt"),
14864
14864
  user_memories: exports_external.object({
14865
- enabled: exports_external.boolean().default(true),
14866
- promotion_threshold: exports_external.number().min(2).max(20).default(3)
14867
- }).default({ enabled: true, promotion_threshold: 3 }),
14865
+ enabled: exports_external.boolean().default(true).describe("Enable user memory extraction and promotion (default: true)"),
14866
+ promotion_threshold: exports_external.number().min(2).max(20).default(3).describe("Minimum candidate observations before dreamer considers promotion (default: 3)")
14867
+ }).default({ enabled: true, promotion_threshold: 3 }).describe("User memory pipeline: historian extracts behavior observations from each compartment run; dreamer reviews recurring patterns and promotes them to stable user memories injected into all sessions as <user-profile>. Requires dreamer to not be disabled for promotion to actually happen. Graduated from experimental in v0.14. Default: enabled."),
14868
14868
  pin_key_files: exports_external.object({
14869
- enabled: exports_external.boolean().default(false),
14870
- token_budget: exports_external.number().min(2000).max(30000).default(1e4),
14871
- min_reads: exports_external.number().min(2).max(20).default(4)
14872
- }).default({ enabled: false, token_budget: 1e4, min_reads: 4 }),
14873
- thinking_level: PiThinkingLevelSchema
14869
+ enabled: exports_external.boolean().default(false).describe("Enable key file pinning (default: false)"),
14870
+ token_budget: exports_external.number().min(2000).max(30000).default(1e4).describe("Total token budget for all pinned key files (min: 2000, max: 30000, default: 10000)"),
14871
+ min_reads: exports_external.number().min(2).max(20).default(4).describe("Minimum full-read count before a file is considered for pinning (min: 2, default: 4)")
14872
+ }).default({ enabled: false, token_budget: 1e4, min_reads: 4 }).describe("Pin frequently-read key files into the system prompt so the agent doesn't need to re-read them after context drops. Dreamer identifies key files per session based on read patterns. Requires dreamer to not be disabled for selection to happen. Graduated from experimental in v0.14. Default: disabled."),
14873
+ thinking_level: PiThinkingLevelSchema.describe("Pi only: explicit thinking level for dreamer subagent invocations. See historian.thinking_level.")
14874
14874
  }));
14875
14875
  SidekickConfigSchema = AgentOverrideConfigSchema.extend({
14876
- timeout_ms: exports_external.number().default(30000),
14877
- system_prompt: exports_external.string().optional(),
14878
- thinking_level: PiThinkingLevelSchema
14876
+ timeout_ms: exports_external.number().default(30000).describe("Timeout for sidekick calls in milliseconds"),
14877
+ system_prompt: exports_external.string().optional().describe("Custom system prompt for sidekick"),
14878
+ thinking_level: PiThinkingLevelSchema.describe("Pi only: explicit thinking level for sidekick subagent invocations. See historian.thinking_level.")
14879
14879
  }).optional();
14880
14880
  HistorianConfigSchema = AgentOverrideConfigSchema.extend({
14881
- two_pass: exports_external.boolean().default(false),
14882
- thinking_level: PiThinkingLevelSchema
14881
+ two_pass: exports_external.boolean().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)"),
14882
+ thinking_level: PiThinkingLevelSchema.describe("Pi only: explicit thinking level passed as --thinking <level> to Pi historian subagent invocations. Required when using reasoning models (e.g. github-copilot/gpt-5.4) because Pi's default thinking-level resolution can pick a value the provider rejects. OpenCode users set variant instead. Valid: off | minimal | low | medium | high | xhigh")
14883
14883
  }).optional();
14884
14884
  BaseEmbeddingConfigSchema = exports_external.object({
14885
- provider: exports_external.enum(["local", "openai-compatible", "off"]).default("local"),
14886
- model: exports_external.string().optional(),
14887
- endpoint: exports_external.string().optional(),
14888
- api_key: exports_external.string().optional()
14885
+ provider: exports_external.enum(["local", "openai-compatible", "off"]).default("local").describe("Embedding provider. 'local' uses Xenova/all-MiniLM-L6-v2, 'openai-compatible' requires endpoint and model, 'off' disables embeddings."),
14886
+ model: exports_external.string().optional().describe("Embedding model name. Required for openai-compatible, ignored for local."),
14887
+ endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
14888
+ api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
14889
+ input_type: exports_external.string().optional().describe("Optional input_type sent in the embedding request body. Required by some openai-compatible providers (e.g. NVIDIA NIM expects 'query' or 'passage'). Omitted from the request when unset."),
14890
+ truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset.")
14889
14891
  }).superRefine((data, ctx) => {
14890
14892
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
14891
14893
  ctx.addIssue({
@@ -14911,76 +14913,80 @@ var init_magic_context = __esm(() => {
14911
14913
  }
14912
14914
  if (data.provider === "openai-compatible") {
14913
14915
  const apiKey = data.api_key?.trim();
14916
+ const inputType = data.input_type?.trim();
14917
+ const truncate = data.truncate?.trim();
14914
14918
  return {
14915
14919
  provider: "openai-compatible",
14916
14920
  model: data.model?.trim() ?? "",
14917
14921
  endpoint: data.endpoint?.trim() ?? "",
14918
- ...apiKey ? { api_key: apiKey } : {}
14922
+ ...apiKey ? { api_key: apiKey } : {},
14923
+ ...inputType ? { input_type: inputType } : {},
14924
+ ...truncate ? { truncate } : {}
14919
14925
  };
14920
14926
  }
14921
14927
  return { provider: "off" };
14922
14928
  });
14923
14929
  MagicContextConfigSchema = exports_external.object({
14924
- enabled: exports_external.boolean().default(true),
14925
- auto_update: exports_external.boolean().optional(),
14926
- ctx_reduce_enabled: exports_external.boolean().default(true),
14927
- historian: HistorianConfigSchema,
14928
- dreamer: DreamerConfigSchema.optional(),
14929
- cache_ttl: exports_external.union([exports_external.string(), exports_external.object({ default: exports_external.string() }).catchall(exports_external.string())]).default("5m"),
14930
- nudge_interval_tokens: exports_external.number().min(1000).default(DEFAULT_NUDGE_INTERVAL_TOKENS),
14930
+ enabled: exports_external.boolean().default(true).describe("Enable magic context (default: true)"),
14931
+ auto_update: exports_external.boolean().optional().describe("Enable automatic npm self-update checks for the OpenCode plugin. Security: USER-only in config loader, so hostile project configs cannot suppress updates."),
14932
+ ctx_reduce_enabled: exports_external.boolean().default(true).describe("When false, ctx_reduce tool is hidden, all nudges disabled, and prompt guidance about ctx_reduce stripped. Heuristic cleanup, compartments, memory, and other features still work. (default: true)"),
14933
+ historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
14934
+ dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
14935
+ cache_ttl: exports_external.union([exports_external.string(), exports_external.object({ default: exports_external.string() }).catchall(exports_external.string())]).default("5m").describe('Cache TTL: string (e.g. "5m") or per-model object ({ default: "5m", "model-id": "10m" })'),
14936
+ nudge_interval_tokens: exports_external.number().min(1000).default(DEFAULT_NUDGE_INTERVAL_TOKENS).describe("Minimum token growth between low-priority rolling nudges (default: DEFAULT_NUDGE_INTERVAL_TOKENS)"),
14931
14937
  execute_threshold_percentage: exports_external.union([
14932
- exports_external.number().min(20).max(80),
14933
- exports_external.object({ default: exports_external.number().min(20).max(80) }).catchall(exports_external.number().min(20).max(80))
14934
- ]).default(DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE),
14938
+ exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE),
14939
+ exports_external.object({ default: exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE) }).catchall(exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE))
14940
+ ]).default(DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE).describe('Context percentage that forces queued operations to execute. Number or per-model object ({ default: 65, "provider/model": 45 }). Values above 80 are rejected because the runtime caps at 80% for cache safety (MAX_EXECUTE_THRESHOLD). Default: DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE'),
14935
14941
  execute_threshold_tokens: exports_external.object({
14936
14942
  default: exports_external.number().min(5000).max(2000000).optional()
14937
- }).catchall(exports_external.number().min(5000).max(2000000)).optional(),
14938
- protected_tags: exports_external.number().min(1).max(100).optional(),
14939
- auto_drop_tool_age: exports_external.number().min(10).default(100),
14940
- drop_tool_structure: exports_external.boolean().default(true),
14941
- clear_reasoning_age: exports_external.number().min(10).default(50),
14942
- iteration_nudge_threshold: exports_external.number().min(5).default(15),
14943
- history_budget_percentage: exports_external.number().min(0.05).max(0.5).default(DEFAULT_HISTORY_BUDGET_PERCENTAGE),
14944
- historian_timeout_ms: exports_external.number().min(60000).default(DEFAULT_HISTORIAN_TIMEOUT_MS),
14943
+ }).catchall(exports_external.number().min(5000).max(2000000)).optional().describe("Absolute token thresholds per model. When matched, overrides execute_threshold_percentage for that model. Accepts `default` for all models or per-model keys. Values above 80% × context_limit are clamped with a warning log. Min 5_000, max 2_000_000."),
14944
+ protected_tags: exports_external.number().min(1).max(100).optional().describe("Number of recent tags to protect from dropping (min: 1, max: 100, default: 20)"),
14945
+ auto_drop_tool_age: exports_external.number().min(10).default(100).describe("Auto-drop tool outputs older than N tags during queue execution (default: 100)"),
14946
+ drop_tool_structure: exports_external.boolean().default(true).describe("When true, dropped tool parts are fully removed instead of truncated in place (default: true)"),
14947
+ clear_reasoning_age: exports_external.number().min(10).default(50).describe("Clear reasoning/thinking blocks older than N tags (default: 50)"),
14948
+ iteration_nudge_threshold: exports_external.number().min(5).default(15).describe("Number of consecutive assistant messages without user input to trigger iteration nudge (default: 15)"),
14949
+ history_budget_percentage: exports_external.number().min(0.05).max(0.5).default(DEFAULT_HISTORY_BUDGET_PERCENTAGE).describe("Fraction of usable context (context_limit × execute_threshold) reserved for the session history block (default: 0.15)"),
14950
+ historian_timeout_ms: exports_external.number().min(60000).default(DEFAULT_HISTORIAN_TIMEOUT_MS).describe("Timeout for each historian prompt call in milliseconds (default: 300000)"),
14945
14951
  commit_cluster_trigger: exports_external.object({
14946
- enabled: exports_external.boolean().default(true),
14947
- min_clusters: exports_external.number().min(1).default(3)
14948
- }).default({ enabled: true, min_clusters: 3 }),
14952
+ enabled: exports_external.boolean().default(true).describe("Enable commit-cluster based historian triggering (default: true)"),
14953
+ min_clusters: exports_external.number().min(1).default(3).describe("Minimum commit clusters required to trigger historian (min: 1, default: 3)")
14954
+ }).default({ enabled: true, min_clusters: 3 }).describe("Commit-cluster trigger: fire historian when enough commit clusters accumulate in the unsummarized tail"),
14949
14955
  system_prompt_injection: exports_external.object({
14950
- enabled: exports_external.boolean().default(true),
14951
- skip_signatures: exports_external.array(exports_external.string()).default(["<!-- magic-context: skip -->"])
14956
+ enabled: exports_external.boolean().default(true).describe("When false, NO injection happens for ANY agent — global escape hatch. (default: true)"),
14957
+ skip_signatures: exports_external.array(exports_external.string()).default(["<!-- magic-context: skip -->"]).describe(`Substring opt-out list. If the agent's system prompt contains any of these strings, skip ALL Magic Context injection for that call. Default "<!-- magic-context: skip -->" is meant to be added inside a user's custom agent prompt to opt that agent out.`)
14952
14958
  }).default({
14953
14959
  enabled: true,
14954
14960
  skip_signatures: ["<!-- magic-context: skip -->"]
14955
- }),
14961
+ }).describe("Controls whether and where Magic Context augments the system prompt. Lets users opt specific agents out of the Magic Context guidance and the surrounding project-docs / user-profile / key-files blocks. OpenCode's internal hidden agents — title, summary, and compaction — are always skipped automatically."),
14956
14962
  sqlite: exports_external.object({
14957
- cache_size_mb: exports_external.number().min(2).max(2048).default(64),
14958
- mmap_size_mb: exports_external.number().min(0).max(8192).default(0)
14959
- }).default({ cache_size_mb: 64, mmap_size_mb: 0 }),
14963
+ cache_size_mb: exports_external.number().min(2).max(2048).default(64).describe("Page-cache size in MiB per connection (PRAGMA cache_size). Larger keeps more hot pages resident, cutting re-reads on repeated full-table scans. (min 2, max 2048, default 64)"),
14964
+ mmap_size_mb: exports_external.number().min(0).max(8192).default(0).describe("Memory-mapped I/O size in MiB (PRAGMA mmap_size). 0 disables mmap (SQLite default). Raising it can cut read overhead on large DBs at the cost of address space. (min 0, max 8192, default 0)")
14965
+ }).default({ cache_size_mb: 64, mmap_size_mb: 0 }).describe("SQLite connection tuning for Magic Context's own context.db. These are per-connection PRAGMAs applied at open; they do not change the schema or what is stored."),
14960
14966
  embedding: EmbeddingConfigSchema.default({
14961
14967
  provider: "local",
14962
14968
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
14963
- }),
14964
- temporal_awareness: exports_external.boolean().default(true),
14969
+ }).describe("Embedding provider configuration"),
14970
+ temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
14965
14971
  caveman_text_compression: exports_external.object({
14966
- enabled: exports_external.boolean().default(false),
14967
- min_chars: exports_external.number().min(100).max(1e4).default(500)
14968
- }).default({ enabled: false, min_chars: 500 }),
14972
+ enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Only active when ctx_reduce_enabled=false. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
14973
+ min_chars: exports_external.number().min(100).max(1e4).default(500).describe("Text parts shorter than this (characters) stay untouched. Min 100, max 10000. Default: 500.")
14974
+ }).default({ enabled: false, min_chars: 500 }).describe("Age-tier caveman compression for long user/assistant text parts. Only active when ctx_reduce_enabled is false. 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)."),
14969
14975
  memory: exports_external.object({
14970
- enabled: exports_external.boolean().default(true),
14971
- injection_budget_tokens: exports_external.number().min(500).max(20000).default(4000),
14972
- auto_promote: exports_external.boolean().default(true),
14973
- retrieval_count_promotion_threshold: exports_external.number().min(1).default(3),
14976
+ enabled: exports_external.boolean().default(true).describe("Enable cross-session memory (default: true)"),
14977
+ injection_budget_tokens: exports_external.number().min(500).max(20000).default(4000).describe("Token budget for memory injection on session start (min: 500, max: 20000, default: 4000)"),
14978
+ auto_promote: exports_external.boolean().default(true).describe("Automatically promote eligible session facts into memory (default: true)"),
14979
+ retrieval_count_promotion_threshold: exports_external.number().min(1).default(3).describe("retrieval_count threshold for promoting memory to permanent status (min: 1, default: 3)"),
14974
14980
  auto_search: exports_external.object({
14975
- enabled: exports_external.boolean().default(true),
14976
- score_threshold: exports_external.number().min(0.3).max(0.95).default(0.6),
14977
- min_prompt_chars: exports_external.number().min(5).max(500).default(20)
14978
- }).default({ enabled: true, score_threshold: 0.6, min_prompt_chars: 20 }),
14981
+ enabled: exports_external.boolean().default(true).describe("Automatically append a compact <ctx-search-hint> to eligible user messages when relevant memories, conversation, or commits are found. Graduated from experimental.auto_search; on by default (set false to opt out). Independent of memory.enabled."),
14982
+ score_threshold: exports_external.number().min(0.3).max(0.95).default(0.6).describe("Top hit score must exceed this threshold for the hint to fire (min: 0.3, max: 0.95, default: 0.60)"),
14983
+ min_prompt_chars: exports_external.number().min(5).max(500).default(20).describe("Skip hint when user message is shorter than this (min: 5, max: 500, default: 20)")
14984
+ }).default({ enabled: true, score_threshold: 0.6, min_prompt_chars: 20 }).describe("Auto-search hint: transform-time ctx_search on each new user message; when the top hit clears the threshold, append a compact <ctx-search-hint> block of vague fragments to that user message. Does NOT inject full content. Graduated from experimental.auto_search; enabled by default (set enabled: false to opt out). Independent of memory.enabled."),
14979
14985
  git_commit_indexing: exports_external.object({
14980
- enabled: exports_external.boolean().default(false),
14981
- since_days: exports_external.number().min(7).max(3650).default(365),
14982
- max_commits: exports_external.number().min(100).max(20000).default(2000)
14983
- }).default({ enabled: false, since_days: 365, max_commits: 2000 })
14986
+ enabled: exports_external.boolean().default(false).describe("Index HEAD git commits for ctx_search (git_commit source). Graduated from experimental.git_commit_indexing; opt-in, default off. Independent of memory.enabled."),
14987
+ since_days: exports_external.number().min(7).max(3650).default(365).describe("Days of HEAD history to index (min: 7, max: 3650, default: 365)"),
14988
+ max_commits: exports_external.number().min(100).max(20000).default(2000).describe("Max commits kept per project; oldest evicted (min: 100, max: 20000, default: 2000)")
14989
+ }).default({ enabled: false, since_days: 365, max_commits: 2000 }).describe("Index git commit messages from HEAD into ctx_search. Commits become a 4th searchable source alongside memories and session history. Graduated from experimental.git_commit_indexing; opt-in, default off (per-project embedding cost). Independent of memory.enabled.")
14984
14990
  }).default({
14985
14991
  enabled: true,
14986
14992
  injection_budget_tokens: 4000,
@@ -14988,8 +14994,8 @@ var init_magic_context = __esm(() => {
14988
14994
  retrieval_count_promotion_threshold: 3,
14989
14995
  auto_search: { enabled: true, score_threshold: 0.6, min_prompt_chars: 20 },
14990
14996
  git_commit_indexing: { enabled: false, since_days: 365, max_commits: 2000 }
14991
- }),
14992
- sidekick: SidekickConfigSchema
14997
+ }).describe("Cross-session memory configuration"),
14998
+ sidekick: SidekickConfigSchema.describe("Optional sidekick agent configuration for session-start memory retrieval")
14993
14999
  }).transform((data) => {
14994
15000
  return {
14995
15001
  ...data,
@@ -15207,9 +15213,6 @@ function getMagicContextStorageDir() {
15207
15213
  function getLegacyOpenCodeMagicContextStorageDir() {
15208
15214
  return path2.join(getOpenCodeStorageDir(), "plugin", "magic-context");
15209
15215
  }
15210
- function getCacheDir() {
15211
- return process.env.XDG_CACHE_HOME ?? path2.join(os.homedir(), ".cache");
15212
- }
15213
15216
  var init_data_path = () => {};
15214
15217
 
15215
15218
  // src/shared/logger.ts
@@ -150721,6 +150724,17 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
150721
150724
  updated_at INTEGER NOT NULL DEFAULT 0
150722
150725
  );
150723
150726
 
150727
+ CREATE TABLE IF NOT EXISTS git_sweep_coordinator (
150728
+ project_path TEXT PRIMARY KEY,
150729
+ lease_holder TEXT,
150730
+ lease_expires_at INTEGER,
150731
+ last_swept_at INTEGER
150732
+ );
150733
+ CREATE INDEX IF NOT EXISTS idx_git_sweep_coordinator_lease_expires
150734
+ ON git_sweep_coordinator(lease_expires_at);
150735
+ CREATE INDEX IF NOT EXISTS idx_git_sweep_coordinator_last_swept
150736
+ ON git_sweep_coordinator(last_swept_at);
150737
+
150724
150738
  CREATE TABLE IF NOT EXISTS m0_mutation_log (
150725
150739
  id INTEGER PRIMARY KEY AUTOINCREMENT,
150726
150740
  session_id TEXT NOT NULL,
@@ -151250,7 +151264,7 @@ function getDatabasePersistenceError(db) {
151250
151264
  return null;
151251
151265
  return persistenceErrorByDatabase.get(db) ?? null;
151252
151266
  }
151253
- var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 27, sqlitePragmaConfig;
151267
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 29, sqlitePragmaConfig;
151254
151268
  var init_storage_db = __esm(async () => {
151255
151269
  init_data_path();
151256
151270
  init_logger();
@@ -152041,6 +152055,38 @@ var init_migrations = __esm(async () => {
152041
152055
  ON tags(session_id, entry_fingerprint)
152042
152056
  WHERE type='message' AND entry_fingerprint IS NOT NULL`);
152043
152057
  }
152058
+ },
152059
+ {
152060
+ version: 28,
152061
+ description: "Add git commit sweep coordinator lease/cooldown table",
152062
+ up: (db) => {
152063
+ db.exec(`
152064
+ CREATE TABLE IF NOT EXISTS git_sweep_coordinator (
152065
+ project_path TEXT PRIMARY KEY,
152066
+ lease_holder TEXT,
152067
+ lease_expires_at INTEGER,
152068
+ last_swept_at INTEGER
152069
+ );
152070
+ CREATE INDEX IF NOT EXISTS idx_git_sweep_coordinator_lease_expires
152071
+ ON git_sweep_coordinator(lease_expires_at);
152072
+ CREATE INDEX IF NOT EXISTS idx_git_sweep_coordinator_last_swept
152073
+ ON git_sweep_coordinator(last_swept_at);
152074
+ `);
152075
+ }
152076
+ },
152077
+ {
152078
+ version: 29,
152079
+ description: "Add anchor_ordinal to notes (traceback to the conversation tail)",
152080
+ up: (db) => {
152081
+ const notesExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'").get();
152082
+ if (!notesExists) {
152083
+ return;
152084
+ }
152085
+ const columns = db.prepare("PRAGMA table_info(notes)").all();
152086
+ if (!columns.some((column) => column.name === "anchor_ordinal")) {
152087
+ db.exec("ALTER TABLE notes ADD COLUMN anchor_ordinal INTEGER");
152088
+ }
152089
+ }
152044
152090
  }
152045
152091
  ];
152046
152092
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -152998,7 +153044,8 @@ function toNote(row) {
152998
153044
  updatedAt: row.updated_at,
152999
153045
  lastCheckedAt: toNullableNumber(row.last_checked_at),
153000
153046
  readyAt: toNullableNumber(row.ready_at),
153001
- readyReason: toNullableString(row.ready_reason)
153047
+ readyReason: toNullableString(row.ready_reason),
153048
+ anchorOrdinal: toNullableNumber(row.anchor_ordinal)
153002
153049
  };
153003
153050
  }
153004
153051
  function getNoteById(db, noteId) {
@@ -153050,7 +153097,7 @@ function getNotes(db, options = {}) {
153050
153097
  }
153051
153098
  function addNote(db, type, options) {
153052
153099
  const now = Date.now();
153053
- const result = type === "session" ? db.prepare("INSERT INTO notes (type, status, content, session_id, created_at, updated_at, harness) VALUES ('session', 'active', ?, ?, ?, ?, ?) RETURNING *").get(options.content, options.sessionId, now, now, getHarness()) : db.prepare("INSERT INTO notes (type, status, content, session_id, project_path, surface_condition, created_at, updated_at, harness) VALUES ('smart', 'pending', ?, ?, ?, ?, ?, ?, ?) RETURNING *").get(options.content, options.sessionId ?? null, options.projectPath, options.surfaceCondition, now, now, getHarness());
153100
+ const result = type === "session" ? db.prepare("INSERT INTO notes (type, status, content, session_id, created_at, updated_at, harness, anchor_ordinal) VALUES ('session', 'active', ?, ?, ?, ?, ?, ?) RETURNING *").get(options.content, options.sessionId, now, now, getHarness(), options.anchorOrdinal ?? null) : db.prepare("INSERT INTO notes (type, status, content, session_id, project_path, surface_condition, created_at, updated_at, harness, anchor_ordinal) VALUES ('smart', 'pending', ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *").get(options.content, options.sessionId ?? null, options.projectPath, options.surfaceCondition, now, now, getHarness(), options.anchorOrdinal ?? null);
153054
153101
  if (!isNoteRow(result)) {
153055
153102
  throw new Error("[notes] failed to insert note");
153056
153103
  }
@@ -163633,7 +163680,8 @@ function getEmbeddingProviderIdentity(config2) {
163633
163680
  provider: "openai-compatible",
163634
163681
  model: config2.model.trim(),
163635
163682
  endpoint: normalizeEndpoint(config2.endpoint),
163636
- apiKeyPresent: Boolean(config2.api_key?.trim())
163683
+ apiKeyPresent: Boolean(config2.api_key?.trim()),
163684
+ inputType: config2.input_type?.trim() || ""
163637
163685
  } : {
163638
163686
  provider: "local",
163639
163687
  model: config2.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
@@ -164002,6 +164050,8 @@ class OpenAICompatibleEmbeddingProvider {
164002
164050
  endpoint;
164003
164051
  model;
164004
164052
  apiKey;
164053
+ inputType;
164054
+ truncate;
164005
164055
  initialized = false;
164006
164056
  failureTimes = [];
164007
164057
  circuitOpenUntil = 0;
@@ -164011,6 +164061,8 @@ class OpenAICompatibleEmbeddingProvider {
164011
164061
  this.endpoint = normalizeEndpoint2(options.endpoint);
164012
164062
  this.model = options.model?.trim() ?? "";
164013
164063
  this.apiKey = options.apiKey?.trim() ?? "";
164064
+ this.inputType = options.inputType?.trim() ?? "";
164065
+ this.truncate = options.truncate?.trim() ?? "";
164014
164066
  this.modelId = getEmbeddingProviderIdentity({
164015
164067
  provider: "openai-compatible",
164016
164068
  endpoint: this.endpoint,
@@ -164067,7 +164119,9 @@ class OpenAICompatibleEmbeddingProvider {
164067
164119
  },
164068
164120
  body: JSON.stringify({
164069
164121
  model: this.model,
164070
- input: texts
164122
+ input: texts,
164123
+ ...this.inputType ? { input_type: this.inputType } : {},
164124
+ ...this.truncate ? { truncate: this.truncate } : {}
164071
164125
  }),
164072
164126
  signal: internalController.signal
164073
164127
  });
@@ -164306,8 +164360,130 @@ var init_storage_git_commit_embeddings = __esm(() => {
164306
164360
  distinctModelIdStatements = new WeakMap;
164307
164361
  });
164308
164362
 
164363
+ // src/features/magic-context/git-commits/sweep-coordinator.ts
164364
+ function runImmediate2(db, body) {
164365
+ db.exec("BEGIN IMMEDIATE");
164366
+ let committed = false;
164367
+ try {
164368
+ const result = body();
164369
+ db.exec("COMMIT");
164370
+ committed = true;
164371
+ return result;
164372
+ } finally {
164373
+ if (!committed) {
164374
+ try {
164375
+ db.exec("ROLLBACK");
164376
+ } catch {}
164377
+ }
164378
+ }
164379
+ }
164380
+ function rowToState(row) {
164381
+ return {
164382
+ projectPath: row.project_path,
164383
+ leaseHolder: row.lease_holder,
164384
+ leaseExpiresAt: row.lease_expires_at,
164385
+ lastSweptAt: row.last_swept_at
164386
+ };
164387
+ }
164388
+ function getGitSweepCoordinatorState(db, projectPath) {
164389
+ const row = db.prepare(`SELECT project_path, lease_holder, lease_expires_at, last_swept_at
164390
+ FROM git_sweep_coordinator
164391
+ WHERE project_path = ?`).get(projectPath);
164392
+ return row ? rowToState(row) : null;
164393
+ }
164394
+ function acquireGitSweepLease(db, projectPath, holderId, options = {}) {
164395
+ const cooldownMs = options.cooldownMs ?? GIT_SWEEP_COOLDOWN_MS;
164396
+ const leaseTtlMs = options.leaseTtlMs ?? GIT_SWEEP_LEASE_TTL_MS;
164397
+ return runImmediate2(db, () => {
164398
+ const now = Date.now();
164399
+ const row = getGitSweepCoordinatorState(db, projectPath);
164400
+ if (row?.leaseHolder && row.leaseExpiresAt !== null && row.leaseExpiresAt > now) {
164401
+ return {
164402
+ acquired: false,
164403
+ projectPath,
164404
+ reason: "lease_active",
164405
+ leaseHolder: row.leaseHolder,
164406
+ leaseExpiresAt: row.leaseExpiresAt,
164407
+ lastSweptAt: row.lastSweptAt,
164408
+ nextAllowedAt: null
164409
+ };
164410
+ }
164411
+ if (!options.ignoreCooldown && row?.lastSweptAt !== null && row?.lastSweptAt !== undefined) {
164412
+ const nextAllowedAt = row.lastSweptAt + cooldownMs;
164413
+ if (nextAllowedAt > now) {
164414
+ return {
164415
+ acquired: false,
164416
+ projectPath,
164417
+ reason: "cooldown_active",
164418
+ leaseHolder: row.leaseHolder,
164419
+ leaseExpiresAt: row.leaseExpiresAt,
164420
+ lastSweptAt: row.lastSweptAt,
164421
+ nextAllowedAt
164422
+ };
164423
+ }
164424
+ }
164425
+ const leaseExpiresAt = now + leaseTtlMs;
164426
+ db.prepare(`INSERT INTO git_sweep_coordinator (
164427
+ project_path,
164428
+ lease_holder,
164429
+ lease_expires_at,
164430
+ last_swept_at
164431
+ ) VALUES (?, ?, ?, NULL)
164432
+ ON CONFLICT(project_path) DO UPDATE SET
164433
+ lease_holder = excluded.lease_holder,
164434
+ lease_expires_at = excluded.lease_expires_at`).run(projectPath, holderId, leaseExpiresAt);
164435
+ return {
164436
+ acquired: true,
164437
+ projectPath,
164438
+ holderId,
164439
+ acquiredAt: now,
164440
+ leaseExpiresAt
164441
+ };
164442
+ });
164443
+ }
164444
+ function renewGitSweepLease(db, projectPath, holderId, leaseTtlMs = GIT_SWEEP_LEASE_TTL_MS) {
164445
+ return runImmediate2(db, () => {
164446
+ const now = Date.now();
164447
+ const leaseExpiresAt = now + leaseTtlMs;
164448
+ const result = db.prepare(`UPDATE git_sweep_coordinator
164449
+ SET lease_expires_at = ?
164450
+ WHERE project_path = ?
164451
+ AND lease_holder = ?
164452
+ AND lease_expires_at > ?`).run(leaseExpiresAt, projectPath, holderId, now);
164453
+ return result.changes === 1;
164454
+ });
164455
+ }
164456
+ function markGitSweepSuccessAndRelease(db, projectPath, holderId) {
164457
+ return runImmediate2(db, () => {
164458
+ const now = Date.now();
164459
+ const result = db.prepare(`UPDATE git_sweep_coordinator
164460
+ SET lease_holder = NULL,
164461
+ lease_expires_at = NULL,
164462
+ last_swept_at = ?
164463
+ WHERE project_path = ?
164464
+ AND lease_holder = ?
164465
+ AND lease_expires_at > ?`).run(now, projectPath, holderId, now);
164466
+ return result.changes === 1;
164467
+ });
164468
+ }
164469
+ function releaseGitSweepLease(db, projectPath, holderId) {
164470
+ runImmediate2(db, () => {
164471
+ db.prepare(`UPDATE git_sweep_coordinator
164472
+ SET lease_holder = NULL,
164473
+ lease_expires_at = NULL
164474
+ WHERE project_path = ?
164475
+ AND lease_holder = ?`).run(projectPath, holderId);
164476
+ });
164477
+ }
164478
+ var GIT_SWEEP_COOLDOWN_MS, GIT_SWEEP_LEASE_TTL_MS, GIT_SWEEP_LEASE_RENEWAL_MS;
164479
+ var init_sweep_coordinator = __esm(() => {
164480
+ GIT_SWEEP_COOLDOWN_MS = 10 * 60 * 1000;
164481
+ GIT_SWEEP_LEASE_TTL_MS = 5 * 60 * 1000;
164482
+ GIT_SWEEP_LEASE_RENEWAL_MS = 60 * 1000;
164483
+ });
164484
+
164309
164485
  // src/features/magic-context/project-embedding-registry.ts
164310
- import { createHash as createHash7 } from "node:crypto";
164486
+ import { createHash as createHash7, randomUUID } from "node:crypto";
164311
164487
  function resolveEmbeddingConfig(config2) {
164312
164488
  if (!config2 || config2.provider === "local") {
164313
164489
  return {
@@ -164571,6 +164747,7 @@ var init_project_embedding_registry = __esm(() => {
164571
164747
  init_magic_context();
164572
164748
  init_logger();
164573
164749
  init_storage_git_commit_embeddings();
164750
+ init_sweep_coordinator();
164574
164751
  init_embedding_cache();
164575
164752
  init_embedding_identity();
164576
164753
  init_embedding_local();
@@ -164590,7 +164767,9 @@ function createProvider2(config2) {
164590
164767
  return new OpenAICompatibleEmbeddingProvider({
164591
164768
  endpoint: config2.endpoint,
164592
164769
  model: config2.model,
164593
- apiKey: config2.api_key
164770
+ apiKey: config2.api_key,
164771
+ inputType: config2.input_type,
164772
+ truncate: config2.truncate
164594
164773
  });
164595
164774
  }
164596
164775
  return new LocalEmbeddingProvider(config2.model);
@@ -164667,32 +164846,48 @@ var init_storage_memory_fts = __esm(() => {
164667
164846
  });
164668
164847
 
164669
164848
  // src/shared/models-dev-cache.ts
164670
- import { createHash as createHash9 } from "node:crypto";
164671
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "node:fs";
164672
- import { homedir as homedir9, platform as platform3 } from "node:os";
164849
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync12, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "node:fs";
164673
164850
  import { join as join18 } from "node:path";
164674
- function hashFast(input) {
164675
- return createHash9("sha1").update(input).digest("hex");
164676
- }
164677
- function getModelsJsonPath() {
164678
- const explicit = process.env.OPENCODE_MODELS_PATH?.trim();
164679
- if (explicit)
164680
- return explicit;
164681
- const cacheBase = getCacheDir();
164682
- const source = process.env.OPENCODE_MODELS_URL?.trim();
164683
- const filename = source && source !== "https://models.dev" ? `models-${hashFast(source)}.json` : "models.json";
164684
- return join18(cacheBase, "opencode", filename);
164685
- }
164686
- function getOpencodeConfigPath() {
164687
- const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
164688
- const configDir = envDir ? envDir : platform3() === "win32" ? join18(homedir9(), ".config", "opencode") : join18(process.env.XDG_CONFIG_HOME || join18(homedir9(), ".config"), "opencode");
164689
- const jsonc = join18(configDir, "opencode.jsonc");
164690
- if (existsSync14(jsonc))
164691
- return jsonc;
164692
- const json2 = join18(configDir, "opencode.json");
164693
- if (existsSync14(json2))
164694
- return json2;
164695
- return null;
164851
+ function isSaneLimit(limit) {
164852
+ return typeof limit === "number" && limit >= MIN_SANE_LIMIT && limit <= MAX_SANE_LIMIT;
164853
+ }
164854
+ function persistFilePath() {
164855
+ return join18(getMagicContextStorageDir(), `model-context-limits-${getHarness()}.json`);
164856
+ }
164857
+ function loadPersistedApiCacheOnce() {
164858
+ if (persistSeedLoaded || apiCache !== null)
164859
+ return;
164860
+ persistSeedLoaded = true;
164861
+ try {
164862
+ const raw = readFileSync12(persistFilePath(), "utf-8");
164863
+ const obj = JSON.parse(raw);
164864
+ const map2 = new Map;
164865
+ for (const [key, limit] of Object.entries(obj)) {
164866
+ if (isSaneLimit(limit))
164867
+ map2.set(key, { limit });
164868
+ }
164869
+ if (map2.size > 0) {
164870
+ apiCache = map2;
164871
+ sessionLog("global", `models-dev-cache: seeded ${map2.size} entries from persisted cache (cold start)`);
164872
+ }
164873
+ } catch {}
164874
+ }
164875
+ function persistApiCache() {
164876
+ if (!apiCache)
164877
+ return;
164878
+ const obj = {};
164879
+ for (const [key, value] of apiCache) {
164880
+ if (isSaneLimit(value.limit))
164881
+ obj[key] = value.limit;
164882
+ }
164883
+ try {
164884
+ const dir = getMagicContextStorageDir();
164885
+ mkdirSync5(dir, { recursive: true });
164886
+ const target = persistFilePath();
164887
+ const tmp = `${target}.${process.pid}.tmp`;
164888
+ writeFileSync4(tmp, JSON.stringify(obj), { encoding: "utf-8", mode: 384 });
164889
+ renameSync3(tmp, target);
164890
+ } catch {}
164696
164891
  }
164697
164892
  function resolveLimit(limit) {
164698
164893
  if (!limit)
@@ -164705,7 +164900,7 @@ function resolveLimit(limit) {
164705
164900
  }
164706
164901
  function setCachedModelMetadata(cache, key, model) {
164707
164902
  const limit = resolveLimit(model?.limit);
164708
- if (limit === undefined) {
164903
+ if (!isSaneLimit(limit)) {
164709
164904
  return;
164710
164905
  }
164711
164906
  const value = { limit };
@@ -164717,54 +164912,26 @@ function setCachedModelMetadata(cache, key, model) {
164717
164912
  }
164718
164913
  }
164719
164914
  }
164720
- function loadModelsDevMetadataFromFile() {
164721
- const metadata = new Map;
164722
- const modelsJsonPath = getModelsJsonPath();
164723
- let fileFound = false;
164724
- try {
164725
- if (existsSync14(modelsJsonPath)) {
164726
- fileFound = true;
164727
- const raw = readFileSync12(modelsJsonPath, "utf-8");
164728
- const data = JSON.parse(raw);
164729
- for (const [providerId, provider2] of Object.entries(data)) {
164730
- if (!provider2?.models || typeof provider2.models !== "object")
164731
- continue;
164732
- for (const [modelId, model] of Object.entries(provider2.models)) {
164733
- setCachedModelMetadata(metadata, `${providerId}/${modelId}`, model);
164734
- }
164735
- }
164736
- }
164737
- } catch (error51) {
164738
- sessionLog("global", `models-dev-cache: failed to read models.json at ${modelsJsonPath}:`, error51 instanceof Error ? error51.message : String(error51));
164739
- }
164740
- try {
164741
- const configPath = getOpencodeConfigPath();
164742
- if (configPath && existsSync14(configPath)) {
164743
- const config2 = parseJsonc(readFileSync12(configPath, "utf-8"));
164744
- if (config2.provider && typeof config2.provider === "object") {
164745
- for (const [providerId, provider2] of Object.entries(config2.provider)) {
164746
- if (!provider2?.models || typeof provider2.models !== "object")
164747
- continue;
164748
- for (const [modelId, model] of Object.entries(provider2.models)) {
164749
- setCachedModelMetadata(metadata, `${providerId}/${modelId}`, model);
164750
- }
164751
- }
164752
- }
164915
+ async function refreshModelLimitsFromApi(client, options) {
164916
+ const attempts = Math.max(1, (options?.retries ?? 0) + 1);
164917
+ const delayMs = options?.retryDelayMs ?? 1000;
164918
+ for (let attempt = 1;attempt <= attempts; attempt++) {
164919
+ const ok = await refreshModelLimitsOnce(client);
164920
+ if (ok)
164921
+ return;
164922
+ if (attempt < attempts) {
164923
+ await new Promise((resolve6) => setTimeout(resolve6, delayMs));
164753
164924
  }
164754
- } catch (error51) {
164755
- sessionLog("global", "models-dev-cache: failed to read opencode config for custom models:", error51 instanceof Error ? error51.message : String(error51));
164756
164925
  }
164757
- sessionLog("global", `models-dev-cache: file-layer loaded ${metadata.size} model metadata entries (modelsJsonPath=${modelsJsonPath}, found=${fileFound})`);
164758
- return metadata;
164759
164926
  }
164760
- async function refreshModelLimitsFromApi(client) {
164927
+ async function refreshModelLimitsOnce(client) {
164761
164928
  try {
164762
164929
  const result = await client.config.providers();
164763
164930
  const data = result.data;
164764
164931
  const providers = data?.providers;
164765
- if (!Array.isArray(providers)) {
164766
- sessionLog("global", "models-dev-cache: API refresh returned no providers payload");
164767
- return;
164932
+ if (!Array.isArray(providers) || providers.length === 0) {
164933
+ sessionLog("global", "models-dev-cache: API refresh returned no providers payload (will retry if attempts remain)");
164934
+ return false;
164768
164935
  }
164769
164936
  const map2 = new Map;
164770
164937
  for (const entry of providers) {
@@ -164778,35 +164945,42 @@ async function refreshModelLimitsFromApi(client) {
164778
164945
  const previousSize = apiCache?.size ?? null;
164779
164946
  apiCache = map2;
164780
164947
  apiLoadedAt = Date.now();
164948
+ persistApiCache();
164781
164949
  if (previousSize === null) {
164782
164950
  sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model metadata entries`);
164783
164951
  } else if (previousSize !== map2.size) {
164784
164952
  sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model metadata entries (was ${previousSize})`);
164785
164953
  }
164954
+ return true;
164786
164955
  } catch (error51) {
164787
164956
  sessionLog("global", "models-dev-cache: API refresh failed:", error51 instanceof Error ? error51.message : String(error51));
164957
+ return false;
164788
164958
  }
164789
164959
  }
164790
- function getModelsDevContextLimit(providerID, modelID) {
164791
- const key = `${providerID}/${modelID}`;
164792
- if (apiCache) {
164793
- const fromApi = apiCache.get(key)?.limit;
164794
- if (typeof fromApi === "number")
164795
- return fromApi;
164796
- }
164797
- const now = Date.now();
164798
- if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
164799
- fileLastAttempt = now;
164800
- fileCache = loadModelsDevMetadataFromFile();
164960
+ function getSdkContextLimit(providerID, modelID) {
164961
+ loadPersistedApiCacheOnce();
164962
+ const fromApi = lookupLimitWithTagFallback(apiCache, providerID, modelID);
164963
+ return isSaneLimit(fromApi) ? fromApi : undefined;
164964
+ }
164965
+ function lookupLimitWithTagFallback(cache, providerID, modelID) {
164966
+ if (!cache)
164967
+ return;
164968
+ const exact = cache.get(`${providerID}/${modelID}`)?.limit;
164969
+ if (typeof exact === "number")
164970
+ return exact;
164971
+ const colonIdx = modelID.lastIndexOf(":");
164972
+ if (colonIdx > 0) {
164973
+ const baseModel = modelID.slice(0, colonIdx);
164974
+ const fallback = cache.get(`${providerID}/${baseModel}`)?.limit;
164975
+ if (typeof fallback === "number")
164976
+ return fallback;
164801
164977
  }
164802
- return fileCache.get(key)?.limit;
164978
+ return;
164803
164979
  }
164804
- var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, fileCache = null, fileLastAttempt = 0;
164980
+ var MIN_SANE_LIMIT = 20000, MAX_SANE_LIMIT = 3000000, apiCache = null, apiLoadedAt = 0, persistSeedLoaded = false;
164805
164981
  var init_models_dev_cache = __esm(() => {
164806
164982
  init_data_path();
164807
- init_jsonc_parser();
164808
164983
  init_logger();
164809
- RELOAD_INTERVAL_MS = 5 * 60 * 1000;
164810
164984
  });
164811
164985
 
164812
164986
  // src/shared/rpc-notifications.ts
@@ -165532,7 +165706,7 @@ var init_compartment_runner_validation = __esm(async () => {
165532
165706
  });
165533
165707
 
165534
165708
  // src/hooks/magic-context/compartment-runner-historian.ts
165535
- import { mkdirSync as mkdirSync5, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
165709
+ import { mkdirSync as mkdirSync6, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
165536
165710
  import { join as join21 } from "node:path";
165537
165711
  function historianResponseDumpDir(directory) {
165538
165712
  return getProjectMagicContextHistorianDir(directory);
@@ -165835,11 +166009,11 @@ function cleanupHistorianDump(sessionId, dumpPath) {
165835
166009
  function dumpHistorianResponse(sessionId, directory, label, text) {
165836
166010
  try {
165837
166011
  const dumpDir = historianResponseDumpDir(directory);
165838
- mkdirSync5(dumpDir, { recursive: true });
166012
+ mkdirSync6(dumpDir, { recursive: true });
165839
166013
  const safeSessionId = sanitizeDumpName(sessionId);
165840
166014
  const safeLabel = sanitizeDumpName(label);
165841
166015
  const dumpPath = join21(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
165842
- writeFileSync4(dumpPath, text, "utf8");
166016
+ writeFileSync5(dumpPath, text, "utf8");
165843
166017
  sessionLog(sessionId, "compartment agent: historian response dumped", {
165844
166018
  label,
165845
166019
  dumpPath
@@ -165961,7 +166135,7 @@ function insertCompartmentEvents(db, sessionId, events, compartmentIds) {
165961
166135
  var init_compartment_events = () => {};
165962
166136
 
165963
166137
  // src/hooks/magic-context/historian-state-file.ts
165964
- import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
166138
+ import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "node:fs";
165965
166139
  function cleanupHistorianStateFile(path6) {
165966
166140
  if (!path6)
165967
166141
  return;
@@ -170797,7 +170971,7 @@ function resolveHistorianContextLimit(historianModelOverride) {
170797
170971
  const [providerID, ...rest] = historianModelOverride.split("/");
170798
170972
  const modelID = rest.join("/");
170799
170973
  if (providerID && modelID) {
170800
- const limit = getModelsDevContextLimit(providerID, modelID);
170974
+ const limit = getSdkContextLimit(providerID, modelID);
170801
170975
  if (typeof limit === "number" && limit > 0)
170802
170976
  return limit;
170803
170977
  }
@@ -170816,7 +170990,7 @@ function resolveHistorianContextLimit(historianModelOverride) {
170816
170990
  const modelID = rest.join("/");
170817
170991
  if (!providerID || !modelID)
170818
170992
  continue;
170819
- const limit = getModelsDevContextLimit(providerID, modelID);
170993
+ const limit = getSdkContextLimit(providerID, modelID);
170820
170994
  if (typeof limit !== "number" || limit <= 0)
170821
170995
  continue;
170822
170996
  if (minLimit === undefined || limit < minLimit)
@@ -171183,6 +171357,7 @@ __export(exports_recomp_orchestrator, {
171183
171357
  setRecompNote: () => setRecompNote,
171184
171358
  runManagedUpgrade: () => runManagedUpgrade,
171185
171359
  runManagedRecomp: () => runManagedRecomp,
171360
+ isRecompSkip: () => isRecompSkip,
171186
171361
  isRecompFailure: () => isRecompFailure,
171187
171362
  isRecompComplete: () => isRecompComplete,
171188
171363
  extractRecompReason: () => extractRecompReason,
@@ -171195,6 +171370,9 @@ function resolveLiveModelKey(liveSessionState, sessionId) {
171195
171370
  function isRecompFailure(message) {
171196
171371
  return /—\s*(Failed|Skipped)/.test(message);
171197
171372
  }
171373
+ function isRecompSkip(message) {
171374
+ return /—\s*Skipped|already mutating compartment state|already running/i.test(message);
171375
+ }
171198
171376
  function isRecompComplete(message) {
171199
171377
  return /—\s*Complete/.test(message);
171200
171378
  }
@@ -171210,9 +171388,10 @@ function contextualizeUpgradeReason(reason) {
171210
171388
  }
171211
171389
  return rewritten;
171212
171390
  }
171213
- function setRecompStarting(liveSessionState, sessionId, note) {
171391
+ function setRecompStarting(liveSessionState, sessionId, note, kind = "recomp") {
171214
171392
  liveSessionState.recompProgressBySession.set(sessionId, {
171215
171393
  sessionId,
171394
+ kind,
171216
171395
  phase: "recomp",
171217
171396
  processedMessages: 0,
171218
171397
  totalMessages: 0,
@@ -171237,6 +171416,7 @@ function setRecompTerminal(liveSessionState, sessionId, phase, message) {
171237
171416
  const existing = liveSessionState.recompProgressBySession.get(sessionId);
171238
171417
  liveSessionState.recompProgressBySession.set(sessionId, {
171239
171418
  sessionId,
171419
+ kind: existing?.kind ?? "recomp",
171240
171420
  phase,
171241
171421
  processedMessages: existing?.processedMessages ?? 0,
171242
171422
  totalMessages: existing?.totalMessages ?? 0,
@@ -171246,10 +171426,10 @@ function setRecompTerminal(liveSessionState, sessionId, phase, message) {
171246
171426
  updatedAt: Date.now(),
171247
171427
  message
171248
171428
  });
171249
- if (phase === "done") {
171429
+ if (phase === "done" || phase === "skipped") {
171250
171430
  const t = setTimeout(() => {
171251
171431
  const cur = liveSessionState.recompProgressBySession.get(sessionId);
171252
- if (cur?.phase === "done")
171432
+ if (cur?.phase === phase)
171253
171433
  liveSessionState.recompProgressBySession.delete(sessionId);
171254
171434
  }, RECOMP_DONE_GRACE_MS);
171255
171435
  t.unref?.();
@@ -171278,7 +171458,11 @@ function buildRecompDeps(ctx, sessionId) {
171278
171458
  ctx.liveSessionState.deferredHistoryRefreshSessions.add(sid);
171279
171459
  },
171280
171460
  onRecompProgress: (p) => {
171281
- ctx.liveSessionState.recompProgressBySession.set(sessionId, p);
171461
+ const prevKind = ctx.liveSessionState.recompProgressBySession.get(sessionId)?.kind ?? "recomp";
171462
+ ctx.liveSessionState.recompProgressBySession.set(sessionId, {
171463
+ ...p,
171464
+ kind: p.kind ?? prevKind
171465
+ });
171282
171466
  }
171283
171467
  };
171284
171468
  }
@@ -171297,10 +171481,11 @@ async function resolveSessionDirectory(ctx, sessionId) {
171297
171481
  return ctx.directory;
171298
171482
  }
171299
171483
  async function runManagedRecomp(ctx, sessionId, options) {
171300
- setRecompStarting(ctx.liveSessionState, sessionId, "Starting recomp…");
171484
+ setRecompStarting(ctx.liveSessionState, sessionId, "Starting recomp…", "recomp");
171301
171485
  try {
171302
171486
  const message = await executeContextRecomp(buildRecompDeps(ctx, sessionId), options);
171303
- setRecompTerminal(ctx.liveSessionState, sessionId, isRecompFailure(message) ? "failed" : "done", extractRecompReason(message));
171487
+ const terminalPhase = isRecompSkip(message) ? "skipped" : isRecompFailure(message) ? "failed" : "done";
171488
+ setRecompTerminal(ctx.liveSessionState, sessionId, terminalPhase, extractRecompReason(message));
171304
171489
  return message;
171305
171490
  } catch (error51) {
171306
171491
  setRecompTerminal(ctx.liveSessionState, sessionId, "failed", `Recomp crashed: ${String(error51)}`);
@@ -171310,7 +171495,7 @@ Recomp crashed: ${String(error51)}`;
171310
171495
  }
171311
171496
  }
171312
171497
  async function runManagedUpgrade(ctx, sessionId) {
171313
- setRecompStarting(ctx.liveSessionState, sessionId, "Starting upgrade…");
171498
+ setRecompStarting(ctx.liveSessionState, sessionId, "Starting upgrade…", "upgrade");
171314
171499
  try {
171315
171500
  const compartments = getCompartments(ctx.db, sessionId);
171316
171501
  const legacyCount = compartments.filter((c) => c.legacy === 1 || !c.p1 || c.p1.trim() === "").length;
@@ -171368,6 +171553,7 @@ async function runUpgradeMemoryMigration(ctx, sessionId, migrationDirectory) {
171368
171553
  const prev = ctx.liveSessionState.recompProgressBySession.get(sessionId);
171369
171554
  ctx.liveSessionState.recompProgressBySession.set(sessionId, {
171370
171555
  sessionId,
171556
+ kind: prev?.kind ?? "upgrade",
171371
171557
  phase: "migration",
171372
171558
  processedMessages: prev?.processedMessages ?? 0,
171373
171559
  totalMessages: prev?.totalMessages ?? 0,
@@ -171440,7 +171626,12 @@ function markAnnouncementSeen(version2) {
171440
171626
  function shouldShowAnnouncement() {
171441
171627
  if (!ANNOUNCEMENT_VERSION || ANNOUNCEMENT_FEATURES.length === 0)
171442
171628
  return false;
171443
- return readLastAnnouncedVersion() !== ANNOUNCEMENT_VERSION;
171629
+ const lastVersion = readLastAnnouncedVersion();
171630
+ if (!lastVersion) {
171631
+ markAnnouncementSeen(ANNOUNCEMENT_VERSION);
171632
+ return false;
171633
+ }
171634
+ return lastVersion !== ANNOUNCEMENT_VERSION;
171444
171635
  }
171445
171636
  var ANNOUNCEMENT_VERSION = "0.22.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
171446
171637
  var init_announcement = __esm(() => {
@@ -171459,7 +171650,7 @@ var exports_tui_config = {};
171459
171650
  __export(exports_tui_config, {
171460
171651
  ensureTuiPluginEntry: () => ensureTuiPluginEntry
171461
171652
  });
171462
- import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "node:fs";
171653
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "node:fs";
171463
171654
  import { dirname as dirname9, join as join25 } from "node:path";
171464
171655
  function isMagicContextEntry(entry) {
171465
171656
  if (!entry)
@@ -171476,9 +171667,9 @@ function resolveTuiConfigPath() {
171476
171667
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
171477
171668
  const jsoncPath = join25(configDir, "tui.jsonc");
171478
171669
  const jsonPath = join25(configDir, "tui.json");
171479
- if (existsSync16(jsoncPath))
171670
+ if (existsSync15(jsoncPath))
171480
171671
  return jsoncPath;
171481
- if (existsSync16(jsonPath))
171672
+ if (existsSync15(jsonPath))
171482
171673
  return jsonPath;
171483
171674
  return jsonPath;
171484
171675
  }
@@ -171486,7 +171677,7 @@ function ensureTuiPluginEntry() {
171486
171677
  try {
171487
171678
  const configPath = resolveTuiConfigPath();
171488
171679
  let config2 = {};
171489
- if (existsSync16(configPath)) {
171680
+ if (existsSync15(configPath)) {
171490
171681
  const raw = readFileSync16(configPath, "utf-8");
171491
171682
  config2 = import_comment_json4.parse(raw) ?? {};
171492
171683
  }
@@ -171506,8 +171697,8 @@ function ensureTuiPluginEntry() {
171506
171697
  plugins.push(PLUGIN_ENTRY);
171507
171698
  }
171508
171699
  config2.plugin = plugins;
171509
- mkdirSync9(dirname9(configPath), { recursive: true });
171510
- writeFileSync8(configPath, `${import_comment_json4.stringify(config2, null, 2)}
171700
+ mkdirSync10(dirname9(configPath), { recursive: true });
171701
+ writeFileSync9(configPath, `${import_comment_json4.stringify(config2, null, 2)}
171511
171702
  `);
171512
171703
  log(`[magic-context] updated TUI plugin entry in ${configPath}`);
171513
171704
  return true;
@@ -171905,10 +172096,19 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
171905
172096
  const defaults = MagicContextConfigSchema.parse({});
171906
172097
  const warnings = [];
171907
172098
  const errorPaths = new Set;
172099
+ const customMessagesByKey = new Map;
172100
+ const GENERIC_ZOD_PREFIXES = ["Too big", "Too small", "Invalid input", "Invalid", "Expected"];
171908
172101
  for (const issue2 of parsed.error.issues) {
171909
172102
  const topKey = issue2.path[0];
171910
172103
  if (topKey !== undefined) {
171911
- errorPaths.add(String(topKey));
172104
+ const key = String(topKey);
172105
+ errorPaths.add(key);
172106
+ const msg = issue2.message;
172107
+ if (msg && !GENERIC_ZOD_PREFIXES.some((p) => msg.startsWith(p))) {
172108
+ if (!customMessagesByKey.has(key)) {
172109
+ customMessagesByKey.set(key, msg);
172110
+ }
172111
+ }
171912
172112
  }
171913
172113
  }
171914
172114
  const patched = { ...rawConfig };
@@ -171921,7 +172121,8 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
171921
172121
  } else {
171922
172122
  delete patched[key];
171923
172123
  const defaultVal = defaults[key];
171924
- warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.`);
172124
+ const reason = customMessagesByKey.get(key);
172125
+ warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.${reason ? ` ${reason}` : ""}`);
171925
172126
  }
171926
172127
  }
171927
172128
  const retryMigrated = migrateLegacyExperimental(patched, preMigrationWarnings);
@@ -173154,9 +173355,18 @@ function removeInstalledPackage(installDir, packageName) {
173154
173355
  const packageDir = join9(installDir, "node_modules", packageName);
173155
173356
  if (!existsSync9(packageDir))
173156
173357
  return false;
173157
- rmSync(packageDir, { recursive: true, force: true });
173158
- log(`[auto-update-checker] Package removed: ${packageDir}`);
173159
- return true;
173358
+ try {
173359
+ rmSync(packageDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
173360
+ log(`[auto-update-checker] Package removed: ${packageDir}`);
173361
+ return true;
173362
+ } catch (err) {
173363
+ const code = err.code;
173364
+ if (code === "EBUSY" || code === "EPERM" || code === "ENOTEMPTY") {
173365
+ warn2(`[auto-update-checker] Could not remove ${packageDir} (${code}); the file is locked by the running process. Continuing — npm install will overwrite it in place.`);
173366
+ return false;
173367
+ }
173368
+ throw err;
173369
+ }
173160
173370
  }
173161
173371
  function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
173162
173372
  if (runtimePackageJsonPath) {
@@ -175235,6 +175445,7 @@ var insertStatements = new WeakMap;
175235
175445
  var existingShasStatements = new WeakMap;
175236
175446
  var projectCountStatements = new WeakMap;
175237
175447
  var evictStatements = new WeakMap;
175448
+ var evictOverflowStatements = new WeakMap;
175238
175449
  var latestCommitTimeStatements = new WeakMap;
175239
175450
  function getInsertStatement(db) {
175240
175451
  let stmt = insertStatements.get(db);
@@ -175277,17 +175488,17 @@ function getLatestCommitTimeStatement(db) {
175277
175488
  }
175278
175489
  return stmt;
175279
175490
  }
175280
- function getEvictStatement(db) {
175281
- let stmt = evictStatements.get(db);
175491
+ function getEvictOverflowStatement(db) {
175492
+ let stmt = evictOverflowStatements.get(db);
175282
175493
  if (!stmt) {
175283
175494
  stmt = db.prepare(`DELETE FROM git_commits
175284
- WHERE sha IN (
175285
- SELECT sha FROM git_commits
175495
+ WHERE rowid IN (
175496
+ SELECT rowid FROM git_commits
175286
175497
  WHERE project_path = ?
175287
- ORDER BY committed_at ASC
175288
- LIMIT ?
175498
+ ORDER BY committed_at DESC, sha DESC
175499
+ LIMIT -1 OFFSET ?
175289
175500
  )`);
175290
- evictStatements.set(db, stmt);
175501
+ evictOverflowStatements.set(db, stmt);
175291
175502
  }
175292
175503
  return stmt;
175293
175504
  }
@@ -175325,22 +175536,15 @@ function getLatestIndexedCommitTimeMs(db, projectPath) {
175325
175536
  const row = getLatestCommitTimeStatement(db).get(projectPath);
175326
175537
  return row?.latest ?? null;
175327
175538
  }
175328
- function evictOldestCommits(db, projectPath, excess) {
175329
- if (excess <= 0)
175330
- return 0;
175331
- const before = getCommitCount(db, projectPath);
175332
- getEvictStatement(db).run(projectPath, excess);
175333
- const after = getCommitCount(db, projectPath);
175334
- return Math.max(0, before - after);
175335
- }
175336
175539
  function enforceProjectCap(db, projectPath, maxCommits) {
175337
175540
  if (maxCommits <= 0)
175338
175541
  return 0;
175339
175542
  const count = getCommitCount(db, projectPath);
175340
175543
  if (count <= maxCommits)
175341
175544
  return 0;
175342
- const excess = count - maxCommits;
175343
- const evicted = evictOldestCommits(db, projectPath, excess);
175545
+ getEvictOverflowStatement(db).run(projectPath, maxCommits);
175546
+ const after = getCommitCount(db, projectPath);
175547
+ const evicted = Math.max(0, count - after);
175344
175548
  if (evicted > 0) {
175345
175549
  log(`[git-commits] evicted ${evicted} oldest commits for project ${projectPath} (cap=${maxCommits}, was=${count})`);
175346
175550
  }
@@ -175584,6 +175788,7 @@ function searchGitCommitsSync(db, projectPath, query, options) {
175584
175788
 
175585
175789
  // src/features/magic-context/git-commits/index.ts
175586
175790
  init_storage_git_commit_embeddings();
175791
+ init_sweep_coordinator();
175587
175792
 
175588
175793
  // src/plugin/dream-timer.ts
175589
175794
  init_embedding();
@@ -175697,10 +175902,31 @@ async function sweepProject(reg, origin, db, gitCommitEnabled = getProjectEmbedd
175697
175902
  log(`[dreamer] timer-triggered queue processing failed for ${reg.directory}:`, error51);
175698
175903
  }
175699
175904
  }
175905
+ function startGitSweepLeaseRenewal(db, projectIdentity, holderId) {
175906
+ const timer = setInterval(() => {
175907
+ try {
175908
+ if (!renewGitSweepLease(db, projectIdentity, holderId)) {
175909
+ log(`[git-commits] sweep lease renewal failed for ${projectIdentity}`);
175910
+ }
175911
+ } catch (error51) {
175912
+ log(`[git-commits] sweep lease renewal errored for ${projectIdentity}: ${error51 instanceof Error ? error51.message : String(error51)}`);
175913
+ }
175914
+ }, GIT_SWEEP_LEASE_RENEWAL_MS);
175915
+ timer.unref?.();
175916
+ return () => clearInterval(timer);
175917
+ }
175700
175918
  async function sweepGitCommits(args) {
175701
175919
  const { directory, projectIdentity, db, gitCommitIndexing } = args;
175920
+ const holderId = crypto.randomUUID();
175921
+ const lease2 = acquireGitSweepLease(db, projectIdentity, holderId);
175922
+ if (!lease2.acquired) {
175923
+ const reason = lease2.reason === "cooldown_active" ? `cooldown active until ${lease2.nextAllowedAt}` : `lease held by ${lease2.leaseHolder ?? "another holder"} until ${lease2.leaseExpiresAt ?? "unknown"}`;
175924
+ log(`[git-commits] sweep skipped for ${projectIdentity} (${directory}): ${reason}`);
175925
+ return;
175926
+ }
175702
175927
  const startedAt = Date.now();
175703
- log(`[git-commits] sweep starting for ${directory} (sinceDays=${gitCommitIndexing.since_days} maxCommits=${gitCommitIndexing.max_commits})`);
175928
+ const stopRenewal = startGitSweepLeaseRenewal(db, projectIdentity, holderId);
175929
+ log(`[git-commits] sweep starting for ${directory} (project=${projectIdentity} sinceDays=${gitCommitIndexing.since_days} maxCommits=${gitCommitIndexing.max_commits})`);
175704
175930
  try {
175705
175931
  const result = await indexCommitsForProject(db, projectIdentity, directory, {
175706
175932
  sinceDays: gitCommitIndexing.since_days,
@@ -175710,11 +175936,19 @@ async function sweepGitCommits(args) {
175710
175936
  if (result.embedded > 0) {
175711
175937
  drainedEmbeddings = await embedUnembeddedCommits(db, projectIdentity);
175712
175938
  }
175939
+ const cooldownMarked = markGitSweepSuccessAndRelease(db, projectIdentity, holderId);
175940
+ if (!cooldownMarked) {
175941
+ releaseGitSweepLease(db, projectIdentity, holderId);
175942
+ log(`[git-commits] sweep finished for ${projectIdentity}, but lease was no longer active; cooldown not advanced`);
175943
+ }
175713
175944
  const elapsedMs = Date.now() - startedAt;
175714
175945
  log(`[git-commits] sweep finished for ${projectIdentity} in ${elapsedMs}ms: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=${result.embedded} drained=${drainedEmbeddings}`);
175715
175946
  } catch (error51) {
175947
+ releaseGitSweepLease(db, projectIdentity, holderId);
175716
175948
  const elapsedMs = Date.now() - startedAt;
175717
175949
  log(`[git-commits] sweep failed for ${directory} after ${elapsedMs}ms: ${error51 instanceof Error ? error51.message : String(error51)}`);
175950
+ } finally {
175951
+ stopRenewal();
175718
175952
  }
175719
175953
  }
175720
175954
 
@@ -175821,6 +176055,14 @@ function createEventHandler(args) {
175821
176055
  return async (input) => {
175822
176056
  await args.autoUpdateChecker?.(input);
175823
176057
  await args.magicContext?.event?.(input);
176058
+ if (args.onInstanceDisposed && input.event?.type === "server.instance.disposed") {
176059
+ const directory = input.event.properties?.directory;
176060
+ if (typeof directory === "string") {
176061
+ try {
176062
+ await args.onInstanceDisposed(directory);
176063
+ } catch {}
176064
+ }
176065
+ }
175824
176066
  };
175825
176067
  }
175826
176068
 
@@ -175847,7 +176089,7 @@ init_models_dev_cache();
175847
176089
  var DEFAULT_CONTEXT_LIMIT = 128000;
175848
176090
  var MAX_EXECUTE_THRESHOLD = 80;
175849
176091
  function resolveContextLimit(providerID, modelID, ctx) {
175850
- const fromModelsDev = providerID && modelID ? getModelsDevContextLimit(providerID, modelID) : undefined;
176092
+ const fromModelsDev = providerID && modelID ? getSdkContextLimit(providerID, modelID) : undefined;
175851
176093
  const baseline = fromModelsDev ?? DEFAULT_CONTEXT_LIMIT;
175852
176094
  if (ctx?.db && ctx.sessionID) {
175853
176095
  try {
@@ -175860,7 +176102,7 @@ function resolveContextLimit(providerID, modelID, ctx) {
175860
176102
  return baseline;
175861
176103
  }
175862
176104
  function resolveTrustedContextLimit(providerID, modelID, ctx) {
175863
- const fromModelsDev = providerID && modelID ? getModelsDevContextLimit(providerID, modelID) : undefined;
176105
+ const fromModelsDev = providerID && modelID ? getSdkContextLimit(providerID, modelID) : undefined;
175864
176106
  let detected;
175865
176107
  if (ctx?.db && ctx.sessionID) {
175866
176108
  try {
@@ -175966,8 +176208,21 @@ function resolveExecuteThresholdDetail(config2, modelKey, fallback, options) {
175966
176208
  if (!Number.isFinite(resolved) || resolved < 0) {
175967
176209
  resolved = fallback;
175968
176210
  }
176211
+ const cappedPercentage = Math.min(resolved, MAX_EXECUTE_THRESHOLD);
176212
+ if (cappedPercentage < resolved) {
176213
+ const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
176214
+ if (!clampWarnSeen.has(dedupeKey)) {
176215
+ clampWarnSeen.add(dedupeKey);
176216
+ const msg = `execute_threshold clamped ${resolved}% → ${MAX_EXECUTE_THRESHOLD}% for ${modelKey ?? "default"} (capped for cache safety; a large step can overflow before compaction, and 80% stays below the 85%/95% emergency bands)`;
176217
+ if (options?.sessionId) {
176218
+ sessionLog(options.sessionId, `WARN: ${msg}`);
176219
+ } else {
176220
+ log(`[magic-context] WARN: ${msg}`);
176221
+ }
176222
+ }
176223
+ }
175969
176224
  return {
175970
- percentage: Math.min(resolved, MAX_EXECUTE_THRESHOLD),
176225
+ percentage: cappedPercentage,
175971
176226
  mode: "percentage",
175972
176227
  matchedKey
175973
176228
  };
@@ -179272,6 +179527,64 @@ init_embedding();
179272
179527
 
179273
179528
  // src/features/magic-context/search.ts
179274
179529
  init_logger();
179530
+
179531
+ // src/features/magic-context/literal-probes.ts
179532
+ var MAX_PROBES = 5;
179533
+ var MIN_PROBE_LENGTH = 3;
179534
+ var SLASH_COMMAND_RE = /\/[a-z][a-z0-9]*(?:-[a-z0-9]+)+/gi;
179535
+ var KEBAB_SNAKE_RE = /[a-z][a-z0-9]*(?:[-_][a-z0-9]+)+/gi;
179536
+ var DOTTED_RE = /[a-z0-9][a-z0-9_-]*(?:\.[a-z0-9_-]+)+/gi;
179537
+ var CAMEL_RE = /\b[a-zA-Z][a-z0-9]*(?:[A-Z][a-z0-9]*)+\b/g;
179538
+ var SHA_RE = /\b[0-9a-f]{7,40}\b/gi;
179539
+ var ERROR_CODE_RE = /\b(?:TS\d{4,}|ERR_[A-Z][A-Z0-9_]*)\b/g;
179540
+ var QUOTED_RE = /["`]([^"`]{3,80})["`]/g;
179541
+ function looksLikeSha(token) {
179542
+ return /[0-9]/.test(token) && /^[0-9a-f]{7,40}$/i.test(token);
179543
+ }
179544
+ function extractLiteralProbes(query) {
179545
+ const trimmed = query.trim();
179546
+ if (trimmed.length === 0)
179547
+ return [];
179548
+ const ordered = [];
179549
+ const seen = new Set;
179550
+ const add = (raw) => {
179551
+ if (!raw)
179552
+ return;
179553
+ const probe = raw.trim();
179554
+ if (probe.length < MIN_PROBE_LENGTH)
179555
+ return;
179556
+ const key = probe.toLowerCase();
179557
+ if (seen.has(key))
179558
+ return;
179559
+ seen.add(key);
179560
+ ordered.push(probe);
179561
+ };
179562
+ for (const m of trimmed.matchAll(QUOTED_RE))
179563
+ add(m[1]);
179564
+ for (const m of trimmed.matchAll(SLASH_COMMAND_RE))
179565
+ add(m[0]);
179566
+ for (const m of trimmed.matchAll(ERROR_CODE_RE))
179567
+ add(m[0]);
179568
+ for (const m of trimmed.matchAll(DOTTED_RE))
179569
+ add(m[0]);
179570
+ for (const m of trimmed.matchAll(KEBAB_SNAKE_RE))
179571
+ add(m[0]);
179572
+ for (const m of trimmed.matchAll(CAMEL_RE))
179573
+ add(m[0]);
179574
+ for (const m of trimmed.matchAll(SHA_RE)) {
179575
+ if (looksLikeSha(m[0]))
179576
+ add(m[0]);
179577
+ }
179578
+ return ordered.slice(0, MAX_PROBES);
179579
+ }
179580
+ function containsProbeVerbatim(text, probes) {
179581
+ if (probes.length === 0)
179582
+ return false;
179583
+ const haystack = text.toLowerCase();
179584
+ return probes.some((probe) => haystack.includes(probe.toLowerCase()));
179585
+ }
179586
+
179587
+ // src/features/magic-context/search.ts
179275
179588
  init_memory();
179276
179589
  init_embedding();
179277
179590
  init_storage_memory_fts();
@@ -179451,36 +179764,82 @@ function linearDecayScore(rank, total) {
179451
179764
  return 0;
179452
179765
  return Math.max(0, 1 - rank / total);
179453
179766
  }
179454
- function searchMessages(args) {
179455
- const sanitizedQuery = sanitizeFtsQuery(args.query.trim());
179456
- if (sanitizedQuery.length === 0) {
179767
+ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
179768
+ if (ftsQuery.length === 0)
179457
179769
  return [];
179458
- }
179459
- const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
179460
- const rows = getMessageSearchStatement(args.db).all(args.sessionId, sanitizedQuery, fetchLimit).map((row) => row);
179461
- const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
179462
- const filtered = rows.map((row) => {
179770
+ const rows = getMessageSearchStatement(db).all(sessionId, ftsQuery, fetchLimit).map((row) => row);
179771
+ const result = [];
179772
+ for (const row of rows) {
179463
179773
  const messageOrdinal = getMessageOrdinal(row.messageOrdinal);
179464
179774
  if (messageOrdinal === null || typeof row.messageId !== "string" || typeof row.role !== "string" || typeof row.content !== "string") {
179465
- return null;
179775
+ continue;
179466
179776
  }
179467
179777
  if (cutoff !== null && messageOrdinal > cutoff) {
179468
- return null;
179778
+ continue;
179469
179779
  }
179470
- return {
179780
+ result.push({
179471
179781
  messageOrdinal,
179472
179782
  messageId: row.messageId,
179473
179783
  role: row.role,
179474
179784
  content: row.content
179475
- };
179476
- }).filter((result) => result !== null).slice(0, args.limit);
179477
- return filtered.map((row, rank) => ({
179785
+ });
179786
+ }
179787
+ return result;
179788
+ }
179789
+ var RRF_K = 60;
179790
+ var VERBATIM_PROBE_BONUS = 0.5;
179791
+ function searchMessages(args) {
179792
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
179793
+ const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
179794
+ const baseQuery = sanitizeFtsQuery(args.query.trim());
179795
+ const probes = args.probes ?? [];
179796
+ if (probes.length === 0) {
179797
+ const filtered = runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff).slice(0, args.limit);
179798
+ return filtered.map((row, rank) => ({
179799
+ source: "message",
179800
+ content: previewText(row.content),
179801
+ score: linearDecayScore(rank, filtered.length),
179802
+ messageOrdinal: row.messageOrdinal,
179803
+ messageId: row.messageId,
179804
+ role: row.role
179805
+ }));
179806
+ }
179807
+ const queryLists = [];
179808
+ if (baseQuery.length > 0) {
179809
+ queryLists.push(runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff));
179810
+ }
179811
+ for (const probe of probes) {
179812
+ const probeQuery = sanitizeFtsQuery(probe);
179813
+ if (probeQuery.length === 0)
179814
+ continue;
179815
+ queryLists.push(runMessageFtsQuery(args.db, args.sessionId, probeQuery, fetchLimit, cutoff));
179816
+ }
179817
+ const fused = new Map;
179818
+ for (const list of queryLists) {
179819
+ list.forEach((row, rank) => {
179820
+ const rrf = 1 / (RRF_K + rank);
179821
+ const existing = fused.get(row.messageId);
179822
+ if (existing) {
179823
+ existing.score += rrf;
179824
+ } else {
179825
+ fused.set(row.messageId, { row, score: rrf });
179826
+ }
179827
+ });
179828
+ }
179829
+ for (const entry of fused.values()) {
179830
+ if (containsProbeVerbatim(entry.row.content, probes)) {
179831
+ entry.score += VERBATIM_PROBE_BONUS;
179832
+ }
179833
+ }
179834
+ const ranked = [...fused.values()].sort((a, b) => b.score !== a.score ? b.score - a.score : a.row.messageOrdinal - b.row.messageOrdinal).slice(0, args.limit);
179835
+ const maxScore = ranked.length > 0 ? ranked[0].score : 1;
179836
+ return ranked.map((entry) => ({
179478
179837
  source: "message",
179479
- content: previewText(row.content),
179480
- score: linearDecayScore(rank, filtered.length),
179481
- messageOrdinal: row.messageOrdinal,
179482
- messageId: row.messageId,
179483
- role: row.role
179838
+ content: previewText(entry.row.content),
179839
+ score: maxScore > 0 ? entry.score / maxScore : 0,
179840
+ messageOrdinal: entry.row.messageOrdinal,
179841
+ messageId: entry.row.messageId,
179842
+ role: entry.row.role
179484
179843
  }));
179485
179844
  }
179486
179845
  function getSourceBoost(result) {
@@ -179564,12 +179923,14 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
179564
179923
  return null;
179565
179924
  }) : Promise.resolve(null);
179566
179925
  await Promise.resolve();
179926
+ const messageProbes = options.explicitSearch ? extractLiteralProbes(trimmedQuery) : [];
179567
179927
  const messageResults = runMessages ? searchMessages({
179568
179928
  db,
179569
179929
  sessionId,
179570
179930
  query: trimmedQuery,
179571
179931
  limit: tierLimit,
179572
- maxOrdinal: options.maxMessageOrdinal
179932
+ maxOrdinal: options.maxMessageOrdinal,
179933
+ probes: messageProbes
179573
179934
  }) : [];
179574
179935
  const queryEmbedding = await queryEmbeddingPromise;
179575
179936
  const [memoryResults, gitCommitResults] = await Promise.all([
@@ -180152,7 +180513,7 @@ function isVisibleNoteReadPart(part) {
180152
180513
  }
180153
180514
 
180154
180515
  // src/hooks/magic-context/todo-view.ts
180155
- import { createHash as createHash10 } from "node:crypto";
180516
+ import { createHash as createHash9 } from "node:crypto";
180156
180517
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
180157
180518
  var TITLE_DONE_STATUSES = new Set(["completed"]);
180158
180519
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -180197,7 +180558,7 @@ function buildSyntheticTodoPart(stateJson) {
180197
180558
  };
180198
180559
  }
180199
180560
  function computeSyntheticCallId(stateJson) {
180200
- const hash2 = createHash10("sha256").update(stateJson).digest("hex").slice(0, 16);
180561
+ const hash2 = createHash9("sha256").update(stateJson).digest("hex").slice(0, 16);
180201
180562
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
180202
180563
  }
180203
180564
  function parseTodoState(stateJson) {
@@ -182064,7 +182425,7 @@ function createToolExecuteAfterHook(args) {
182064
182425
  init_send_session_notification();
182065
182426
 
182066
182427
  // src/hooks/magic-context/system-prompt-hash.ts
182067
- import { createHash as createHash11 } from "node:crypto";
182428
+ import { createHash as createHash10 } from "node:crypto";
182068
182429
 
182069
182430
  // src/agents/magic-context-prompt.ts
182070
182431
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -182248,7 +182609,7 @@ function createSystemPromptHashHandler(deps) {
182248
182609
  `);
182249
182610
  if (systemContent.length === 0)
182250
182611
  return;
182251
- const currentHash = createHash11("md5").update(systemContent).digest("hex");
182612
+ const currentHash = createHash10("md5").update(systemContent).digest("hex");
182252
182613
  if (!sessionMetaEarly) {
182253
182614
  return;
182254
182615
  }
@@ -183477,6 +183838,7 @@ function buildSidebarSnapshot(db, sessionId, directory, liveSessionState, inject
183477
183838
  if (!p)
183478
183839
  return null;
183479
183840
  return {
183841
+ kind: p.kind ?? "recomp",
183480
183842
  phase: p.phase,
183481
183843
  processedMessages: p.processedMessages,
183482
183844
  totalMessages: p.totalMessages,
@@ -183496,6 +183858,7 @@ function buildSidebarSnapshot(db, sessionId, directory, liveSessionState, inject
183496
183858
  return {
183497
183859
  ...empty,
183498
183860
  recompProgress: {
183861
+ kind: p.kind ?? "recomp",
183499
183862
  phase: p.phase,
183500
183863
  processedMessages: p.processedMessages,
183501
183864
  totalMessages: p.totalMessages,
@@ -184212,16 +184575,30 @@ Example: \`ctx_note(action="write", content="Implement X because Y", surface_con
184212
184575
 
184213
184576
  Historian reads these notes, deduplicates them, and rewrites the remaining useful notes over time.`;
184214
184577
  // src/tools/ctx-note/tools.ts
184215
- await init_storage();
184578
+ await __promiseAll([
184579
+ init_message_index(),
184580
+ init_storage()
184581
+ ]);
184216
184582
  import { tool as tool3 } from "@opencode-ai/plugin";
184583
+ function captureAnchorOrdinal(db, sessionId) {
184584
+ try {
184585
+ const ordinal = getLastIndexedOrdinal(db, sessionId);
184586
+ return ordinal > 0 ? ordinal : null;
184587
+ } catch {
184588
+ return null;
184589
+ }
184590
+ }
184591
+ function anchorSuffix(note) {
184592
+ return note.anchorOrdinal !== null ? ` ↳ @msg ${note.anchorOrdinal}` : "";
184593
+ }
184217
184594
  function formatNoteLine(note) {
184218
184595
  const statusSuffix = note.status === "active" ? "" : ` (${note.status})`;
184219
184596
  if (note.type === "session") {
184220
- return `- **#${note.id}**${statusSuffix}: ${note.content}`;
184597
+ return `- **#${note.id}**${statusSuffix}: ${note.content}${anchorSuffix(note)}`;
184221
184598
  }
184222
184599
  const conditionText = note.status === "ready" ? note.readyReason ?? note.surfaceCondition ?? "Condition satisfied" : note.surfaceCondition ?? "No condition recorded";
184223
184600
  const conditionLabel = note.status === "ready" ? "Condition met" : "Condition";
184224
- return `- **#${note.id}**${statusSuffix}: ${note.content}
184601
+ return `- **#${note.id}**${statusSuffix}: ${note.content}${anchorSuffix(note)}
184225
184602
  ${conditionLabel}: ${conditionText}`;
184226
184603
  }
184227
184604
  var DISMISS_FOOTER = `
@@ -184299,6 +184676,7 @@ function createCtxNoteTool(deps) {
184299
184676
  if (!content) {
184300
184677
  return "Error: 'content' is required when action is 'write'.";
184301
184678
  }
184679
+ const anchorOrdinal = captureAnchorOrdinal(deps.db, sessionId);
184302
184680
  if (args.surface_condition?.trim()) {
184303
184681
  if (!deps.dreamerEnabled) {
184304
184682
  return "Error: Smart notes require dreamer to be enabled. Enable dreamer in magic-context.jsonc to use surface_condition.";
@@ -184310,13 +184688,14 @@ function createCtxNoteTool(deps) {
184310
184688
  content,
184311
184689
  projectPath: projectIdentity,
184312
184690
  sessionId,
184313
- surfaceCondition: args.surface_condition.trim()
184691
+ surfaceCondition: args.surface_condition.trim(),
184692
+ anchorOrdinal
184314
184693
  });
184315
184694
  return `Created smart note #${note2.id}. Dreamer will evaluate the condition during nightly runs:
184316
184695
  - Content: ${content}
184317
184696
  - Condition: ${args.surface_condition.trim()}`;
184318
184697
  }
184319
- const note = addNote(deps.db, "session", { sessionId, content });
184698
+ const note = addNote(deps.db, "session", { sessionId, content, anchorOrdinal });
184320
184699
  return `Saved session note #${note.id}. Historian will rewrite or deduplicate notes as needed.`;
184321
184700
  }
184322
184701
  if (action === "dismiss") {
@@ -184379,9 +184758,13 @@ ${parts.join(`
184379
184758
 
184380
184759
  No session notes or smart notes.`;
184381
184760
  }
184382
- return sections.join(`
184761
+ const body = sections.join(`
184762
+
184763
+ `);
184764
+ const anchorHint = body.includes("↳ @msg ") ? `
184383
184765
 
184384
- `) + DISMISS_FOOTER;
184766
+ ↳ @msg N marks the conversation tail when a note was written. To see what led to it: ctx_expand(start=N-x, end=N) (pick x for how far back to look).` : "";
184767
+ return body + anchorHint + DISMISS_FOOTER;
184385
184768
  }
184386
184769
  });
184387
184770
  }
@@ -184672,7 +185055,8 @@ function createCtxSearchTool(deps) {
184672
185055
  maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined,
184673
185056
  gitCommitsEnabled,
184674
185057
  sources: normalizeSources(args.sources),
184675
- visibleMemoryIds
185058
+ visibleMemoryIds,
185059
+ explicitSearch: true
184676
185060
  });
184677
185061
  return formatSearchResults(query, results);
184678
185062
  }
@@ -184773,22 +185157,22 @@ init_models_dev_cache();
184773
185157
  init_logger();
184774
185158
  import { randomBytes } from "node:crypto";
184775
185159
  import {
184776
- mkdirSync as mkdirSync8,
185160
+ mkdirSync as mkdirSync9,
184777
185161
  readdirSync,
184778
185162
  readFileSync as readFileSync15,
184779
- renameSync as renameSync3,
185163
+ renameSync as renameSync4,
184780
185164
  unlinkSync as unlinkSync3,
184781
- writeFileSync as writeFileSync7
185165
+ writeFileSync as writeFileSync8
184782
185166
  } from "node:fs";
184783
185167
  import { createServer } from "node:http";
184784
185168
  import { dirname as dirname8 } from "node:path";
184785
185169
 
184786
185170
  // src/shared/rpc-utils.ts
184787
- import { createHash as createHash12 } from "node:crypto";
185171
+ import { createHash as createHash11 } from "node:crypto";
184788
185172
  import { join as join24 } from "node:path";
184789
185173
  function projectHash(directory) {
184790
185174
  const normalized = directory.replace(/\/+$/, "");
184791
- return createHash12("sha256").update(normalized).digest("hex").slice(0, 16);
185175
+ return createHash11("sha256").update(normalized).digest("hex").slice(0, 16);
184792
185176
  }
184793
185177
  function rpcPortDir(storageDir, directory) {
184794
185178
  return join24(storageDir, "rpc", projectHash(directory));
@@ -184871,15 +185255,15 @@ class MagicContextRpcServer {
184871
185255
  try {
184872
185256
  this.warnIfOtherLiveInstance();
184873
185257
  const dir = dirname8(this.portFilePath);
184874
- mkdirSync8(dir, { recursive: true, mode: 448 });
185258
+ mkdirSync9(dir, { recursive: true, mode: 448 });
184875
185259
  const tmpPath = `${this.portFilePath}.tmp`;
184876
- writeFileSync7(tmpPath, JSON.stringify({
185260
+ writeFileSync8(tmpPath, JSON.stringify({
184877
185261
  port: this.port,
184878
185262
  pid: process.pid,
184879
185263
  started_at: this.startedAt,
184880
185264
  token: this.token
184881
185265
  }), { encoding: "utf-8", mode: 384 });
184882
- renameSync3(tmpPath, this.portFilePath);
185266
+ renameSync4(tmpPath, this.portFilePath);
184883
185267
  log(`[rpc] server listening on 127.0.0.1:${this.port}`);
184884
185268
  } catch (err) {
184885
185269
  log(`[rpc] failed to write port file: ${err}`);
@@ -185041,6 +185425,8 @@ var plugin = async (ctx) => {
185041
185425
  }
185042
185426
  }
185043
185427
  const storageDir = getMagicContextStorageDir();
185428
+ let rpcServer = null;
185429
+ let stopDreamTimerRegistration;
185044
185430
  if (pluginConfig.enabled) {
185045
185431
  const dreamerRunnable = isDreamerRunnable(pluginConfig);
185046
185432
  const timerRegistration = {
@@ -185066,8 +185452,8 @@ var plugin = async (ctx) => {
185066
185452
  } : undefined,
185067
185453
  ensureRegistered: ensureProjectRegisteredFromOpenCodeDirectory
185068
185454
  };
185069
- await startDreamScheduleTimer(timerRegistration);
185070
- const rpcServer = new MagicContextRpcServer(storageDir, ctx.directory);
185455
+ stopDreamTimerRegistration = await startDreamScheduleTimer(timerRegistration);
185456
+ rpcServer = new MagicContextRpcServer(storageDir, ctx.directory);
185071
185457
  registerRpcHandlers(rpcServer, {
185072
185458
  directory: ctx.directory,
185073
185459
  config: pluginConfig,
@@ -185077,7 +185463,7 @@ var plugin = async (ctx) => {
185077
185463
  rpcServer.start().catch((err) => {
185078
185464
  log(`[magic-context] RPC server failed to start: ${err}`);
185079
185465
  });
185080
- refreshModelLimitsFromApi(ctx.client);
185466
+ refreshModelLimitsFromApi(ctx.client, { retries: 3, retryDelayMs: 1000 });
185081
185467
  }
185082
185468
  {
185083
185469
  const fence = getSchemaFenceRejection();
@@ -185121,6 +185507,7 @@ var plugin = async (ctx) => {
185121
185507
  } catch {}
185122
185508
  }
185123
185509
  let lastChatContext = null;
185510
+ const ownProjectIdentity = resolveProjectIdentity(ctx.directory);
185124
185511
  return {
185125
185512
  tool: tools5,
185126
185513
  event: createEventHandler({
@@ -185129,7 +185516,21 @@ var plugin = async (ctx) => {
185129
185516
  autoUpdate: pluginConfig.auto_update !== false,
185130
185517
  signal: autoUpdateAbort.signal,
185131
185518
  storageDir
185132
- })
185519
+ }),
185520
+ onInstanceDisposed: (disposedDirectory) => {
185521
+ if (resolveProjectIdentity(disposedDirectory) !== ownProjectIdentity)
185522
+ return;
185523
+ try {
185524
+ autoUpdateAbort.abort();
185525
+ } catch {}
185526
+ try {
185527
+ stopDreamTimerRegistration?.();
185528
+ } catch {}
185529
+ try {
185530
+ rpcServer?.stop();
185531
+ } catch {}
185532
+ log("[magic-context] instance disposed — stopped RPC server, dream timer, auto-update");
185533
+ }
185133
185534
  }),
185134
185535
  "experimental.chat.messages.transform": createMessagesTransformHandler({
185135
185536
  magicContext: hooks.magicContext