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

@@ -10,7 +10,7 @@ import { type FrozenPlan, planImport } from "./services/organize.ts";
import { errMessage, hashJson, jsonb, toApi, toApiList } from "./util.ts";
// 本模块领域的能力处理器(规格一律取 contracts):
// inventory.query / download.plan / download.submit / organize.plan / organize.execute / policy.preview / policy.apply
// inventory.query / downloader.list / download.plan / download.submit / organize.plan / organize.execute / policy.preview / policy.apply
// 授权(grant/approval)、幂等键与操作账本由注册中心统一处理(modules/agent/capability-registry.ts);
// 这里的处理器只调本模块服务,并保证自身幂等(崩溃后同幂等键重跑用于对账)。
@@ -159,6 +159,57 @@ export function registerAoCapabilities(registry: CapabilityRegistry, deps: () =>
} as never;
});
// FIX-7:已配置的下载器与媒体服务器(只读本地记录:启用、最近连通状态、管理的任务数;不含地址与凭证)
registry.register(capabilitySpec("downloader.list"), async (input) => {
const db = deps().ctx.db;
const [downloaders, counts, servers] = await Promise.all([
db
.selectFrom("downloader_instance")
.select(["id", "name", "kind", "enabled", "health_status", "last_seen_at", "version_info"])
.$if(!input.includeDisabled, (qb) => qb.where("enabled", "=", true))
.orderBy("name")
.execute(),
sql<{ instance_id: string; total: number; active: number; failed: number }>`
select instance_id, count(*)::int as total,
count(*) filter (where status in ('queued', 'submitting', 'downloading', 'paused', 'stalled', 'importing'))::int as active,
count(*) filter (where status in ('failed', 'missing'))::int as failed
from download_task
where managed and removed_at is null
group by instance_id`.execute(db),
db
.selectFrom("media_server_instance")
.select(["id", "name", "kind", "enabled", "health_status", "last_seen_at"])
.$if(!input.includeDisabled, (qb) => qb.where("enabled", "=", true))
.orderBy("name")
.execute(),
]);
const iso = (d: Date | null) => (d ? d.toISOString() : null);
return {
downloaders: downloaders.map((r) => {
const c = counts.rows.find((x) => x.instance_id === r.id);
const v = (r.version_info ?? {}) as { app?: unknown };
return {
id: r.id,
name: r.name,
kind: r.kind,
enabled: r.enabled,
healthStatus: r.health_status as never,
lastSeenAt: iso(r.last_seen_at),
version: typeof v.app === "string" ? v.app : null,
tasks: { total: c?.total ?? 0, active: c?.active ?? 0, failed: c?.failed ?? 0 },
};
}),
mediaServers: servers.map((r) => ({
id: r.id,
name: r.name,
kind: r.kind,
enabled: r.enabled,
healthStatus: r.health_status as never,
lastSeenAt: iso(r.last_seen_at),
})),
};
});
// AG-3:下载任务详情(按 id 或 hash);关联计划 / 资源 / 业务任务 / 整理记录 / 待处理事项
registry.register(capabilitySpec("download.inspect"), async (input) => {
const db = deps().ctx.db;
+1
View File
@@ -60,6 +60,7 @@ export const INVESTIGATE_CAPABILITIES = [
"inventory.query",
"task.inspect",
"download.inspect",
"downloader.list",
"decision.list",
"subscription.list",
"identity.resolve",