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>;