@cortexkit/opencode-magic-context 0.8.3 → 0.8.4

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 (33) hide show
  1. package/README.md +17 -7
  2. package/dist/cli.js +2 -2
  3. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  4. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  5. package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +62246 -61171
  8. package/dist/plugin/rpc-handlers.d.ts +11 -0
  9. package/dist/plugin/rpc-handlers.d.ts.map +1 -0
  10. package/dist/shared/conflict-detector.d.ts +0 -4
  11. package/dist/shared/conflict-detector.d.ts.map +1 -1
  12. package/dist/shared/rpc-client.d.ts +16 -0
  13. package/dist/shared/rpc-client.d.ts.map +1 -0
  14. package/dist/shared/rpc-notifications.d.ts +21 -0
  15. package/dist/shared/rpc-notifications.d.ts.map +1 -0
  16. package/dist/shared/rpc-server.d.ts +17 -0
  17. package/dist/shared/rpc-server.d.ts.map +1 -0
  18. package/dist/shared/rpc-types.d.ts +59 -0
  19. package/dist/shared/rpc-types.d.ts.map +1 -0
  20. package/dist/shared/rpc-utils.d.ts +8 -0
  21. package/dist/shared/rpc-utils.d.ts.map +1 -0
  22. package/dist/tui/data/context-db.d.ts +17 -69
  23. package/dist/tui/data/context-db.d.ts.map +1 -1
  24. package/package.json +1 -1
  25. package/src/shared/conflict-detector.ts +1 -17
  26. package/src/shared/rpc-client.ts +123 -0
  27. package/src/shared/rpc-notifications.ts +44 -0
  28. package/src/shared/rpc-server.ts +136 -0
  29. package/src/shared/rpc-types.ts +58 -0
  30. package/src/shared/rpc-utils.ts +16 -0
  31. package/src/tui/data/context-db.ts +99 -625
  32. package/src/tui/index.tsx +53 -55
  33. package/src/tui/slots/sidebar-content.tsx +8 -7
package/src/tui/index.tsx CHANGED
@@ -6,7 +6,7 @@ import { createMemo } from "solid-js"
6
6
  import type { TuiPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui"
7
7
  import { createSidebarContentSlot } from "./slots/sidebar-content"
8
8
  import packageJson from "../../package.json"
9
- import { closeDb, consumeTuiMessages, getCompartmentCount, loadStatusDetail, sendMessageToServer, type StatusDetail } from "./data/context-db"
9
+ import { closeRpc, consumeTuiMessages, getCompartmentCount, initRpcClient, loadStatusDetail, requestRecomp, type StatusDetail } from "./data/context-db"
10
10
  import { detectConflicts } from "../shared/conflict-detector"
11
11
  import { fixConflicts } from "../shared/conflict-fixer"
12
12
  import { readJsoncFile } from "../shared/jsonc-parser"
@@ -103,7 +103,7 @@ function showConflictDialog(api: TuiPluginApi, directory: string, reasons: strin
103
103
  }, 50)
104
104
  }}
105
105
  onCancel={() => {
106
- api.ui.toast({ message: "Magic Context remains disabled. Run: bunx @cortexkit/opencode-magic-context doctor", variant: "warning", duration: 5000 })
106
+ api.ui.toast({ message: "Magic Context remains disabled. Run: bunx @cortexkit/opencode-magic-context@latest doctor", variant: "warning", duration: 5000 })
107
107
  }}
108
108
  />
109
109
  ))
@@ -152,7 +152,7 @@ function showTuiSetupDialog(api: TuiPluginApi) {
152
152
  }, 50)
153
153
  }}
154
154
  onCancel={() => {
155
- api.ui.toast({ message: "You can add the sidebar later via: bunx @cortexkit/opencode-magic-context doctor", variant: "info", duration: 5000 })
155
+ api.ui.toast({ message: "You can add the sidebar later via: bunx @cortexkit/opencode-magic-context@latest doctor", variant: "info", duration: 5000 })
156
156
  }}
