@wolfx/opencode-magic-context 0.23.1 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/config/schema/magic-context.d.ts +10 -3
  2. package/dist/config/schema/magic-context.d.ts.map +1 -1
  3. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  4. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +80 -0
  5. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -0
  6. package/dist/features/magic-context/compartment-embedding.d.ts +22 -26
  7. package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -1
  8. package/dist/features/magic-context/memory/embedding-backfill.d.ts +3 -2
  9. package/dist/features/magic-context/memory/embedding-backfill.d.ts.map +1 -1
  10. package/dist/features/magic-context/memory/embedding-cache.d.ts +3 -2
  11. package/dist/features/magic-context/memory/embedding-cache.d.ts.map +1 -1
  12. package/dist/features/magic-context/memory/embedding-local.d.ts +2 -1
  13. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/embedding-openai.d.ts +17 -0
  15. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  16. package/dist/features/magic-context/memory/embedding-provider.d.ts +2 -0
  17. package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
  18. package/dist/features/magic-context/memory/embedding.d.ts +1 -1
  19. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  20. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +5 -1
  21. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  22. package/dist/features/magic-context/memory/storage-memory-fts.d.ts +1 -7
  23. package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
  24. package/dist/features/magic-context/memory/storage-memory.d.ts +18 -12
  25. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  26. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  27. package/dist/features/magic-context/project-embedding-registry.d.ts +53 -0
  28. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  29. package/dist/features/magic-context/search.d.ts +14 -1
  30. package/dist/features/magic-context/search.d.ts.map +1 -1
  31. package/dist/features/magic-context/session-project-storage.d.ts +17 -0
  32. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -0
  33. package/dist/features/magic-context/storage-db.d.ts +1 -1
  34. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  35. package/dist/features/magic-context/storage-memory-mutation-log.d.ts +2 -0
  36. package/dist/features/magic-context/storage-memory-mutation-log.d.ts.map +1 -1
  37. package/dist/features/magic-context/storage-meta-persisted.d.ts +16 -0
  38. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  39. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage-meta-shared.d.ts +3 -1
  41. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  42. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  43. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  44. package/dist/features/magic-context/storage-tags.d.ts +10 -1
  45. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  46. package/dist/features/magic-context/storage.d.ts +3 -2
  47. package/dist/features/magic-context/storage.d.ts.map +1 -1
  48. package/dist/features/magic-context/types.d.ts +1 -0
  49. package/dist/features/magic-context/types.d.ts.map +1 -1
  50. package/dist/features/magic-context/workspaces.d.ts +20 -0
  51. package/dist/features/magic-context/workspaces.d.ts.map +1 -0
  52. package/dist/hooks/magic-context/apply-operations.d.ts +23 -0
  53. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/command-handler.d.ts +5 -0
  56. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/compartment-runner-types.d.ts +11 -1
  61. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +7 -2
  64. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/inject-compartments.d.ts +23 -3
  68. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +1 -1
  70. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  71. package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
  72. package/dist/hooks/magic-context/sentinel.d.ts +33 -28
  73. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/strip-content.d.ts +34 -17
  75. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  76. package/dist/hooks/magic-context/strip-structural-noise.d.ts +5 -7
  77. package/dist/hooks/magic-context/strip-structural-noise.d.ts.map +1 -1
  78. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +4 -5
  79. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  80. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  81. package/dist/index.js +2411 -365
  82. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  83. package/dist/shared/announcement.d.ts +1 -1
  84. package/dist/shared/rpc-types.d.ts +1 -1
  85. package/dist/shared/rpc-types.d.ts.map +1 -1
  86. package/dist/shared/tui-preferences.d.ts +32 -0
  87. package/dist/shared/tui-preferences.d.ts.map +1 -0
  88. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  89. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  90. package/package.json +1 -1
  91. package/src/shared/announcement.ts +6 -6
  92. package/src/shared/rpc-types.ts +1 -1
  93. package/src/shared/tui-preferences.test.ts +210 -0
  94. package/src/shared/tui-preferences.ts +303 -0
  95. package/src/tui/index.tsx +5 -3
  96. package/src/tui/slots/sidebar-content.tsx +119 -14
package/src/tui/index.tsx CHANGED
@@ -328,7 +328,7 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
328
328
  const p = s().recompProgress!
329
329
  // Label follows the flow that started the run, so a plain
330
330
  // /ctx-recomp never reads as an "Upgrade" (dogfood 2026-06-04).
