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