Skip to content

Commit f69f81a

Browse files
committed
fix(cli): use gateway skills status when available
1 parent cdf4268 commit f69f81a

2 files changed

Lines changed: 97 additions & 2 deletions

File tree

src/cli/skills-cli.commands.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const mocks = vi.hoisted(() => {
7272
return skillStatusReportFixture;
7373
});
7474
return {
75+
callGatewayMock: vi.fn(),
7576
loadConfigMock: vi.fn(() => ({})),
7677
resolveDefaultAgentIdMock: vi.fn((_configForTest: unknown) => "main"),
7778
resolveAgentIdByWorkspacePathMock: vi.fn(
@@ -102,6 +103,7 @@ const mocks = vi.hoisted(() => {
102103
});
103104

104105
const {
106+
callGatewayMock,
105107
loadConfigMock,
106108
resolveDefaultAgentIdMock,
107109
resolveAgentIdByWorkspacePathMock,
@@ -169,6 +171,10 @@ vi.mock("../runtime.js", () => ({
169171
defaultRuntime: mocks.defaultRuntime,
170172
}));
171173

174+
vi.mock("../gateway/call.js", () => ({
175+
callGateway: (...args: unknown[]) => mocks.callGatewayMock(...args),
176+
}));
177+
172178
vi.mock("../utils.js", async (importOriginal) => ({
173179
...(await importOriginal<typeof import("../utils.js")>()),
174180
CONFIG_DIR: "/tmp/openclaw-config",
@@ -250,6 +256,7 @@ describe("skills cli commands", () => {
250256
runtimeLogs.length = 0;
251257
runtimeStdout.length = 0;
252258
runtimeErrors.length = 0;
259+
callGatewayMock.mockReset();
253260
loadConfigMock.mockReset();
254261
resolveDefaultAgentIdMock.mockReset();
255262
resolveAgentIdByWorkspacePathMock.mockReset();
@@ -268,6 +275,7 @@ describe("skills cli commands", () => {
268275
fetchClawHubSkillCardMock.mockReset();
269276
buildWorkspaceSkillStatusMock.mockReset();
270277

278+
callGatewayMock.mockRejectedValue(new Error("gateway unavailable"));
271279
loadConfigMock.mockReturnValue({});
272280
resolveDefaultAgentIdMock.mockReturnValue("main");
273281
resolveAgentIdByWorkspacePathMock.mockReturnValue(undefined);
@@ -1195,6 +1203,60 @@ describe("skills cli commands", () => {
11951203
expectStatusWorkspaceCall("/tmp/workspace-writer");
11961204
});
11971205

1206+
it("uses gateway skills.status for read-only status commands when reachable", async () => {
1207+
routeWorkspaceByAgent();
1208+
const gatewayReport = {
1209+
...skillStatusReportFixture,
1210+
agentId: "writer",
1211+
workspaceDir: "/gateway/workspace-writer",
1212+
skills: [
1213+
{
1214+
...skillStatusReportFixture.skills[0],
1215+
name: "apple-notes",
1216+
description: "Notes helpers",
1217+
eligible: true,
1218+
modelVisible: true,
1219+
commandVisible: true,
1220+
requirements: {
1221+
bins: ["memo"],
1222+
anyBins: [],
1223+
env: [],
1224+
config: [],
1225+
os: ["darwin"],
1226+
},
1227+
missing: {
1228+
bins: [],
1229+
anyBins: [],
1230+
env: [],
1231+
config: [],
1232+
os: [],
1233+
},
1234+
},
1235+
],
1236+
};
1237+
callGatewayMock.mockResolvedValue(gatewayReport);
1238+
1239+
await runCommand(["skills", "check", "--agent", "writer", "--json"]);
1240+
1241+
expect(callGatewayMock).toHaveBeenCalledWith({
1242+
config: {},
1243+
method: "skills.status",
1244+
params: { agentId: "writer" },
1245+
timeoutMs: 1_500,
1246+
clientName: "cli",
1247+
mode: "cli",
1248+
});
1249+
expect(buildWorkspaceSkillStatusMock).not.toHaveBeenCalled();
1250+
const output = JSON.parse(runtimeStdout.at(-1) ?? "{}") as {
1251+
workspaceDir?: string;
1252+
eligible?: string[];
1253+
missingRequirements?: Array<{ name: string }>;
1254+
};
1255+
expect(output.workspaceDir).toBe("/gateway/workspace-writer");
1256+
expect(output.eligible).toEqual(["apple-notes"]);
1257+
expect(output.missingRequirements).toEqual([]);
1258+
});
1259+
11981260
it.each([
11991261
["list", ["skills", "list", "--agent", "writer", "--json"]],
12001262
["info", ["skills", "info", "calendar", "--agent", "writer", "--json"]],

src/cli/skills-cli.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// Skills CLI for workspace status, install/update, ClawHub verification, and workshop proposals.
22
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
33
import type { Command } from "commander";
4+
import {
5+
GATEWAY_CLIENT_MODES,
6+
GATEWAY_CLIENT_NAMES,
7+
} from "../../packages/gateway-protocol/src/client-info.js";
48
import { formatDocsLink } from "../../packages/terminal-core/src/links.js";
59
import { theme } from "../../packages/terminal-core/src/theme.js";
610
import {
@@ -69,6 +73,10 @@ type ResolveSkillsWorkspaceOptions = {
6973
cwd?: string;
7074
};
7175

76+
type ResolvedSkillsWorkspace = ReturnType<typeof resolveSkillsWorkspace>;
77+
78+
const GATEWAY_SKILLS_STATUS_TIMEOUT_MS = 1_500;
79+
7280
function resolveSkillsWorkspace(options?: ResolveSkillsWorkspaceOptions): {
7381
config: ReturnType<typeof getRuntimeConfig>;
7482
workspaceDir: string;
@@ -95,12 +103,37 @@ function resolveAgentOption(
95103
return resolveOptionFromCommand<string>(command, "agent") ?? opts?.agent;
96104
}
97105

106+
async function loadGatewaySkillsStatusReport(
107+
resolved: ResolvedSkillsWorkspace,
108+
): Promise<SkillStatusReport | null> {
109+
try {
110+
const { callGateway } = await import("../gateway/call.js");
111+
return await callGateway<SkillStatusReport>({
112+
config: resolved.config,
113+
method: "skills.status",
114+
params: { agentId: resolved.agentId },
115+
timeoutMs: GATEWAY_SKILLS_STATUS_TIMEOUT_MS,
116+
clientName: GATEWAY_CLIENT_NAMES.CLI,
117+
mode: GATEWAY_CLIENT_MODES.CLI,
118+
});
119+
} catch {
120+
return null;
121+
}
122+
}
123+
98124
async function loadSkillsStatusReport(
99125
options?: ResolveSkillsWorkspaceOptions,
100126
): Promise<SkillStatusReport> {
101-
const { config, workspaceDir, agentId } = resolveSkillsWorkspace(options);
127+
const resolved = resolveSkillsWorkspace(options);
128+
const gatewayReport = await loadGatewaySkillsStatusReport(resolved);
129+
if (gatewayReport) {
130+
return gatewayReport;
131+
}
102132
const { buildWorkspaceSkillStatus } = await import("../skills/discovery/status.js");
103-
return buildWorkspaceSkillStatus(workspaceDir, { config, agentId });
133+
return buildWorkspaceSkillStatus(resolved.workspaceDir, {
134+
config: resolved.config,
135+
agentId: resolved.agentId,
136+
});
104137
}
105138

106139
async function runSkillsAction(

0 commit comments

Comments
 (0)