fix(rpc): route command update frames

This commit is contained in:
danzaio
2026-06-10 14:43:58 -03:00
parent 1f2fcff15f
commit 600117ef65
3 changed files with 41 additions and 1 deletions
@@ -13,6 +13,7 @@ import type { FileSink } from "bun";
import type { BashResult } from "../../exec/bash-executor";
import type { AgentSessionEvent, SessionStats } from "../../session/agent-session";
import type {
RpcAvailableCommandsUpdateFrame,
RpcAvailableSlashCommand,
RpcCommand,
RpcExtensionUIRequest,
@@ -64,6 +65,7 @@ export type RpcSessionEventListener = (event: AgentSessionEvent) => void;
export type RpcSubagentLifecycleListener = (payload: RpcSubagentLifecycleFrame["payload"]) => void;
export type RpcSubagentProgressListener = (payload: RpcSubagentProgressFrame["payload"]) => void;
export type RpcSubagentEventListener = (payload: RpcSubagentEventFrame["payload"]) => void;
export type RpcAvailableCommandsUpdateListener = (commands: RpcAvailableSlashCommand[]) => void;
export interface RpcClientToolContext<TDetails = unknown> {
toolCallId: string;
@@ -162,6 +164,11 @@ function isRpcSubagentEventFrame(value: unknown): value is RpcSubagentEventFrame
return value.type === "subagent_event" && isRecord(value.payload);
}
function isRpcAvailableCommandsUpdateFrame(value: unknown): value is RpcAvailableCommandsUpdateFrame {
if (!isRecord(value)) return false;
return value.type === "available_commands_update" && Array.isArray(value.commands);
}
function isRpcHostToolCallRequest(value: unknown): value is RpcHostToolCallRequest {
if (!isRecord(value)) return false;
return (
@@ -203,6 +210,7 @@ export class RpcClient {
#subagentLifecycleListeners = new Set<RpcSubagentLifecycleListener>();
#subagentProgressListeners = new Set<RpcSubagentProgressListener>();
#subagentEventListeners = new Set<RpcSubagentEventListener>();
#availableCommandsUpdateListeners = new Set<RpcAvailableCommandsUpdateListener>();
#pendingRequests: Map<string, { resolve: (response: RpcResponse) => void; reject: (error: Error) => void }> =
new Map();
#customTools: RpcClientCustomTool[] = [];
@@ -378,6 +386,14 @@ export class RpcClient {
return () => this.#subagentEventListeners.delete(listener);
}
/**
* Subscribe to slash-command availability updates emitted by the RPC server.
*/
onAvailableCommandsUpdate(listener: RpcAvailableCommandsUpdateListener): () => void {
this.#availableCommandsUpdateListeners.add(listener);
return () => this.#availableCommandsUpdateListeners.delete(listener);
}
/**
* Get collected stderr output (useful for debugging).
*/
@@ -834,6 +850,13 @@ export class RpcClient {
return;
}
if (isRpcAvailableCommandsUpdateFrame(data)) {
for (const listener of this.#availableCommandsUpdateListeners) {
listener(data.commands);
}
return;
}
if (!isAgentSessionEvent(data)) return;
for (const listener of this.#sessionEventListeners) {
@@ -12,6 +12,8 @@
*/
import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
import { $env, readJsonl, Snowflake } from "@oh-my-pi/pi-utils";
import { reset as resetCapabilities } from "../../capability";
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
import {
type ExtensionUIContext,
type ExtensionUIDialogOptions,
@@ -19,6 +21,7 @@ import {
type ExtensionWidgetOptions,
getExtensionUISelectOptionLabel,
} from "../../extensibility/extensions";
import { loadSlashCommands } from "../../extensibility/slash-commands";
import { type Theme, theme } from "../../modes/theme/theme";
import type { AgentSession } from "../../session/agent-session";
import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
@@ -514,6 +517,15 @@ export async function runRpcMode(
});
const getAvailableCommands = async () => buildAvailableSlashCommands(session);
const reloadPluginState = async () => {
const cwd = session.sessionManager.getCwd();
const projectPath = await resolveActiveProjectRegistryPath(cwd);
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
resetCapabilities();
session.setSlashCommands(await loadSlashCommands({ cwd }));
await session.refreshSshTool({ activateIfAvailable: true });
await emitAvailableCommandsUpdate();
};
const emitAvailableCommandsUpdate = async () => {
output({ type: "available_commands_update", commands: await getAvailableCommands() });
};
@@ -536,7 +548,7 @@ export async function runRpcMode(
cwd: session.sessionManager.getCwd(),
output: text => output({ type: "command_output", text }),
refreshCommands: emitAvailableCommandsUpdate,
reloadPlugins: async () => {},
reloadPlugins: reloadPluginState,
notifyTitleChanged: async () => {},
notifyConfigChanged: async () => {},
});
@@ -121,6 +121,11 @@ export interface RpcAvailableSlashCommand {
source: AvailableSlashCommandSource;
}
export interface RpcAvailableCommandsUpdateFrame {
type: "available_commands_update";
commands: RpcAvailableSlashCommand[];
}
export interface RpcHandoffResult {
savedPath?: string;
}