feat(agent): 集数待确认的智能体兜底——查看上下文 / 模拟 / 写规则三个能力,写入前强制模拟,接后台调查与授权内恢复
能力 ID 的域名允许单个下划线分词(episode_mapping)。 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
45a114263b
commit
7793c7b3bc
27 files changed
+1747
-48
No files matched your search
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: episode-mapping
|
||||
title: 集数映射
|
||||
description: 资源的集数对不上作品季表(绝对集号、连续编号、特别篇混在正片里)时,看清上下文、试一条结构化的映射规则,模拟全部通过才写入,写入后同类资源由规则自动处理。集数待确认事项的后台调查,或用户说「某部剧集数不对」「集数待确认」时用。
|
||||
triggers:
|
||||
- 集数(待确认|对不上|不对|错了?|映射|乱了)
|
||||
- (绝对|连续)(集号|编号)|集号(不对|对不上|错)
|
||||
- 映射规则
|
||||
- 认成.{0,8}第.{0,4}集|第.{0,4}集.{0,8}(认成|变成|对不上)
|
||||
capabilities:
|
||||
- episode_mapping.inspect
|
||||
- episode_mapping.simulate
|
||||
- episode_mapping.apply_rule
|
||||
- decision.list
|
||||
- catalog.lookup
|
||||
sessions: [chat, investigate, child]
|
||||
---
|
||||
|
||||
## 适用意图
|
||||
|
||||
- 后台调查「集数待确认」事项:规则、原号校验、绝对集号 / 播出日期换算都没对上,要判断这个发布组这一季该怎么映射。
|
||||
- 用户在对话里说「《药屋少女》第二季集数不对」「Lilith 的连续编号帮我对一下」「这部剧集数待确认怎么处理」。
|
||||
|
||||
## 步骤
|
||||
|
||||
1. 定位:后台调查直接用事项 ID;对话里先 decision.list 找这部作品的集数待确认事项,没有事项时用 catalog.lookup 拿作品 ID,再按作品 + 来源季 + 发布组看。
|
||||
2. 先 episode_mapping.inspect:看事项内资源的原标题、发布时间、原始季集,同组相邻资源已经怎么对上的,季表每季总集数与每集播出日期,现有规则(停用的看停用原因),以及确定性建议。
|
||||
3. 想规则,按优先级:
|
||||
- 首选 expression(偏移表达式):连续编号、整体差一个固定数,如 `EP-12` 换到第 2 季、`EP*2-1`;确定性建议给了就从它开始。
|
||||
- 其次 table(对照表):编号不规则、只有少数几集要对,逐行写 `来源集号 → 第几季第几集`。
|
||||
- 特别篇、总集篇、花絮、PV 用 special(特别篇 / 花絮 / 忽略),不要硬塞成某一集。
|
||||
- 作用域默认用事项的来源季和发布组;只有确信所有发布组都这么编号时才放宽到任意发布组。
|
||||
4. 再 episode_mapping.simulate(带事项 ID 和一两句理由):看事项内样本是否全部解决、有没有让已对上的资源变成待确认。没通过就按失败原因改规则再试,最多试三次。
|
||||
5. 通过且有把握时,episode_mapping.apply_rule 写一次(带理由:依据哪些播出日期 / 标题 / 相邻资源)。服务端会再模拟一次,不通过会拒绝。写入后同类资源由规则自动处理,不要逐集再调用。
|
||||
6. 拿不准(样本太少、几种规则都说得通、季表缺集)就不写:只读调查里模拟通过的规则会自动作为「智能体建议」留在事项上,让用户确认;说清楚还差什么证据。
|
||||
|
||||
## 需要批准
|
||||
|
||||
- episode_mapping.apply_rule(写映射规则)需要持续授权;没有授权时写一句「没做:写集数映射规则,需要你批准」,模拟通过的规则已留在待处理里可一键确认。
|
||||
- episode_mapping.inspect、episode_mapping.simulate 只读,不需要批准。
|
||||
|
||||
## 汇报模板
|
||||
|
||||
> 《药屋少女的呢喃》第 1 季 · Lilith-Raws 的 40、41 集是连续编号,已按「减 30,换到第 2 季」写入规则,两条资源分别对上第 2 季第 10、11 集,播出日期一致。
|
||||
|
||||
> 《药屋少女的呢喃》第 1 季 · Lilith-Raws 的集数还没定:模拟通过的规则是「减 30,换到第 2 季」,已作为智能体建议留在待处理里,确认后生效。
|
||||
|
||||
- 第一句写结论(写了什么规则 / 为什么没写),第二句写依据(播出日期、标题、相邻资源),需要用户做的放最后。
|
||||
|
||||
## 不要做
|
||||
|
||||
- 不要绕过模拟:没跑过 episode_mapping.simulate 或模拟没通过,不调用 episode_mapping.apply_rule。
|
||||
- 不要逐集处理,也不要为每条资源各写一条规则:一条规则覆盖这个发布组这一季。
|
||||
- 不要把特别篇、总集篇硬算成正片集号。
|
||||
- 不要改别的发布组、别的季已经对上的资源;模拟显示会让已对上的资源变成待确认就换规则。
|
||||
- 汇报里不写规则 ID、资源 ID 和能力名。
|
||||
@@ -4,7 +4,7 @@ import { CapabilityId } from "@umt/contracts";
|
||||
* 能力 ID ↔ 模型工具名 的唯一映射。
|
||||
* 上游(Anthropic/OpenAI)工具名只允许 ^[a-zA-Z0-9_-]{1,64}$,不能带点,
|
||||
* 所以 `download.submit` 对 Pi 暴露为 `download__submit`。
|
||||
* 域名部分不含下划线,反向映射按第一个 `__` 切分即可无歧义。
|
||||
* 域名部分最多含单个下划线分词(episode_mapping),不含连续下划线,反向映射按第一个 `__` 切分即可无歧义。
|
||||
*/
|
||||
const TOOL_NAME_SEPARATOR = "__";
|
||||
const TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
|
||||
|
||||
@@ -67,6 +67,17 @@ export const INTENT_ROUTES: readonly IntentRoute[] = [
|
||||
{ cap: "organize.execute", note: "执行整理" },
|
||||
],
|
||||
},
|
||||
{
|
||||
intent: "集数对不上、集数待确认(绝对集号、连续编号、特别篇)",
|
||||
steps: [
|
||||
{ cap: "episode_mapping.inspect", note: "事项内资源、同组相邻资源、季表、现有规则与确定性建议" },
|
||||
{ cap: "episode_mapping.simulate", note: "试草稿规则,看样本是否全部解决、有没有误伤已对上的资源" },
|
||||
{ cap: "episode_mapping.apply_rule", note: "模拟通过才写规则,写入后同类资源自动处理" },
|
||||
],
|
||||
tips: [
|
||||
"优先 expression(偏移表达式),其次 table(对照表),特别篇 / 花絮用 special;拿不准就不写,只把模拟通过的规则留作建议。",
|
||||
],
|
||||
},
|
||||
{
|
||||
intent: "问订阅、追更、缺集",
|
||||
steps: [
|
||||
@@ -171,7 +182,7 @@ export function renderRoutes(available: ReadonlySet<string>): string[] {
|
||||
const chain = steps.map((s) => `${s.cap}(${s.note}${needsApproval(s.cap) ? ",需要批准" : ""})`).join(" → ");
|
||||
out.push(`- ${r.intent}:${chain}`);
|
||||
for (const t of r.tips ?? []) {
|
||||
const caps = t.match(/\b[a-z]+\.[a-z_]+\b/g) ?? [];
|
||||
const caps = t.match(/\b[a-z][a-z_]*\.[a-z_]+\b/g) ?? [];
|
||||
if (caps.every((c) => !(c in CAPABILITY_CATALOG) || available.has(c))) out.push(` - ${t}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,12 @@ describe("能力 ID ↔ 工具名", () => {
|
||||
expect(capabilityIdToToolName("task.retry_later")).toBe("task__retry_later");
|
||||
expect(toolNameToCapabilityId("download__submit")).toBe("download.submit");
|
||||
expect(toolNameToCapabilityId("task__retry_later")).toBe("task.retry_later");
|
||||
for (const id of ["catalog.lookup", "policy.apply", "organize.execute"]) {
|
||||
// 域名可用单个下划线分词:按第一个 `__` 切分仍无歧义
|
||||
expect(capabilityIdToToolName("episode_mapping.apply_rule")).toBe("episode_mapping__apply_rule");
|
||||
expect(toolNameToCapabilityId("episode_mapping__apply_rule")).toBe("episode_mapping.apply_rule");
|
||||
expect(() => capabilityIdToToolName("episode__mapping.inspect")).toThrow();
|
||||
expect(() => capabilityIdToToolName("episode_.inspect")).toThrow();
|
||||
for (const id of ["catalog.lookup", "policy.apply", "organize.execute", "episode_mapping.inspect"]) {
|
||||
expect(toolNameToCapabilityId(capabilityIdToToolName(id))).toBe(id);
|
||||
}
|
||||
expect(toolNameToCapabilityId("bash")).toBeUndefined();
|
||||
|
||||
@@ -50,7 +50,7 @@ describe("加载(Pi harness loadSkills)", () => {
|
||||
|
||||
it("正文提到的能力都在 frontmatter 的 capabilities 里,且都是已知能力", () => {
|
||||
for (const s of skills) {
|
||||
const mentioned = new Set(s.content.match(/\b[a-z]+\.[a-z_]+\b/g) ?? []);
|
||||
const mentioned = new Set(s.content.match(/\b[a-z][a-z_]*\.[a-z_]+\b/g) ?? []);
|
||||
for (const m of mentioned) {
|
||||
if (!(CAPABILITY_IDS as readonly string[]).includes(m)) continue;
|
||||
expect(s.capabilities, `${s.name} 正文用了 ${m}`).toContain(m);
|
||||
@@ -99,6 +99,8 @@ const ROUTING: [string, string][] = [
|
||||
["下载进度怎么样了", "download-diagnose"],
|
||||
["帮我建个巡检,每天早上看看有没有失败的下载", "patrol-draft"],
|
||||
["每隔 6 小时检查一下站点登录", "patrol-draft"],
|
||||
["药屋少女第二季的集数对不上", "episode-mapping"],
|
||||
["Lilith 的连续编号帮我对一下", "episode-mapping"],
|
||||
];
|
||||
|
||||
describe("按意图选择", () => {
|
||||
@@ -107,6 +109,15 @@ describe("按意图选择", () => {
|
||||
expect(picked[0]?.name).toBe(name);
|
||||
});
|
||||
|
||||
it("集数待确认事项的后台调查载入「集数映射」", () => {
|
||||
const goal = "调查待处理事项 x(episode_mapping):集数待确认:药屋少女的呢喃 第 1 季 · Lilith-Raws。";
|
||||
const picked = selectSkills(skills, goal, {
|
||||
kind: "investigate",
|
||||
capabilities: ["episode_mapping.inspect", "episode_mapping.simulate", "decision.list"],
|
||||
});
|
||||
expect(picked[0]?.name).toBe("episode-mapping");
|
||||
});
|
||||
|
||||
it("闲聊不载入 Skill;最多载入 2 个", () => {
|
||||
expect(selectSkills(skills, "你好", { kind: "chat" })).toEqual([]);
|
||||
expect(selectSkills(skills, "巡检一下下载失败和订阅追更,再清理空间", { kind: "chat" }).length).toBe(2);
|
||||
|
||||
@@ -6,8 +6,25 @@ import { BrowserController, BrowserDownloadVerification, BrowserSnapshot } from
|
||||
import { CAPABILITY_CATALOG, type CapabilitySpec, type KnownCapabilityId } from "./capability.ts";
|
||||
import { Id, JsonObject, NonNegInt } from "./common.ts";
|
||||
import { MediaEntity, MediaKind } from "./content.ts";
|
||||
import {
|
||||
EpisodeMappingRuleSpec,
|
||||
EpisodeMappingRuleStatus,
|
||||
EpisodeMappingTableEntry,
|
||||
EpisodeReresolveResult,
|
||||
EpisodeResolutionMethod,
|
||||
EpisodeSpecialKind,
|
||||
EpisodeSuggestion,
|
||||
EpisodeValidationState,
|
||||
} from "./episode-mapping.ts";
|
||||
import { AcquisitionPlan, DownloadStatus, DownloadTask, Task, TaskStatus } from "./execution.ts";
|
||||
import { EnrichmentStatus, EnrichSuggestion, EnrichUsage, ExternalEntityType, ExternalIdentity } from "./identity.ts";
|
||||
import {
|
||||
EnrichmentStatus,
|
||||
EnrichSuggestion,
|
||||
EnrichUsage,
|
||||
EvidenceLevel,
|
||||
ExternalEntityType,
|
||||
ExternalIdentity,
|
||||
} from "./identity.ts";
|
||||
import { OrganizeMetadataInput, OrganizeMetadataOutput } from "./local-metadata.ts";
|
||||
import { ServerMapping } from "./ops.ts";
|
||||
import { OrganizationAction, OrganizationRecord } from "./organization.ts";
|
||||
@@ -909,6 +926,168 @@ export const WebSearchTestResult = z.object({
|
||||
});
|
||||
export type WebSearchTestResult = z.infer<typeof WebSearchTestResult>;
|
||||
|
||||
// ── episode_mapping.*:集数映射的智能体兜底(docs/plan/episode-resolution.md 批次 5) ──
|
||||
// inspect 给上下文包(有上限),simulate 试草稿规则,apply_rule 服务端强制先模拟、通过才写规则并重算。
|
||||
|
||||
const EpisodeRange = z.object({ start: z.number().int(), end: z.number().int() });
|
||||
const EpisodeSourceView = z.object({
|
||||
season: z.number().int().optional(),
|
||||
episodes: EpisodeRange.optional(),
|
||||
airDate: z.string().optional(),
|
||||
});
|
||||
/** 解析结果摘要:方式、规范季集、特别篇去向 */
|
||||
export const EpisodeResultBrief = z.object({
|
||||
method: EpisodeResolutionMethod,
|
||||
canonical: z.object({ season: z.number().int(), episodes: EpisodeRange }).nullable(),
|
||||
special: EpisodeSpecialKind.nullable(),
|
||||
});
|
||||
export type EpisodeResultBrief = z.infer<typeof EpisodeResultBrief>;
|
||||
|
||||
export const EpisodeMappingInspectInput = z.object({
|
||||
/** 集数待确认事项 ID;给了就按事项的作品 / 来源季 / 发布组取上下文 */
|
||||
decisionId: Id.optional(),
|
||||
/** 不给 decisionId 时必填 */
|
||||
mediaEntityId: Id.optional(),
|
||||
/** 来源季;不填 = 任意季 */
|
||||
season: z.number().int().min(0).max(999).nullable().optional(),
|
||||
/** 发布组;不填 = 没有发布组的资源 */
|
||||
releaseGroup: z.string().trim().max(200).nullable().optional(),
|
||||
});
|
||||
export const EpisodeInspectRelease = z.object({
|
||||
sourceReleaseId: Id,
|
||||
title: z.string(),
|
||||
publishedAt: z.string(),
|
||||
releaseGroup: z.string().nullable(),
|
||||
/** 发布者声称的季集 */
|
||||
source: EpisodeSourceView,
|
||||
/** 当前结果与依据 */
|
||||
current: EpisodeResultBrief.nullable(),
|
||||
evidence: z.array(z.string()),
|
||||
});
|
||||
export type EpisodeInspectRelease = z.infer<typeof EpisodeInspectRelease>;
|
||||
export const EpisodeMappingInspectOutput = z.object({
|
||||
entity: z.object({ id: Id, title: z.string(), kind: MediaKind, year: z.number().int().nullable() }),
|
||||
scope: z.object({ season: z.number().int().nullable(), releaseGroup: z.string().nullable() }),
|
||||
decision: z
|
||||
.object({
|
||||
id: Id,
|
||||
status: DecisionStatus,
|
||||
title: z.string(),
|
||||
summary: z.string().nullable(),
|
||||
/** 算出越界被停用的规则 */
|
||||
failedRules: z.array(z.string()),
|
||||
})
|
||||
.nullable(),
|
||||
/** 事项内资源(没有事项时 = 这个范围里当前待确认的资源),最多 50 条 */
|
||||
releases: z.array(EpisodeInspectRelease),
|
||||
/** 同作品同发布组近期的其他资源(含已对上 / 已换算的),最多 50 条 */
|
||||
neighbors: z.array(EpisodeInspectRelease),
|
||||
/** 季表:每季总集数、已知每集播出日期与标题(每季最多 100 集) */
|
||||
seasons: z.array(
|
||||
z.object({
|
||||
season: z.number().int(),
|
||||
total: z.number().int().nullable(),
|
||||
episodes: z.array(
|
||||
z.object({ episode: z.number().int(), airDate: z.string().nullable(), title: z.string().nullable() }),
|
||||
),
|
||||
}),
|
||||
),
|
||||
latestSeason: z.number().int().nullable(),
|
||||
ended: z.boolean(),
|
||||
/** 这部作品的映射规则(含停用及原因);byAgent = 智能体写的 */
|
||||
rules: z.array(
|
||||
z.object({
|
||||
id: Id,
|
||||
season: z.number().int().nullable(),
|
||||
releaseGroup: z.string().nullable(),
|
||||
kind: z.enum(["expression", "table", "special"]),
|
||||
expression: z.string().nullable(),
|
||||
targetSeason: z.number().int().nullable(),
|
||||
tableEntries: z.array(EpisodeMappingTableEntry).nullable(),
|
||||
special: EpisodeSpecialKind.nullable(),
|
||||
status: EpisodeMappingRuleStatus,
|
||||
disabledReason: z.string().nullable(),
|
||||
evidenceLevel: EvidenceLevel,
|
||||
byAgent: z.boolean(),
|
||||
/** 智能体写规则时的理由 */
|
||||
reasons: z.array(z.string()),
|
||||
}),
|
||||
),
|
||||
/** 确定性建议(按发布时间对播出日期,全部样本成立且唯一才有) */
|
||||
suggestion: EpisodeSuggestion.nullable(),
|
||||
truncated: z.object({ releases: z.boolean(), neighbors: z.boolean() }),
|
||||
});
|
||||
|
||||
export const EpisodeMappingSimulateInput = z.object({
|
||||
/** 集数待确认事项 ID:样本取事项内资源;规则的季 / 发布组不填时用事项的 */
|
||||
decisionId: Id.optional(),
|
||||
/** 不给 decisionId 时必填 */
|
||||
mediaEntityId: Id.optional(),
|
||||
/** 草稿规则(expression / table / special + 作用域) */
|
||||
rule: EpisodeMappingRuleSpec,
|
||||
/** 理由:模拟通过时作为智能体建议留在事项上(后台调查没有写规则授权时) */
|
||||
reasons: z.array(z.string().trim().min(1).max(300)).max(10).optional(),
|
||||
});
|
||||
export const EpisodeSimulationItem = z.object({
|
||||
sourceReleaseId: Id,
|
||||
title: z.string(),
|
||||
releaseGroup: z.string().nullable(),
|
||||
/** 是否是事项内样本 */
|
||||
sample: z.boolean(),
|
||||
source: EpisodeSourceView,
|
||||
before: EpisodeResultBrief.nullable(),
|
||||
after: EpisodeResultBrief,
|
||||
validation: EpisodeValidationState,
|
||||
changed: z.boolean(),
|
||||
});
|
||||
export type EpisodeSimulationItem = z.infer<typeof EpisodeSimulationItem>;
|
||||
export const EpisodeSimulationSummary = z.object({
|
||||
/** 事项内样本数 */
|
||||
samples: NonNegInt,
|
||||
/** 其中套用后解决的(非待确认、校验非 fail) */
|
||||
resolved: NonNegInt,
|
||||
/** 其中校验结果为「对得上」的 */
|
||||
passed: NonNegInt,
|
||||
/** 样本不在模拟范围内(太旧)的条数 */
|
||||
missing: NonNegInt,
|
||||
/** 当前已对上 / 已换算、套用后变成待确认的条数 */
|
||||
regressions: NonNegInt,
|
||||
/** 套用后结果有变化的条数 */
|
||||
changed: NonNegInt,
|
||||
/** 参与模拟的资源数 */
|
||||
scanned: NonNegInt,
|
||||
/** 事项内样本全部解决且没有让已对上的资源变成待确认 */
|
||||
ok: z.boolean(),
|
||||
});
|
||||
export type EpisodeSimulationSummary = z.infer<typeof EpisodeSimulationSummary>;
|
||||
export const EpisodeMappingSimulateOutput = z.object({
|
||||
rule: EpisodeMappingRuleSpec,
|
||||
summary: EpisodeSimulationSummary,
|
||||
/** 没通过的原因(通过时为空) */
|
||||
failures: z.array(z.string()),
|
||||
/** 样本 + 有变化的资源,最多 100 条 */
|
||||
items: z.array(EpisodeSimulationItem),
|
||||
/** 已作为智能体建议留在事项上 */
|
||||
suggestionRecorded: z.boolean(),
|
||||
});
|
||||
|
||||
export const EpisodeMappingApplyRuleInput = z.object({
|
||||
/** 集数待确认事项 ID */
|
||||
decisionId: Id,
|
||||
/** 草稿规则;季 / 发布组不填时用事项的 */
|
||||
rule: EpisodeMappingRuleSpec,
|
||||
/** 为什么这样映射(依据),至少一条 */
|
||||
reasons: z.array(z.string().trim().min(1).max(300)).min(1).max(10),
|
||||
});
|
||||
export const EpisodeMappingApplyRuleOutput = z.object({
|
||||
ruleId: Id,
|
||||
evidenceLevel: EvidenceLevel,
|
||||
summary: EpisodeSimulationSummary,
|
||||
reresolve: EpisodeReresolveResult,
|
||||
/** 写入后事项的状态(资源都解决时已自动关闭) */
|
||||
decisionStatus: DecisionStatus,
|
||||
});
|
||||
|
||||
/** 能力 ID → 输入/输出 schema(server 注册处理器、agent 生成工具定义时共用) */
|
||||
// ── schedule.draft_check:按自然语言需求起草巡检(建成未启用,用户在计划页检查后启用) ──
|
||||
|
||||
@@ -985,6 +1164,9 @@ export const CAPABILITY_IO = {
|
||||
"metadata.resolve_organize": { input: OrganizeMetadataInput, output: OrganizeMetadataOutput },
|
||||
"metadata.enrich": { input: MetadataEnrichInput, output: MetadataEnrichOutput },
|
||||
"schedule.draft_check": { input: ScheduleDraftCheckInput, output: ScheduleDraftCheckOutput },
|
||||
"episode_mapping.inspect": { input: EpisodeMappingInspectInput, output: EpisodeMappingInspectOutput },
|
||||
"episode_mapping.simulate": { input: EpisodeMappingSimulateInput, output: EpisodeMappingSimulateOutput },
|
||||
"episode_mapping.apply_rule": { input: EpisodeMappingApplyRuleInput, output: EpisodeMappingApplyRuleOutput },
|
||||
} as const satisfies Record<KnownCapabilityId, { input: z.ZodType; output: z.ZodType }>;
|
||||
|
||||
type CapabilityIo = typeof CAPABILITY_IO;
|
||||
@@ -1076,3 +1258,9 @@ export type WebFetchInput = z.infer<typeof WebFetchInput>;
|
||||
export type WebFetchOutput = z.infer<typeof WebFetchOutput>;
|
||||
export type MetadataEnrichInput = z.infer<typeof MetadataEnrichInput>;
|
||||
export type MetadataEnrichOutput = z.infer<typeof MetadataEnrichOutput>;
|
||||
export type EpisodeMappingInspectInput = z.infer<typeof EpisodeMappingInspectInput>;
|
||||
export type EpisodeMappingInspectOutput = z.infer<typeof EpisodeMappingInspectOutput>;
|
||||
export type EpisodeMappingSimulateInput = z.infer<typeof EpisodeMappingSimulateInput>;
|
||||
export type EpisodeMappingSimulateOutput = z.infer<typeof EpisodeMappingSimulateOutput>;
|
||||
export type EpisodeMappingApplyRuleInput = z.infer<typeof EpisodeMappingApplyRuleInput>;
|
||||
export type EpisodeMappingApplyRuleOutput = z.infer<typeof EpisodeMappingApplyRuleOutput>;
|
||||
@@ -24,8 +24,11 @@ export const MutationPlan = z.object({
|
||||
});
|
||||
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_]*$/);
|
||||
/**
|
||||
* 能力 ID:`域.动作`,如 download.submit;域名可用单个下划线分词(episode_mapping.inspect),
|
||||
* 不以下划线结尾、不含连续下划线,工具名(`域__动作`)按第一个 `__` 反向切分仍无歧义
|
||||
*/
|
||||
export const CapabilityId = z.string().regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*\.[a-z][a-z0-9_]*$/);
|
||||
export type CapabilityId = z.infer<typeof CapabilityId>;
|
||||
|
||||
// ── 能力描述符与首批能力(任务书 §3.1-9;REQ §10.3) ──
|
||||
@@ -94,6 +97,9 @@ export const CAPABILITY_IDS = [
|
||||
"metadata.enrich",
|
||||
"metadata.resolve_organize",
|
||||
"schedule.draft_check",
|
||||
"episode_mapping.inspect",
|
||||
"episode_mapping.simulate",
|
||||
"episode_mapping.apply_rule",
|
||||
] as const;
|
||||
export const KnownCapabilityId = z.enum(CAPABILITY_IDS);
|
||||
export type KnownCapabilityId = z.infer<typeof KnownCapabilityId>;
|
||||
@@ -629,6 +635,39 @@ export const CAPABILITY_CATALOG: Record<KnownCapabilityId, CapabilityDescriptor>
|
||||
false,
|
||||
"safe",
|
||||
),
|
||||
"episode_mapping.inspect": d(
|
||||
"episode_mapping.inspect",
|
||||
"看集数映射上下文",
|
||||
"集数待确认时用:按事项(或作品 + 来源季 + 发布组)取上下文——事项内资源的原标题、发布时间、原始季集与当前结果,同组近期其他资源,季表(每季总集数、每集播出日期与标题),现有规则(含停用原因),确定性建议规则。写规则前先看这个",
|
||||
"read",
|
||||
"EpisodeMappingInspect",
|
||||
"read",
|
||||
"low",
|
||||
true,
|
||||
false,
|
||||
),
|
||||
"episode_mapping.simulate": d(
|
||||
"episode_mapping.simulate",
|
||||
"模拟映射规则",
|
||||
"试一条草稿映射规则(expression 偏移表达式 / table 对照表 / special 特别篇·花絮·忽略),逐条给出套用后的季集与校验,并汇总事项内样本是否全部解决、有没有让已对上的资源变成待确认。不写规则;智能体会话里带 decisionId 且模拟通过时,规则和理由会作为智能体建议留在事项上,由用户确认",
|
||||
"preview",
|
||||
"EpisodeMappingSimulate",
|
||||
"read",
|
||||
"low",
|
||||
true,
|
||||
false,
|
||||
),
|
||||
"episode_mapping.apply_rule": d(
|
||||
"episode_mapping.apply_rule",
|
||||
"写映射规则",
|
||||
"把草稿映射规则写进作品并按作品重算资源的集数。服务端先强制模拟:事项内样本必须全部解决、已对上的资源不能变成待确认,否则拒绝不写。先用 episode_mapping.simulate 确认通过再调用,每个事项只写一次;拿不准就不要写",
|
||||
"execute",
|
||||
"EpisodeMappingApplyRule",
|
||||
"organize.execute",
|
||||
"medium",
|
||||
false,
|
||||
true,
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -691,6 +730,9 @@ export const CAPABILITY_ACTIVITY: Record<KnownCapabilityId, CapabilityActivity>
|
||||
"web.fetch": act("读", "网页", "个"),
|
||||
"metadata.resolve_organize": act("补全", "整理元数据"),
|
||||
"metadata.enrich": act("补全", "元数据"),
|
||||
"episode_mapping.inspect": act("查", "集数映射"),
|
||||
"episode_mapping.simulate": act("模拟", "映射规则", "次"),
|
||||
"episode_mapping.apply_rule": act("写入", "映射规则", "条"),
|
||||
};
|
||||
|
||||
function d(
|
||||
|
||||
Reference in new issue
Block a user