331
- const verb = p.kind === "upgrade" ? "Upgrade" : "Recomp"
331
+ const verb = p.kind === "upgrade" ? "Upgrade" : p.kind === "embed" ? "Embed" : "Recomp"
332
332
  return (
333
333
  <box marginTop={1} width="100%" flexDirection="column">
334
334
  <text fg={t().text}><b>{verb}</b></text>
@@ -340,12 +340,14 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
340
340
  const bar = p.totalMessages > 0
341
341
  ? `[${"█".repeat(filled)}${"░".repeat(width - filled)}]`
342
342
  : "(starting…)"
343
- const activeLabel = p.kind === "upgrade" ? "upgrading" : "comparting"
343
+ const activeLabel = p.kind === "upgrade" ? "upgrading" : p.kind === "embed" ? "embedding" : "comparting"
344
344
  return (
345
345
  <>
346
346
  <R t={t()} l={activeLabel} v={p.totalMessages > 0 ? `${bar} ${Math.round(frac * 100)}%` : bar} fg={t().warning} />
347
347
  {p.note ? <R t={t()} l="Status" v={p.note} fg={t().textMuted} /> : null}
348
- <R t={t()} l="Compartments" v={`${p.compartmentsCreated} (${p.passCount} pass${p.passCount === 1 ? "" : "es"})`} fg={t().textMuted} />
348
+ {p.kind === "embed"
349
+ ? <R t={t()} l="Compartments" v={`${p.processedMessages}/${p.totalMessages} embedded`} fg={t().textMuted} />
350
+ : <R t={t()} l="Compartments" v={`${p.compartmentsCreated} (${p.passCount} pass${p.passCount === 1 ? "" : "es"})`} fg={t().textMuted} />}
349
351
  </>
350
352
  )
351
353
  }
