feat(agent): downloader.list 能力与汇报计数规则(FIX-7)

智能体原先只能从 site.list 推断下载器,会把没看到汇报成下载器不在系统内。
新增只读能力 downloader.list(下载器与媒体服务器、连通状态、管理的任务数);
原因码表补能力错误、下载盘判定、站点登录与待处理类型,带分类前缀的码按后半段查;
汇报格式加「数量以工具返回为准」。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 23:59:51 +08:00
1 parent 68f53390f6
commit 1b22bb5131
12 files changed
+163 -9

No files matched your search

+12 -1
View File
@@ -4,7 +4,7 @@ import { resolveBusiness } from "../../src/modules/auth-settings/engine-business
import { createE2E, type E2E, pgAvailable, runEvents, runRow, seedBase, seedMovieTarget, waitFor } from "./harness.ts";
// E15 Agent 质量(AG-3,来自 AGENT-REAL 真实上游验证):
// - 只读摘要能力 subscription.list / decision.list / download.inspect(按 id 或 hash);task.inspect 收到下载任务 ID
// - 只读摘要能力 subscription.list / decision.list / download.inspect(按 id 或 hash)/ downloader.list;task.inspect 收到下载任务 ID
// 时明确报错并指向 download.inspect;
// - download.plan 的 requiresApproval 按授权范围判定(覆盖本作品的持续授权 → false;范围外 / 没有 → true);
// - 超预算:run = budget_exceeded,任务页(引擎 run 的 business.outcome)显示超出预算而不是完成;
@@ -23,10 +23,12 @@ describe.skipIf(!hasPg)("E15 Agent 质量(fake 模型 + 真实能力)", () => {
let e: E2E;
let target: Awaited<ReturnType<typeof seedMovieTarget>>;
let downloadTaskId: string;
let instanceId: string;
beforeAll(async () => {
e = await createE2E("e15", { fakeScripts: scripts });
const base = await seedBase(e);
instanceId = base.instanceId;
target = await seedMovieTarget(e, base.siteId);
downloadTaskId = (
await e.db
@@ -64,6 +66,7 @@ describe.skipIf(!hasPg)("E15 Agent 质量(fake 模型 + 真实能力)", () => {
text: "按 hash 查下载。",
toolCalls: [{ capability: "download.inspect", input: { hash: HASH.toUpperCase() } }],
},
{ text: "看下载器。", toolCalls: [{ capability: "downloader.list", input: {} }] },
// 常见误用:把下载任务 ID 当业务任务 ID
{ text: "看任务时间线。", toolCalls: [{ capability: "task.inspect", input: { taskId: downloadTaskId } }] },
],
@@ -124,6 +127,14 @@ describe.skipIf(!hasPg)("E15 Agent 质量(fake 模型 + 真实能力)", () => {
decisions: [{ kind: "download_failed" }],
otherMatches: [],
});
// FIX-7:下载器是否接入由 downloader.list 回答(site.list 不含下载器),任务按实例计数,不含地址与凭证
expect(byCap("downloader.list")).toMatchObject({ isError: false });
expect(byCap("downloader.list").json).toMatchObject({
// 种下的失败任务在假 qB 里不存在,同步后标 removed(见上面 download.inspect),已移除的不计入
downloaders: [{ id: instanceId, kind: "qbittorrent", enabled: true, tasks: { total: 0, active: 0, failed: 0 } }],
mediaServers: [],
});
expect(byCap("downloader.list").text).not.toMatch(/baseUrl|base_url|password|secret/i);
expect(byCap("task.inspect")).toMatchObject({ isError: true });
expect(byCap("task.inspect").text).toContain("download.inspect");
@@ -45,8 +45,8 @@ describe.skipIf(!hasPg)("E2 Agent 委托闭环(fake 模型 + 真实能力/授权
return r.body.data.id as string;
};
it("启动断言:39 个能力全部注册,并在能力目录里可见", async () => {
expect(e.booted.ctx.capabilities.list()).toHaveLength(39);
it("启动断言:40 个能力全部注册,并在能力目录里可见", async () => {
expect(e.booted.ctx.capabilities.list()).toHaveLength(40);
const r = await e.api("GET", "/capabilities");
expect(r.body.data.items.map((c: { id: string }) => c.id).sort()).toEqual(
e.booted.ctx.capabilities