157
157
  />
158
158
  ))
@@ -407,27 +407,28 @@ function showRecompDialog(api: TuiPluginApi) {
407
407
  return
408
408
  }
409
409
 
410
- const count = getCompartmentCount(sessionId)
411
- api.ui.dialog.replace(() => (
412
- <api.ui.DialogConfirm
413
- title="⚠️ Recomp Confirmation"
414
- message={[
415
- `You have ${count} compartments.`,
416
- "",
417
- "Recomp will regenerate all compartments and facts from raw history.",
418
- "This may take a long time and consume significant tokens.",
419
- "",
420
- "Proceed?",
421
- ].join("\n")}
422
- onConfirm={() => {
423
- sendMessageToServer("action", { command: "recomp" }, sessionId)
424
- api.ui.toast({ message: "Recomp requested — historian will start shortly", variant: "info", duration: 5000 })
425
- }}
426
- onCancel={() => {
427
- api.ui.toast({ message: "Recomp cancelled", variant: "info", duration: 3000 })
428
- }}
429
- />
430
- ))
410
+ void getCompartmentCount(sessionId).then((count) => {
411
+ api.ui.dialog.replace(() => (
412
+ <api.ui.DialogConfirm
413
+ title="⚠️ Recomp Confirmation"
414
+ message={[
415
+ `You have ${count} compartments.`,
416
+ "",
417
+ "Recomp will regenerate all compartments and facts from raw history.",
418
+ "This may take a long time and consume significant tokens.",
419
+ "",
420
+ "Proceed?",
421
+ ].join("\n")}
422
+ onConfirm={() => {
423
+ void requestRecomp(sessionId)
424
+ api.ui.toast({ message: "Recomp requested — historian will start shortly", variant: "info", duration: 5000 })
425
+ }}
426
+ onCancel={() => {
427
+ api.ui.toast({ message: "Recomp cancelled", variant: "info", duration: 3000 })
428
+ }}
429
+ />
430
+ ))
431
+ })
431
432
  }
432
433
 
