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:
rootandClaude Opus 5.5 committed 2026-10-03 20:08:20 +08:00
1 parent 45a114263b
commit 7793c7b3bc
27 files changed
+1747 -48

No files matched your search

+189 -1
View File
@@ -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>;
+44 -2
View File
@@ -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(