fix: skills list should prefer cwd-matched workspace over default agent

When running 'openclaw skills list' from a directory that belongs to
a configured agent's workspace, the CLI now correctly infers the
agent from the current working directory instead of always using
the default agent's workspace.

Changes:
- loadSkillsStatusReport() now calls resolveAgentIdByWorkspacePath()
  with process.cwd() before falling back to resolveDefaultAgentId()
- Added regression test for cwd-based agent inference

Fixes #48266
This commit is contained in:
root 2026-03-16 14:58:12 +00:00
parent f8bcfb9d73
commit f4db93176e
2 changed files with 26 additions and 2 deletions

View File

@ -2,6 +2,7 @@ import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const loadConfigMock = vi.fn();
const resolveAgentIdByWorkspacePathMock = vi.fn();
const resolveAgentWorkspaceDirMock = vi.fn();
const resolveDefaultAgentIdMock = vi.fn();
const buildWorkspaceSkillStatusMock = vi.fn();
@ -20,6 +21,7 @@ vi.mock("../config/config.js", () => ({
}));
vi.mock("../agents/agent-scope.js", () => ({
resolveAgentIdByWorkspacePath: resolveAgentIdByWorkspacePathMock,
resolveAgentWorkspaceDir: resolveAgentWorkspaceDirMock,
resolveDefaultAgentId: resolveDefaultAgentIdMock,
}));
@ -60,6 +62,7 @@ describe("registerSkillsCli", () => {
beforeEach(() => {
vi.clearAllMocks();
loadConfigMock.mockReturnValue({ gateway: {} });
resolveAgentIdByWorkspacePathMock.mockReturnValue(undefined);
resolveDefaultAgentIdMock.mockReturnValue("main");
resolveAgentWorkspaceDirMock.mockReturnValue("/tmp/workspace");
buildWorkspaceSkillStatusMock.mockReturnValue(report);
@ -71,6 +74,8 @@ describe("registerSkillsCli", () => {
it("runs list command with resolved report and formatter options", async () => {
await runCli(["skills", "list", "--eligible", "--verbose", "--json"]);
expect(resolveAgentIdByWorkspacePathMock).toHaveBeenCalledWith({ gateway: {} }, process.cwd());
expect(resolveAgentWorkspaceDirMock).toHaveBeenCalledWith({ gateway: {} }, "main");
expect(buildWorkspaceSkillStatusMock).toHaveBeenCalledWith("/tmp/workspace", {
config: { gateway: {} },
});
@ -85,6 +90,19 @@ describe("registerSkillsCli", () => {
expect(runtime.log).toHaveBeenCalledWith("skills-list-output");
});
it("prefers the agent inferred from cwd over the default agent", async () => {
resolveAgentIdByWorkspacePathMock.mockReturnValue("ops");
resolveAgentWorkspaceDirMock.mockReturnValue("/tmp/ops-workspace");
await runCli(["skills", "list"]);
expect(resolveAgentWorkspaceDirMock).toHaveBeenCalledWith({ gateway: {} }, "ops");
expect(resolveDefaultAgentIdMock).not.toHaveBeenCalled();
expect(buildWorkspaceSkillStatusMock).toHaveBeenCalledWith("/tmp/ops-workspace", {
config: { gateway: {} },
});
});
it("runs info command and forwards skill name", async () => {
await runCli(["skills", "info", "peekaboo", "--json"]);

View File

@ -1,5 +1,9 @@
import type { Command } from "commander";
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
import {
resolveAgentIdByWorkspacePath,
resolveAgentWorkspaceDir,
resolveDefaultAgentId,
} from "../agents/agent-scope.js";
import { loadConfig } from "../config/config.js";
import { defaultRuntime } from "../runtime.js";
import { formatDocsLink } from "../terminal/links.js";
@ -19,7 +23,9 @@ type SkillStatusReport = Awaited<
async function loadSkillsStatusReport(): Promise<SkillStatusReport> {
const config = loadConfig();
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
const agentId =
resolveAgentIdByWorkspacePath(config, process.cwd()) ?? resolveDefaultAgentId(config);
const workspaceDir = resolveAgentWorkspaceDir(config, agentId);
const { buildWorkspaceSkillStatus } = await import("../agents/skills-status.js");
return buildWorkspaceSkillStatus(workspaceDir, { config });
}