433
434
  function showStatusDialog(api: TuiPluginApi) {
@@ -439,22 +440,28 @@ function showStatusDialog(api: TuiPluginApi) {
439
440
 
440
441
  const directory = api.state.path.directory ?? ""
441
442
  const modelKey = getModelKeyFromMessages(api, sessionId)
442
- const detail = loadStatusDetail(sessionId, directory, modelKey)
443
-
444
- api.ui.dialog.replace(() => <StatusDialog api={api} s={detail} />)
443
+ void loadStatusDetail(sessionId, directory, modelKey).then((detail) => {
444
+ api.ui.dialog.replace(() => <StatusDialog api={api} s={detail} />)
445
+ })
445
446
  }
446
447
 
447
448
  const tui: TuiPlugin = async (api, _options, meta) => {
449
+ // Initialize RPC client for server communication
450
+ const directory = api.state.path.directory ?? ""
451
+ initRpcClient(directory)
452
+
448
453
  // Register sidebar slot
449
454
  api.slots.register(createSidebarContentSlot(api))
450
455
 
451
- // Register TUI command palette entries for commands with richer TUI-native UI.
456
+ // Register TUI command palette entries (no slash field slash commands
457
+ // are registered server-side so there's only one /ctx-* registration).
458
+ // The server detects TUI mode and sends dialog requests via RPC instead
459
+ // of sendIgnoredMessage.
452
460
  api.command.register(() => [
453
461
  {
454
462
  title: "Magic Context: Status",
455
463
  value: "magic-context.status",
456
464
  category: "Magic Context",
457
- slash: { name: "ctx-status" },
458
465
  onSelect() {
459
466
  showStatusDialog(api)
460
467
  },
@@ -463,17 +470,16 @@ const tui: TuiPlugin = async (api, _options, meta) => {
463
470
  title: "Magic Context: Recomp",
464
471
  value: "magic-context.recomp",
465
472
  category: "Magic Context",
466
- slash: { name: "ctx-recomp" },
467
473
  onSelect() {
468
474
  showRecompDialog(api)
469
475
  },
470
476
  },
471
477
  ])
472
478
 
473
- // Poll for server→TUI messages (toasts, dialogs) every 2 seconds
479
+ // Poll for server→TUI messages: toasts and dialog requests.
480
+ // Single poller because consumeTuiMessages() is destructive (deletes consumed rows).
474
481
  const messagePoller = setInterval(() => {
475
- try {
476
- const messages = consumeTuiMessages()
482
+ void consumeTuiMessages().then((messages) => {
477
483
  for (const msg of messages) {
478
484
  if (msg.type === "toast") {
479
485
  const p = msg.payload
@@ -482,43 +488,35 @@ const tui: TuiPlugin = async (api, _options, meta) => {
482
488
  variant: (p.variant as "info" | "warning" | "error" | "success") ?? "info",
483
489
  duration: typeof p.duration === "number" ? p.duration : 5000,
484
490
  })
485
- } else if (msg.type === "dialog_confirm") {
486
- const p = msg.payload
487
- const dialogId = String(p.id ?? "")
488
- api.ui.dialog.replace(() => (
489
- <api.ui.DialogConfirm
490
- title={String(p.title ?? "Confirm")}
491
- message={String(p.message ?? "")}
492
- onConfirm={() => {
493
- sendMessageToServer("dialog_result", { id: dialogId, confirmed: true }, msg.sessionId ?? undefined)
494
- }}
495
- onCancel={() => {
496
- sendMessageToServer("dialog_result", { id: dialogId, confirmed: false }, msg.sessionId ?? undefined)
497
- }}
498
- />
499
- ))
491
+ } else if (msg.type === "action") {
492
+ const action = msg.payload?.action
493
+ if (action === "show-status-dialog") {
494
+ showStatusDialog(api)
495
+ } else if (action === "show-recomp-dialog") {
496
+ showRecompDialog(api)
497
+ }
500
498
  }
501
499
  }
502
- } catch {
500
+ }).catch(() => {
503
501
  // Intentional: message polling should never crash the TUI
504
- }
505
- }, 2000)
502
+ })
503
+ }, 500)
506
504
 
507
505
  // Clean up on dispose
508
506
  api.lifecycle.onDispose(() => {
509
507
  clearInterval(messagePoller)
510
- closeDb()
508
+ closeRpc()
511
509
  })
512
510
 
513
- const directory = api.state.path.directory ?? ""
514
511
  const conflictResult = detectConflicts(directory)
515
512
  if (conflictResult.hasConflict) {
516
513
  showConflictDialog(api, directory, conflictResult.reasons, conflictResult.conflicts)
517
514
  return
518
515
  }
519
516
 
520
- // Note: tui.json detection moved to server plugin (src/index.ts) since
521
- // if tui.json doesn't have our plugin, this TUI code never loads at all.
517
+ // Note: if TUI plugin is loaded, tui.json already has our entry.
518
+ // But if the user added it manually and later removes it, or if they
519
+ // use setup/doctor which handles tui.json, this code is already running.
522
520
  }
523
521
 
524
522
  const id = "opencode-magic-context"
@@ -231,13 +231,14 @@ const SidebarContent = (props: {
231
231
  const sid = props.sessionID()
232
232
  if (!sid) return
233
233
  const directory = props.api.state.path.directory ?? ""
234
- const data = loadSidebarSnapshot(sid, directory)
235
- setSnapshot(data)
236
- try {
237
- props.api.renderer.requestRender()
238
- } catch {
239
- // Ignore render errors
240
- }
234
+ void loadSidebarSnapshot(sid, directory).then((data) => {
235
+ setSnapshot(data)
236
+ try {
237
+ props.api.renderer.requestRender()
238
+ } catch {
239
+ // Ignore render errors
240
+ }
241
+ })
241
242
  }
242
243
 
243
244
  const scheduleRefresh = () => {