@@ -4,6 +4,15 @@ import type { TuiSlotPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/
4
4
  import packageJson from "../../../package.json"
5
5
  import { loadSidebarSnapshot, type SidebarSnapshot } from "../data/context-db"
6
6
  import { formatThresholdPercent } from "../../shared/format-threshold"
7
+ import {
8
+ type MagicContextTuiPrefs,
9
+ PLUGIN_KEY,
10
+ queueTuiPreferenceUpdate,
11
+ readTuiPreferencesFile,
12
+ readTuiPreferencesFileSync,
13
+ resolveMagicContextPrefs,
14
+ watchTuiPreferences,
15
+ } from "../../shared/tui-preferences"
7
16
 
8
17
  // Module-level hook so the upgrade/recomp dialog can kick the sidebar into its
9
18
  // fast recomp self-poll the INSTANT the user confirms — without waiting for a
@@ -17,6 +26,64 @@ export function kickRecompProgressRefresh(): void {
17
26
  const SINGLE_BORDER = { type: "single" } as any
18
27
  const REFRESH_DEBOUNCE_MS = 150
19
28
 
29
+ export interface SidebarController {
30
+ prefs: () => MagicContextTuiPrefs
31
+ collapsed: () => boolean
32
+ toggleCollapsed: () => void
33
+ }
34
+
35
+ // The TUI may unmount and remount sidebar_content when the user switches views
36
+ // (main -> subagent -> main). A remount re-runs the component body, so a signal
37
+ // created inside the component would reset to its seed. The controller lives in
38
+ // the slot-factory closure (plugin/process lifetime) and owns the durable
39
+ // prefs/collapse signals plus the single shared file watcher, so collapse state
40
+ // and live pref reloads survive remounts. No Solid effects/memos here — those
41
+ // need an owner; the poll-interval effect stays inside the component.
42
+ function createSidebarController(initialPrefs: MagicContextTuiPrefs): SidebarController {
43
+ const [prefs, setPrefs] = createSignal<MagicContextTuiPrefs>(initialPrefs)
44
+ const seedCollapsed =
45
+ initialPrefs.rememberCollapsed && initialPrefs.collapsed != null
46
+ ? initialPrefs.collapsed
47
+ : initialPrefs.startCollapsed
48
+ const [collapsed, setCollapsed] = createSignal(seedCollapsed)
49
+ let lastPersistedCollapsed: boolean | null = initialPrefs.collapsed
50
+ let lastApplied = JSON.stringify(initialPrefs)
51
+
52
+ // Watcher lives for the process lifetime — intentionally never disposed.
53
+ // Collapse echo guard: lastPersistedCollapsed advances only once our own
54
+ // write lands, so a watcher echo of the value we just wrote is rejected by
55
+ // the `!==` check and cannot revert a user click.
56
+ watchTuiPreferences(() => {
57
+ void (async () => {
58
+ const next = resolveMagicContextPrefs(await readTuiPreferencesFile())
59
+ const serialized = JSON.stringify(next)
60
+ if (serialized === lastApplied) return
61
+ lastApplied = serialized
62
+ setPrefs(next)
63
+ if (
64
+ next.rememberCollapsed &&
65
+ next.collapsed != null &&
66
+ next.collapsed !== lastPersistedCollapsed
67
+ ) {
68
+ lastPersistedCollapsed = next.collapsed
69
+ setCollapsed(next.collapsed)
70
+ }
71
+ })()
72
+ })
73
+
74
+ function toggleCollapsed() {
75
+ const next = !collapsed()
76
+ setCollapsed(next)
77
+ if (prefs().rememberCollapsed) {
78
+ void queueTuiPreferenceUpdate(PLUGIN_KEY, ["collapsed"], next).then(() => {
79
+ lastPersistedCollapsed = next
80
+ })
81
+ }
82
+ }
83
+
84
+ return { prefs, collapsed, toggleCollapsed }
85
+ }
86
+
20
87
  function compactTokens(value: number): string {
21
88
  if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`
22
89
  if (value >= 1_000) return `${(value / 1_000).toFixed(0)}K`
@@ -299,14 +366,25 @@ const RecompProgressSection = (props: {
299
366
  : 0
300
367
  const pct = () => Math.round(fraction() * 100)
301
368
 
302
- // "Recomp" vs "Upgrade" wording follows the flow that started this run, so a
303
- // plain /ctx-recomp never renders as an "Upgrade" (dogfood 2026-06-04).
304
- const verb = () => (props.progress.kind === "upgrade" ? "Upgrade" : "Recomp")
369
+ // "Recomp" vs "Upgrade" vs "Embed" wording follows the flow that started this
370
+ // run, so a plain /ctx-recomp never renders as an "Upgrade" (dogfood 2026-06-04).
371
+ const verb = () =>
372
+ props.progress.kind === "upgrade"
373
+ ? "Upgrade"
374
+ : props.progress.kind === "embed"
375
+ ? "Embed"
376
+ : "Recomp"
377
+ const activeText = () =>
378
+ props.progress.kind === "upgrade"
379
+ ? "upgrading ⟳"
380
+ : props.progress.kind === "embed"
381
+ ? "embedding ⟳"
382
+ : "comparting ⟳"
305
383
  const label = createMemo(() => {
306
384
  switch (props.progress.phase) {
307
385
  case "recomp":
308
386
  return {
309
- text: props.progress.kind === "upgrade" ? "upgrading ⟳" : "comparting ⟳",
387
+ text: activeText(),
310
388
  color: props.theme.warning,
311
389
  }
312
390
  case "migration":
@@ -342,7 +420,7 @@ const RecompProgressSection = (props: {
342
420
  {(phase() === "recomp" || phase() === "migration") && props.progress.note && (
343
421
  <text fg={props.theme.textMuted}>{props.progress.note}</text>
344
422
  )}
345
- {phase() === "recomp" && (
423
+ {phase() === "recomp" && props.progress.kind !== "embed" && (
346
424
  <StatRow
347
425
  theme={props.theme}
348
426
  label="Compartments"
@@ -350,6 +428,14 @@ const RecompProgressSection = (props: {
350
428
  dim
351
429
  />
352
430
  )}
431
+ {phase() === "recomp" && props.progress.kind === "embed" && (
432
+ <StatRow
433
+ theme={props.theme}
434
+ label="Compartments"
435
+ value={`${props.progress.processedMessages}/${props.progress.totalMessages} embedded`}
436
+ dim
437
+ />
438
+ )}
353
439
  {/* Terminal reason (failed/skipped) — kept visible so the user sees
354
440
  WHY (a failure, or the transient "retry shortly" skip cause). */}
355
441
  {(phase() === "failed" || phase() === "skipped") && props.progress.message && (
@@ -363,12 +449,15 @@ const SidebarContent = (props: {
363
449
  api: TuiPluginApi
364
450
  sessionID: () => string
365
451
  theme: TuiThemeCurrent
452
+ controller: SidebarController
366
453
  }) => {
367
454
  const [snapshot, setSnapshot] = createSignal<SidebarSnapshot | null>(null)
368
- // Collapsed view: progress bar + 3 summary lines (Historian / Memories /
369
- // Status), no per-category legend or section grid. In-memory only (resets
370
- // to expanded on TUI restart), mirroring the native MCP sidebar toggle.
371
- const [collapsed, setCollapsed] = createSignal(false)
455
+ // Collapse state + section visibility prefs live in the controller (plugin
456
+ // closure), so they survive view-switch remounts and persist across restarts
457
+ // via ~/.config/opencode/tui-preferences.jsonc. Read reactively.
458
+ const collapsed = props.controller.collapsed
459
+ const sections = () => props.controller.prefs().sections
460
+ const headerLabel = () => props.controller.prefs().header.label
372
461
  let refreshTimer: ReturnType<typeof setTimeout> | undefined
373
462
  // Self-sustaining poll while a recomp/upgrade is running. Recomp work
374
463
  // happens in CHILD sessions whose message events are filtered out of the
@@ -591,11 +680,11 @@ const SidebarContent = (props: {
591
680
  flexDirection="row"
592
681
  justifyContent="space-between"
593
682
  alignItems="center"
594
- onMouseDown={() => setCollapsed((x) => !x)}
683
+ onMouseDown={() => props.controller.toggleCollapsed()}
595
684
  >
596
685
  <box paddingLeft={1} paddingRight={1} backgroundColor={props.theme.accent}>
597
686
  <text fg={props.theme.background}>
598
- <b>{collapsed() ? "▶ " : "▼ "}Magic Context</b>
687
+ <b>{collapsed() ? "▶ " : "▼ "}{headerLabel()}</b>
599
688
  </text>
600
689
  </box>
601
690
  <text fg={props.theme.textMuted}>v{packageJson.version}</text>
@@ -669,6 +758,8 @@ const SidebarContent = (props: {
669
758
  {!collapsed() && (
670
759
  <>
671
760
  {/* Historian section */}
761
+ {sections().historian && (
762
+ <>
672
763
  <box width="100%" marginTop={1} flexDirection="row" justifyContent="space-between">
673
764
  <text fg={props.theme.text}>
674
765
  <b>Historian</b>
@@ -694,8 +785,12 @@ const SidebarContent = (props: {
694
785
  {s()?.recompProgress && (
695
786
  <RecompProgressSection theme={props.theme} progress={s()!.recompProgress!} />
696
787
  )}
788
+ </>
789
+ )}
697
790
 
698
791
  {/* Memory section */}
792
+ {sections().memory && (
793
+ <>
699
794
  <SectionHeader theme={props.theme} title="Memory" />
700
795
  <StatRow
701
796
  theme={props.theme}
@@ -711,9 +806,12 @@ const SidebarContent = (props: {
711
806
  dim
712
807
  />
713
808
  )}
809
+ </>
810
+ )}
714
811
 
715
812
  {/* Queue & Status */}
716
- {((s()?.pendingOpsCount ?? 0) > 0 ||
813
+ {sections().status &&
814
+ ((s()?.pendingOpsCount ?? 0) > 0 ||
717
815
  (s()?.sessionNoteCount ?? 0) > 0 ||
718
816
  (s()?.readySmartNoteCount ?? 0) > 0) && (
719
817
  <>
@@ -745,7 +843,7 @@ const SidebarContent = (props: {
745
843
  )}
746
844
 
747
845
  {/* Dreamer */}
748
- {s()?.lastDreamerRunAt && (
846
+ {sections().dreamer && s()?.lastDreamerRunAt && (
749
847
  <>
750
848
  <SectionHeader theme={props.theme} title="Dreamer" />
751
849
  <StatRow
@@ -763,7 +861,7 @@ const SidebarContent = (props: {
763
861
  snapshot fields (newWorkTokens, totalInputTokens) and the
764
862
  session_meta columns are still populated; only the UI is
765
863
  simplified for now. */}
766
- {s()?.totalInputTokens != null && (
864
+ {sections().stats && s()?.totalInputTokens != null && (
767
865
  <>
768
866
  <SectionHeader theme={props.theme} title="Stats" />
769
867
  <StatRow
@@ -781,6 +879,12 @@ const SidebarContent = (props: {
781
879
  }
782
880
 
783
881
  export function createSidebarContentSlot(api: TuiPluginApi): TuiSlotPlugin {
882
+ // Seed synchronously at slot construction so the sidebar renders at its
883
+ // final collapse state + order on the first paint (no async flicker). The
884
+ // controller lives here in the factory closure for the plugin lifetime, so
885
+ // collapse state and live pref reloads survive sidebar_content remounts.
886
+ const seedRoot = readTuiPreferencesFileSync()
887
+ const controller = createSidebarController(resolveMagicContextPrefs(seedRoot))
784
888
  return {
785
889
  order: 153,
786
890
  slots: {
@@ -791,6 +895,7 @@ export function createSidebarContentSlot(api: TuiPluginApi): TuiSlotPlugin {
791
895
  api={api}
792
896
  sessionID={() => value.session_id}
793
897
  theme={theme()}
898
+ controller={controller}
794
899
  />
795
900
  )
796
901
  },