refactor(inventory): 优化技能与代理配置代码格式和检测逻辑

- 统一代码格式,增加多处代码块的换行和缩进保持一致
- 调整技能安装目标列表的格式,提升可读性
- 修复解压缩逻辑中异常抛出格式,增强异常信息规范
- 优化归一化文件名过滤条件表达式格式
- 修改配置文件检测逻辑,兼容环境变量和旧版配置方案
- 增强对 Bailian 相关模型提供者的检测逻辑支持
- 规范代理详情字段生成方法的代码风格
- 调整 MCP 写回相关函数的格式,提升可维护性
- 改进技能和代理详情函数参数格式,统一参数拆分显示
- 修复单元测试中路径和 JSON 写入格式,增加不同配置场景测试覆盖
- 确保软链接技能目录被正确识别为安装来源
- 增加多代理配置文件和技能安装的检测测试用例,提升测试精准度
This commit is contained in:
lisheng.lisheng
2026-07-28 20:51:07 +08:00
parent adc89f635d
commit 2c53b0692b
2 changed files with 138 additions and 21 deletions
@@ -144,7 +144,10 @@ function skillRoots(home: string): Array<{ source: string; dir: string }> {
{ source: "openclaw", dir: join(home, ".openclaw", "workspace", "skills") },
{ source: "hermes", dir: join(home, ".hermes", "skills") },
{ source: "gemini", dir: join(home, ".gemini", "skills") },
{ source: "antigravity", dir: join(home, ".gemini", "antigravity", "skills") },
{
source: "antigravity",
dir: join(home, ".gemini", "antigravity", "skills"),
},
{ source: "windsurf", dir: join(home, ".windsurf", "skills") },
{ source: "windsurf", dir: join(home, ".codeium", "windsurf", "skills") },
{ source: "qoderwork", dir: join(home, ".qoderwork", "skills") },
@@ -228,21 +231,43 @@ export function getSkillDetail(id: string, home: string = homedir()): SkillDetai
// ---- Skill install (upload a .zip and unpack it into a skill root) ----
/** Allow-listed skill install targets: source id -> label + path segments. */
const SKILL_INSTALL_TARGETS: Array<{ source: string; label: string; sub: string[] }> = [
{ source: "global", label: "All agents (~/.agents/skills)", sub: [".agents", "skills"] },
const SKILL_INSTALL_TARGETS: Array<{
source: string;
label: string;
sub: string[];
}> = [
{
source: "global",
label: "All agents (~/.agents/skills)",
sub: [".agents", "skills"],
},
{ source: "claude-code", label: "Claude Code", sub: [".claude", "skills"] },
{ source: "qwen-code", label: "Qwen Code", sub: [".qwen", "skills"] },
{ source: "codex", label: "Codex", sub: [".codex", "skills"] },
{ source: "opencode", label: "OpenCode", sub: [".config", "opencode", "skills"] },
{
source: "opencode",
label: "OpenCode",
sub: [".config", "opencode", "skills"],
},
{ source: "openclaw", label: "OpenClaw", sub: [".openclaw", "skills"] },
{ source: "qoderwork", label: "QoderWork", sub: [".qoderwork", "skills"] },
{ source: "windsurf", label: "Windsurf", sub: [".codeium", "windsurf", "skills"] },
{
source: "windsurf",
label: "Windsurf",
sub: [".codeium", "windsurf", "skills"],
},
{ source: "gemini", label: "Gemini", sub: [".gemini", "skills"] },
];
/** The list of install targets exposed to the UI (source + human label). */
export function skillInstallTargets(): Array<{ source: string; label: string }> {
return SKILL_INSTALL_TARGETS.map((t) => ({ source: t.source, label: t.label }));
export function skillInstallTargets(): Array<{
source: string;
label: string;
}> {
return SKILL_INSTALL_TARGETS.map((t) => ({
source: t.source,
label: t.label,
}));
}
function skillInstallRoot(source: string, home: string): string | null {
@@ -511,7 +536,11 @@ function mcpWriteTarget(source: string, scope: string, home: string): McpWriteTa
projectScoped: false,
};
if (source === "cursor")
return { file: join(home, ".cursor", "mcp.json"), mapKey: "mcpServers", projectScoped: false };
return {
file: join(home, ".cursor", "mcp.json"),
mapKey: "mcpServers",
projectScoped: false,
};
if (source === "windsurf")
return {
file: join(home, ".codeium", "windsurf", "mcp_config.json"),
@@ -537,7 +566,11 @@ function mcpWriteTarget(source: string, scope: string, home: string): McpWriteTa
projectScoped: false,
};
if (source === "claude-desktop")
return { file: claudeDesktopConfigPath(home), mapKey: "mcpServers", projectScoped: false };
return {
file: claudeDesktopConfigPath(home),
mapKey: "mcpServers",
projectScoped: false,
};
return null;
}
@@ -643,9 +676,14 @@ const AGENT_PROBES: AgentProbe[] = [
{
id: "claude-code",
label: "Claude Code",
paths: (h) => [join(h, ".claude", "settings.json"), join(h, ".claude.json")],
// The agent writer honors CLAUDE_CONFIG_DIR, so probe the same location.
paths: (h) => [
join(process.env.CLAUDE_CONFIG_DIR || join(h, ".claude"), "settings.json"),
join(h, ".claude.json"),
],
detect: (h) => {
const env = asRecord(readJsonSafe(join(h, ".claude", "settings.json"))?.env);
const configDir = process.env.CLAUDE_CONFIG_DIR || join(h, ".claude");
const env = asRecord(readJsonSafe(join(configDir, "settings.json"))?.env);
const baseUrl =
env && typeof env.ANTHROPIC_BASE_URL === "string" ? env.ANTHROPIC_BASE_URL : undefined;
const model =
@@ -660,9 +698,19 @@ const AGENT_PROBES: AgentProbe[] = [
detect: (h) => {
const settings = readJsonSafe(join(h, ".qwen", "settings.json"));
const providers = asRecord(settings?.modelProviders);
// The writer brands entries either "bailian-cli" (legacy) or with a
// "[Bailian] <model>" display name.
const hasBailian = providers
? Object.values(providers).some(
(list) => Array.isArray(list) && list.some((e) => asRecord(e)?.name === "bailian-cli"),
(list) =>
Array.isArray(list) &&
list.some((entry) => {
const name = asRecord(entry)?.name;
return (
typeof name === "string" &&
(name === "bailian-cli" || name.startsWith("[Bailian]"))
);
}),
)
: false;
const model = asRecord(settings?.model);
@@ -718,10 +766,21 @@ const AGENT_PROBES: AgentProbe[] = [
return { configured: false };
}
const providers = Array.isArray(config?.custom_providers) ? config.custom_providers : [];
const configured = providers.some((p) => asRecord(p)?.name === "bailian-cli");
const legacyConfigured = providers.some(
(provider) => asRecord(provider)?.name === "bailian-cli",
);
// The writer now emits the official flat `model.*` block (provider
// "custom" + a DashScope/Token Plan base_url); keep detecting legacy
// custom_providers entries written by older CLI versions.
const model = asRecord(config?.model);
const flatConfigured = Boolean(
model &&
model.provider === "custom" &&
typeof model.base_url === "string" &&
model.base_url.includes("aliyuncs.com"),
);
return {
configured,
configured: legacyConfigured || flatConfigured,
model: model && typeof model.default === "string" ? model.default : undefined,
};
},
@@ -937,7 +996,11 @@ export function getAgentDetail(id: string, home: string = homedir()): AgentDetai
const settings: AgentSettingsFile[] = installed
? paths
.filter((p) => existsSync(p))
.map((p) => ({ path: p, lang: langForPath(p), text: readText(p) ?? "" }))
.map((p) => ({
path: p,
lang: langForPath(p),
text: readText(p) ?? "",
}))
.filter((s) => s.text.trim())
: [];
return {
+60 -6
View File
@@ -110,15 +110,27 @@ test("listMcpServers 汇总 codex(toml) 与 claude(json) 的 MCP 定义", () =>
".claude.json",
JSON.stringify({
mcpServers: { web: { url: "https://example.com/mcp", type: "sse" } },
projects: { "/proj": { mcpServers: { local: { command: "python", args: ["s.py"] } } } },
projects: {
"/proj": {
mcpServers: { local: { command: "python", args: ["s.py"] } },
},
},
}),
);
const servers = listMcpServers(home);
const byName = Object.fromEntries(servers.map((s) => [s.name, s]));
expect(byName.repl).toMatchObject({ source: "codex", transport: "stdio", origin: "local" });
expect(byName.repl).toMatchObject({
source: "codex",
transport: "stdio",
origin: "local",
});
expect(byName.repl.detail).toContain("node repl.js");
expect(byName.web).toMatchObject({ source: "claude-code", transport: "sse", scope: "global" });
expect(byName.web).toMatchObject({
source: "claude-code",
transport: "sse",
scope: "global",
});
expect(byName.local).toMatchObject({
source: "claude-code",
transport: "stdio",
@@ -139,10 +151,35 @@ test("listAgents 报告安装与已连接 bailian-cli 的状态", () => {
write(
home,
".claude/settings.json",
JSON.stringify({ env: { ANTHROPIC_BASE_URL: "https://x", ANTHROPIC_MODEL: "qwen3-max" } }),
JSON.stringify({
env: { ANTHROPIC_BASE_URL: "https://x", ANTHROPIC_MODEL: "qwen3-max" },
}),
);
// Codex: installed but NOT configured (no bailian-cli provider).
write(home, ".codex/config.toml", 'model = "gpt-5"\n');
// Qwen Code: configured via the new "[Bailian] <model>" display name.
write(
home,
".qwen/settings.json",
JSON.stringify({
modelProviders: {
openai: [{ id: "qwen3-coder-plus", name: "[Bailian] qwen3-coder-plus" }],
},
model: { name: "qwen3-coder-plus" },
}),
);
// Hermes: configured via the official flat model block (no custom_providers).
write(
home,
".hermes/config.yaml",
[
"model:",
" default: qwen3-max",
" provider: custom",
" base_url: https://dashscope.aliyuncs.com/compatible-mode/v1",
" api_key: sk-test",
].join("\n"),
);
const agents = listAgents(home);
const byId = Object.fromEntries(agents.map((a) => [a.id, a]));
@@ -153,8 +190,25 @@ test("listAgents 报告安装与已连接 bailian-cli 的状态", () => {
model: "qwen3-max",
origin: "local",
});
expect(byId.codex).toMatchObject({ installed: true, configured: false, model: "gpt-5" });
expect(byId.opencode).toMatchObject({ installed: false, configured: false });
expect(byId.codex).toMatchObject({
installed: true,
configured: false,
model: "gpt-5",
});
expect(byId["qwen-code"]).toMatchObject({
installed: true,
configured: true,
model: "qwen3-coder-plus",
});
expect(byId.hermes).toMatchObject({
installed: true,
configured: true,
model: "qwen3-max",
});
expect(byId.opencode).toMatchObject({
installed: false,
configured: false,
});
// Always reports all six known frameworks.
expect(agents).toHaveLength(6);
});