(api, _options, meta)
| 725 | } |
| 726 | |
| 727 | const tui: TuiPlugin = async (api, _options, meta) => { |
| 728 | // Initialize RPC client for server communication |
| 729 | const directory = api.state.path.directory ?? "" |
| 730 | initRpcClient(directory) |
| 731 | await refreshToastDurationMs() |
| 732 | |
| 733 | // Register sidebar slot |
| 734 | api.slots.register(createSidebarContentSlot(api)) |
| 735 | |
| 736 | // Register TUI command palette entries (no slash field — slash commands |
| 737 | // are registered server-side so there's only one /ctx-* registration). |
| 738 | // The server detects TUI mode and sends dialog requests via RPC instead |
| 739 | // of sendIgnoredMessage. |
| 740 | // |
| 741 | // OpenCode 1.14.42 removed `api.command.register` entirely |
| 742 | // (anomalyco/opencode#26053). A later patch (1.14.44+) reinstated it as |
| 743 | // a deprecated shim that translates to `api.keymap.registerLayer`. To |
| 744 | // work across all hosts (1.14.0–1.14.41 with command-only, the broken |
| 745 | // 1.14.42–1.14.43, and 1.14.44+ where both exist), we prefer |
| 746 | // `api.keymap.registerLayer` and fall back to `api.command.register` |
| 747 | // only when keymap is missing. |
| 748 | registerCommandPaletteEntries(api) |
| 749 | |
| 750 | // Receive server→TUI notifications (toasts + dialog requests) over a single |
| 751 | // persistent WebSocket, pushed the instant the server queues them. This |
| 752 | // replaces the old 500ms HTTP poll whose new-connection-per-tick cost was the |
| 753 | // source of idle TUI CPU (#200). The socket carries the active session in its |
| 754 | // hello so the server scopes delivery; here we re-check the active session per |
| 755 | // notification (it can change between queue and delivery) before acting. |
| 756 | const handleNotification = async (n: SocketNotification): Promise<boolean> => { |
| 757 | const requestedSessionId = getSessionId(api) |
| 758 | const generation = getRpcGeneration() |
| 759 | // A session-scoped notification only applies while we're viewing that |
| 760 | // session; global (session-less) ones always apply. Returning false leaves |
| 761 | // it unacked so a TUI on the right session (or a later switch back) still |
| 762 | // gets it. |
| 763 | if (n.sessionId && requestedSessionId && n.sessionId !== requestedSessionId) { |
| 764 | return false |
| 765 | } |
| 766 | if (n.type === "toast") { |
| 767 | const p = n.payload |
| 768 | showToast(api, { |
| 769 | message: String(p.message ?? ""), |
| 770 | variant: (p.variant as "info" | "warning" | "error" | "success") ?? "info", |
| 771 | durationOverrideMs: |
| 772 | typeof p.duration === "number" && Number.isFinite(p.duration) |
| 773 | ? p.duration |
| 774 | : undefined, |
| 775 | }) |
| 776 | return true |
| 777 | } |
| 778 | if (n.type !== "action") return false |
| 779 | const action = n.payload?.action |
| 780 | const stillActive = () => |
| 781 | getRpcGeneration() === generation && getSessionId(api) === requestedSessionId |
| 782 | if (action === "show-status-dialog") { |
| 783 | return stillActive() && (await showStatusDialog(api, requestedSessionId)) |
| 784 | } |
nothing calls this directly
no test coverage detected