Skip to content

Commit db8acb1

Browse files
committed
fix: use RPC-based TUI detection instead of broken OPENCODE_CLIENT env var
process.env.OPENCODE_CLIENT is undefined in the server plugin process, so all server-side isTui checks were always false. Replace with isTuiConnected() which tracks whether TUI has polled via RPC. - Unify /ctx-status and /ctx-recomp: single server-side registration, RPC action dispatch to TUI dialog, ignored message for Desktop/web - Fix sendIgnoredMessage toast path that was never reaching TUI - Replace SQLite plugin_messages with in-memory RPC notification queue - Add RPC server/client for server<->TUI communication
1 parent 0e4acdf commit db8acb1

13 files changed

Lines changed: 1090 additions & 714 deletions

File tree

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
import type { BuiltinCommandConfig } from "./types";
22

3-
/** Only display-only commands that are fully replaced by TUI-native UI (e.g. dialog).
4-
* Action commands (flush, aug, dream, recomp) must stay server-side because the
5-
* server-side command.execute.before hook does the actual work. */
6-
const TUI_OVERRIDE_COMMANDS = new Set(["ctx-status"]);
7-
83
export function getMagicContextBuiltinCommands(): BuiltinCommandConfig {
9-
const isTui = process.env.OPENCODE_CLIENT === "cli";
10-
11-
const commands: BuiltinCommandConfig = {
4+
return {
125
"ctx-status": {
136
template: "ctx-status",
147
description: "Show magic context status, pending queue, cache TTL, and debug info",
@@ -31,13 +24,4 @@ export function getMagicContextBuiltinCommands(): BuiltinCommandConfig {
3124
description: "Run the hidden dreamer maintenance pass for this project now",
3225
},
3326
};
34-
35-
// In TUI mode, skip commands that have richer TUI-native implementations
36-
if (isTui) {
37-
for (const name of TUI_OVERRIDE_COMMANDS) {
38-
delete commands[name];
39-
}
40-
}
41-
42-
return commands;
4327
}

packages/plugin/src/hooks/magic-context/command-handler.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { runSidekick } from "../../features/magic-context/sidekick/agent";
99
import { getCompartments } from "../../features/magic-context/storage";
1010
import type { PluginContext } from "../../plugin/types";
1111
import { sessionLog } from "../../shared";
12+
import { isTuiConnected, pushNotification } from "../../shared/rpc-notifications";
1213
import { executeFlush } from "./execute-flush";
1314
import { executeStatus } from "./execute-status";
1415
import type { NotificationParams } from "./send-session-notification";
@@ -274,6 +275,12 @@ export function createMagicContextCommandHandler(deps: {
274275
}
275276

276277
if (isStatus) {
278+
if (isTuiConnected()) {
279+
// In TUI, push an RPC action so the TUI poller shows a native dialog
280+
pushNotification("action", { action: "show-status-dialog" }, sessionId);
281+
sessionLog(sessionId, "command ctx-status: pushed show-status-dialog to TUI");
282+
throwSentinel(input.command);
283+
}
277284
const liveModelKey = deps.getLiveModelKey?.(sessionId);
278285
const statusOutput = executeStatus(
279286
deps.db,
@@ -289,12 +296,17 @@ export function createMagicContextCommandHandler(deps: {
289296
}
290297

291298
if (isRecomp) {
299+
if (isTuiConnected()) {
300+
// In TUI, push an RPC action so the TUI poller shows a confirmation dialog
301+
pushNotification("action", { action: "show-recomp-dialog" }, sessionId);
302+
sessionLog(sessionId, "command ctx-recomp: pushed show-recomp-dialog to TUI");
303+
throwSentinel(input.command);
304+
}
292305
if (!deps.executeRecomp) {
293306
result =
294307
"## Magic Recomp\n\n/ctx-recomp is unavailable because the recomp handler is not configured.";
295308
} else {
296-
// Desktop double-tap confirmation.
297-
// TUI uses a native dialog → message bus → tui-action-consumer.ts
309+
// Desktop double-tap confirmation (no native dialog available).
298310
const lastConfirmation = recompConfirmationBySession.get(sessionId);
299311
const now = Date.now();
300312

packages/plugin/src/hooks/magic-context/send-session-notification.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ export async function sendIgnoredMessage(
6464
text: string,
6565
params: NotificationParams,
6666
): Promise<void> {
67-
// In TUI mode, show as toast instead of ignored message
68-
const isTui = process.env.OPENCODE_CLIENT === "cli";
69-
if (isTui) {
67+
// In TUI mode, show as toast via RPC instead of ignored message.
68+
// Cannot use process.env.OPENCODE_CLIENT — it's undefined in the server plugin process.
69+
const { isTuiConnected: checkTui } = await import("../../shared/rpc-notifications");
70+
if (checkTui()) {
7071
try {
7172
const c = client as Record<string, unknown>;
7273
const tui = c?.tui as Record<string, unknown> | undefined;

packages/plugin/src/index.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import { createEventHandler } from "./plugin/event";
1616
import { createSessionHooks } from "./plugin/hooks/create-session-hooks";
1717
import { createMessagesTransformHandler } from "./plugin/messages-transform";
1818
import { createToolRegistry } from "./plugin/tool-registry";
19-
import { startTuiActionConsumer } from "./plugin/tui-action-consumer";
19+
import { registerRpcHandlers } from "./plugin/rpc-handlers";
2020
import { type ConflictResult, detectConflicts } from "./shared/conflict-detector";
21+
import { getOpenCodeStorageDir } from "./shared/data-path";
22+
import { MagicContextRpcServer } from "./shared/rpc-server";
2123
import { log } from "./shared/logger";
2224
import { getAgentFallbackModels } from "./shared/model-requirements";
2325

@@ -71,11 +73,16 @@ const plugin: Plugin = async (ctx) => {
7173
: undefined,
7274
});
7375

74-
// Start TUI→server action message consumer (handles recomp confirmations etc.)
75-
startTuiActionConsumer({
76-
client: ctx.client,
76+
// Start RPC server for TUI↔server communication (replaces SQLite plugin_messages bus)
77+
const storageDir = `${getOpenCodeStorageDir()}/plugin/magic-context`;
78+
const rpcServer = new MagicContextRpcServer(storageDir, ctx.directory);
79+
registerRpcHandlers(rpcServer, {
7780
directory: ctx.directory,
7881
config: pluginConfig,
82+
client: ctx.client,
83+
});
84+
rpcServer.start().catch((err) => {
85+
log(`[magic-context] RPC server failed to start: ${err}`);
7986
});
8087
}
8188

0 commit comments

Comments
 (0)