Files
UHD_Media_Tool/packages/contracts/src/capability.ts
T
rootandClaude Opus 5.5 fb84a71f80 style(copy): fix remaining punctuation violations and make check:copy strict
Auto-fix of 602 string literals across 74 files (half-width punctuation next to
Chinese, parenthesised Chinese, quotes); biome reflow; e2/e6 assertions follow
the new full-width summaries. check:copy now runs with --strict in ci.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 21:34:51 +08:00

459 lines
13 KiB
TypeScript

import { z } from "zod";
import { ApiScope } from "./common.ts";
/**
* 能力调用上下文(REQ §10.3)。产品自有接口,不是 Pi SDK 原生 API。
* signal 由任务取消传播而来。
*/
export interface CapabilityContext {
actorId: string;
taskId: string;
scopeIds: string[];
signal: AbortSignal;
}
/** 变更计划:由业务端生成与保存,审批绑定 id + payloadHash + expectedRevision */
export const MutationPlan = z.object({
id: z.string(),
operation: z.string(),
targetIds: z.array(z.string()),
expectedRevision: z.number().int().nonnegative(),
evidenceIds: z.array(z.string()),
payloadHash: z.string(),
requiresApproval: z.boolean(),
});
export type MutationPlan = z.infer<typeof MutationPlan>;
/** 能力 ID:`域.动作`,如 download.submit */
export const CapabilityId = z.string().regex(/^[a-z][a-z0-9]*\.[a-z][a-z0-9_]*$/);
export type CapabilityId = z.infer<typeof CapabilityId>;
// ── 能力描述符与首批能力(任务书 §3.1-9;REQ §10.3) ──
/** 风险级别:low 只读;medium 预览/可撤回写入;high 触发下载、文件变更或策略发布 */
export const RiskLevel = z.enum(["low", "medium", "high"]);
export type RiskLevel = z.infer<typeof RiskLevel>;
/**
* 接口类别(REQ §12.3),由弱到强:
* - read:只读查询
* - preview:生成预览/方案,不落任何变更
* - propose:生成并保存变更计划(MutationPlan),等待授权
* - approve:对计划作出授权决定
* - execute:产生外部副作用(下载提交、Copy/Move、策略发布…)
*/
export const CapabilityKind = z.enum(["read", "preview", "propose", "approve", "execute"]);
export type CapabilityKind = z.infer<typeof CapabilityKind>;
/** 崩溃恢复时 harness 能否用持久化参数重跑:有副作用的一律 never,只读用 safe */
export const CapabilityReplay = z.enum(["safe", "never"]);
export type CapabilityReplay = z.infer<typeof CapabilityReplay>;
/** 首批能力 ID */
export const CAPABILITY_IDS = [
"catalog.lookup",
"release.search",
"release.inspect",
"identity.resolve",
"download.plan",
"download.submit",
"download.recheck",
"organize.plan",
"organize.execute",
"task.inspect",
"task.retry",
"task.cancel",
"policy.preview",
"policy.apply",
"inventory.query",
"agent.spawn",
"agent.await",
"site.list",
"site.health_check",
"site.refresh_login",
"site.user_data",
"browser.snapshot",
"browser.navigate",
"browser.interact",
"browser.download",
"agent.plan",
"agent.propose_options",
] as const;
export const KnownCapabilityId = z.enum(CAPABILITY_IDS);
export type KnownCapabilityId = z.infer<typeof KnownCapabilityId>;
/** schema 引用:指向 contracts 中导出的 schema 名(如 "ReleaseSearchInput"),便于序列化给 UI/MCP 列表 */
export const SchemaRef = z.string().regex(/^[A-Z][A-Za-z0-9]*$/);
/**
* 能力的可序列化描述(唯一定义;处理器在 server 侧挂载,模型只能看到白名单内的描述)。
* 运行时注册用 `CapabilitySpec`(同字段,inputSchema/outputSchema 换成真实 zod schema),
* 首批能力的 spec 用 `capabilitySpec(id)` 从 CAPABILITY_CATALOG + CAPABILITY_IO 组装。
*/
export const CapabilityDescriptor = z.object({
id: CapabilityId,
version: z.number().int().min(1),
title: z.string().min(1),
description: z.string().min(1),
kind: CapabilityKind,
inputSchema: SchemaRef,
outputSchema: SchemaRef,
/** 经 HTTP/MCP 调用时所需的 API scope */
scope: ApiScope,
riskLevel: RiskLevel,
idempotent: z.boolean(),
supportsPreview: z.boolean(),
replay: CapabilityReplay,
});
export type CapabilityDescriptor = z.infer<typeof CapabilityDescriptor>;
/** 运行时能力规格:描述符字段 + 真实的输入/输出 zod schema(注册中心、Pi 工具、HTTP 共用) */
export type CapabilitySpec<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodType> = Omit<
CapabilityDescriptor,
"inputSchema" | "outputSchema"
> & { inputSchema: I; outputSchema: O };
/** 需要走授权回调(automation_grant / approval)的类别 */
export const AUTHORIZED_KINDS: readonly CapabilityKind[] = ["propose", "approve", "execute"];
/** 首批能力的描述(inputSchema/outputSchema 名对应 capabilities-io.ts 中的导出) */
export const CAPABILITY_CATALOG: Record<KnownCapabilityId, CapabilityDescriptor> = {
"catalog.lookup": d(
"catalog.lookup",
"查作品",
"按标题/别名/外部 ID 查本地作品与元数据候选",
"read",
"CatalogLookup",
"read",
"low",
true,
false,
),
"release.search": d(
"release.search",
"搜资源",
"先查本地资源索引与站点快照,不足时对已开启站点实时补查",
"read",
"ReleaseSearch",
"read",
"low",
true,
false,
),
"release.inspect": d(
"release.inspect",
"看资源详情",
"读取资源详情、文件清单、促销与解析结果",
"read",
"ReleaseInspect",
"read",
"low",
true,
false,
),
"identity.resolve": d(
"identity.resolve",
"识别身份",
"为观察/资源提出身份映射;证据充分时自动接受,有歧义时转为待处理事项",
"propose",
"IdentityResolve",
"organize.execute",
"medium",
true,
true,
),
"download.plan": d(
"download.plan",
"生成下载计划",
"评估候选,生成占用版本目标(slot)与预算预留的下载计划;不提交",
"preview",
"DownloadPlan",
"organize.execute",
"medium",
true,
true,
),
"download.submit": d(
"download.submit",
"提交下载",
"按已保存的计划提交到下载器;校验授权与硬阈值",
"execute",
"DownloadSubmit",
"organize.execute",
"high",
true,
false,
),
"download.recheck": d(
"download.recheck",
"重新校验/恢复下载",
"对已纳管的下载任务强制重新校验(action=recheck),或恢复非阈值、非人工的暂停(action=resume);不换源、不删除、不改保存路径",
"execute",
"DownloadRecheck",
"organize.execute",
"medium",
true,
false,
),
"organize.plan": d(
"organize.plan",
"生成整理计划",
"生成并保存整理计划:冻结源/目标路径、整理方式与前置条件;不执行",
"preview",
"OrganizePlan",
"organize.read",
"low",
true,
true,
),
"organize.execute": d(
"organize.execute",
"执行整理",
"执行已保存的整理计划(复制/移动),生成整理记录",
"execute",
"OrganizeExecute",
"organize.execute",
"high",
true,
false,
),
"task.inspect": d(
"task.inspect",
"查看任务",
"读取任务状态、业务时间线与错误",
"read",
"TaskInspect",
"read",
"low",
true,
false,
),
"task.retry": d(
"task.retry",
"重试任务",
"对失败/死信任务重新排队;副作用经操作账本去重",
"execute",
"TaskRetry",
"organize.execute",
"medium",
true,
false,
),
"task.cancel": d(
"task.cancel",
"取消任务",
"停止未来步骤,不撤销已完成的外部动作",
"execute",
"TaskCancel",
"organize.execute",
"medium",
true,
false,
),
"policy.preview": d(
"policy.preview",
"预览策略",
"用历史数据回放策略草案,展示新增命中与误排除",
"preview",
"PolicyPreview",
"read",
"low",
true,
true,
),
"policy.apply": d(
"policy.apply",
"发布策略",
"发布策略版本(需授权);不直接执行 JavaScript",
"execute",
"PolicyApply",
"admin",
"high",
true,
true,
),
"inventory.query": d(
"inventory.query",
"查库存",
"查询本地资产、版本目标满足情况与外部库存证据",
"read",
"InventoryQuery",
"read",
"low",
true,
false,
),
// 子代理(设置 pi.subagents 开启后才提供给模型):类别 propose——只建有界子会话,不产生外部副作用,
// "先给方案"的会话也能派生(子会话同样只给方案);授权不高于父会话,由服务端校验
"agent.spawn": d(
"agent.spawn",
"派生子会话",
"把一个有界子目标交给子会话处理:能力白名单须是本会话白名单的子集,预算从本会话剩余预算里划拨,模式不高于本会话;返回子会话 runId。await=true 时等它结束并返回汇报",
"propose",
"AgentSpawn",
"organize.execute",
"medium",
true,
false,
),
"agent.await": d(
"agent.await",
"等待子会话",
"等待本会话派生的子会话结束(等待期间不调用模型),返回各自状态、用量与汇报摘要;超时仍未结束的标为 pending",
"read",
"AgentAwait",
"read",
"low",
true,
false,
),
// W10-pre2 站点能力(W9-A 站点中心的 Agent 入口);输出一律不含凭证、Cookie 与 secret 引用
"site.list": d(
"site.list",
"列出站点",
"列出已登记站点的健康、登录状态与账号摘要(不含凭证)",
"read",
"SiteList",
"read",
"low",
true,
false,
),
// 只读站点首页并写回健康列,重跑无害:声明 replay safe
"site.health_check": d(
"site.health_check",
"检测站点",
"检测站点首页连通与登录态,写回站点健康状态",
"execute",
"SiteHealthCheck",
"admin",
"low",
true,
false,
"safe",
),
"site.refresh_login": d(
"site.refresh_login",
"刷新站点登录",
"对站点账号重新登录或校验 Cookie / API Key,成功后拉取用户数据;需持续授权或审批",
"execute",
"SiteLoginRefresh",
"admin",
"medium",
true,
false,
),
"site.user_data": d(
"site.user_data",
"查站点用户数据",
"读取站点账号的最新用户数据(上传、下载、分享率、魔力、做种)与按天历史",
"read",
"SiteUserData",
"read",
"low",
true,
false,
),
// C1-E 浏览器能力(REQ §4.4、§4.7.3):经 BrowserProvider 访问用户已有的 camofox-browser;
// 导航限站点域名,凭证由服务端注入,人工接管期间自动化动作被拒;下载产物只进后端暂存核验
"browser.snapshot": d(
"browser.snapshot",
"读取原站页面",
"读取站点浏览器会话当前页面的结构化快照(可访问性文本 + eN 元素引用,凭证已遮蔽);控制权交还或导航后必须重新读取",
"read",
"BrowserSnapshot",
"read",
"low",
true,
false,
),
"browser.navigate": d(
"browser.navigate",
"打开原站页面",
"在站点浏览器会话里打开站点域名内的地址(没有会话时按站点打开);受站点限流、超时与重试预算约束",
"execute",
"BrowserNavigate",
"admin",
"low",
false,
false,
),
"browser.interact": d(
"browser.interact",
"操作原站页面",
"在站点浏览器会话里点击 / 输入 / 选择 / 滚动 / 按键;登录字段用 credential 由服务端注入凭证;验证码或二次认证用 request_human 转人工",
"execute",
"BrowserInteract",
"admin",
"medium",
false,
false,
),
"browser.download": d(
"browser.download",
"捕获原站下载",
"捕获浏览器会话里的种子下载,交后端暂存并核验 infohash、匹配本地资源;提交下载仍用 download.plan / download.submit",
"execute",
"BrowserDownload",
"organize.execute",
"medium",
false,
false,
),
// AG-2 自动化优先的会话交互:步骤清单与方案选择。只在 Agent 会话里可用(HTTP / MCP 调用直接报错)
"agent.plan": d(
"agent.plan",
"更新步骤清单",
"开始时列出 3–7 个步骤,之后每开始、完成、跳过或失败一步就更新(只传变化的步骤,按 id 合并;replace=true 整体替换)。步骤清单实时显示在会话页顶部",
"read",
"AgentPlan",
"read",
"low",
true,
false,
),
// 类别 propose:只登记一个待用户选择的待处理事项,不产生外部副作用;等待期间会话挂起(不占 worker、不调用模型)。
// 与 agent.spawn 一样在注册时声明 replay=safe(恢复后重放本调用取用户的选择)
"agent.propose_options": d(
"agent.propose_options",
"给出方案",
"有明显取舍时给用户 2–3 个方案(各写摘要、风险、估算代价,标出推荐项),等用户选定后返回所选方案;用户也可以都不要并说明原因。超时按设置自动选推荐项或停止",
"propose",
"AgentProposeOptions",
"organize.execute",
"low",
true,
false,
),
};
function d(
id: KnownCapabilityId,
title: string,
description: string,
kind: CapabilityKind,
schemaBase: string,
scope: ApiScope,
riskLevel: RiskLevel,
idempotent: boolean,
supportsPreview: boolean,
/** 缺省按类别推导;仅对幂等且副作用可重跑的能力显式给 safe */
replay?: CapabilityReplay,
): CapabilityDescriptor {
return {
id,
version: 1,
title,
description,
kind,
inputSchema: `${schemaBase}Input`,
outputSchema: `${schemaBase}Output`,
scope,
riskLevel,
idempotent,
supportsPreview,
// 有副作用的类别一律不可重放
replay: replay ?? (kind === "read" || kind === "preview" ? "safe" : "never"),
};
}