mirror of
https://github.com/cline/cline.git
synced 2026-10-24 02:02:27 +08:00
fix(core): treat hubs without plugin command RPCs as having no commands (#15188)
* fix(core): own plugin commands in the runtime and hub * fix(core): treat hubs without plugin command RPCs as having no commands A running Hub that predates plugins.commands.* replies unsupported_command, and replacement defers while it has active work. The client threw on that reply, so every non-built-in slash command, skills included, failed in Desktop until the old Hub exited. Return an empty catalog and no command result instead. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix(core): catch the unsupported_command error the hub client throws NodeHubClient.command() throws HubCommandError for a non-ok reply, so checking the reply never saw an older Hub's unsupported_command. Catch the thrown error instead, and make the regression test reject the way the real client does. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> --------- Co-authored-by: abeatrix <beatrix@cline.bot> Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
This commit is contained in:
co-authored by
Saoud Rizwan
abeatrix
parent
ec6a906101
commit
ee1c2b7724
@@ -1,9 +1,33 @@
|
||||
import type { HubCommandEnvelope, HubReplyEnvelope } from "@cline/shared";
|
||||
import type {
|
||||
PluginCommandCatalog,
|
||||
PluginCommandResult,
|
||||
PluginCommandsApi,
|
||||
} from "../../services/plugin-command-api";
|
||||
import type { NodeHubClient } from "./index";
|
||||
import { HubCommandError, type NodeHubClient } from "./index";
|
||||
|
||||
/**
|
||||
* A Hub that predates plugin command RPCs, kept alive while it has active
|
||||
* work, answers unsupported_command. Resolves undefined for that Hub.
|
||||
*/
|
||||
async function commandIfSupported(
|
||||
client: Pick<NodeHubClient, "command">,
|
||||
command: HubCommandEnvelope["command"],
|
||||
payload: Record<string, unknown>,
|
||||
sessionId: string | undefined,
|
||||
options: Parameters<NodeHubClient["command"]>[3],
|
||||
): Promise<HubReplyEnvelope | undefined> {
|
||||
try {
|
||||
return await client.command(command, payload, sessionId, options);
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof HubCommandError &&
|
||||
error.code === "unsupported_command"
|
||||
)
|
||||
return undefined;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses the caller's existing connection and subscription lifecycle. */
|
||||
export function createHubPluginCommandsApi(
|
||||
@@ -11,12 +35,14 @@ export function createHubPluginCommandsApi(
|
||||
): PluginCommandsApi {
|
||||
return {
|
||||
async list(target) {
|
||||
const reply = await client.command(
|
||||
const reply = await commandIfSupported(
|
||||
client,
|
||||
"plugins.commands.list",
|
||||
{ ...target },
|
||||
target.sessionId,
|
||||
{ timeoutMs: null },
|
||||
);
|
||||
if (!reply) return { ...target, status: "ready", commands: [] };
|
||||
if (!reply.ok)
|
||||
throw new Error(
|
||||
reply.error?.message ?? "Plugin command discovery failed",
|
||||
@@ -24,12 +50,14 @@ export function createHubPluginCommandsApi(
|
||||
return reply.payload?.catalog as unknown as PluginCommandCatalog;
|
||||
},
|
||||
async run(input) {
|
||||
const reply = await client.command(
|
||||
const reply = await commandIfSupported(
|
||||
client,
|
||||
"plugins.commands.run",
|
||||
{ ...input },
|
||||
input.sessionId,
|
||||
{ timeoutMs: null, retryAfterDispatch: false },
|
||||
);
|
||||
if (!reply) return undefined;
|
||||
if (!reply.ok)
|
||||
throw new Error(
|
||||
reply.error?.message ?? "Plugin command execution failed",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { LocalRuntimeHost } from "../../runtime/host/local-runtime-host";
|
||||
import { splitCoreSessionConfig } from "../../runtime/host/runtime-host";
|
||||
import { FileSessionService } from "../../session/services/file-session-service";
|
||||
import { HubCommandError } from "../client";
|
||||
import { createHubPluginCommandsApi } from "../client/plugin-commands";
|
||||
import { createLocalHubScheduleRuntimeHandlers } from "../daemon/runtime-handlers";
|
||||
import { HubServerTransport } from "./hub-server-transport";
|
||||
@@ -265,6 +266,26 @@ api.registerCommand({name:'recovered',handler:()=> 'ready'});
|
||||
});
|
||||
stop();
|
||||
});
|
||||
it("treats a Hub without plugin command RPCs as having no plugin commands", async () => {
|
||||
const api = createHubPluginCommandsApi({
|
||||
command: async (name) => {
|
||||
throw new HubCommandError(
|
||||
name,
|
||||
"unsupported_command",
|
||||
`Unsupported hub schedule command: ${name}`,
|
||||
);
|
||||
},
|
||||
subscribe: () => () => {},
|
||||
});
|
||||
expect(await api.list({ workspacePath: root })).toEqual({
|
||||
workspacePath: root,
|
||||
status: "ready",
|
||||
commands: [],
|
||||
});
|
||||
expect(
|
||||
await api.run({ workspacePath: root, prompt: "/counter" }),
|
||||
).toBeUndefined();
|
||||
});
|
||||
it("shares a workspace sandbox across client requests and preserves arguments", async () => {
|
||||
const first = await command("plugins.commands.list", {
|
||||
workspacePath: root,
|
||||
|
||||
Reference in New Issue
Block a user