copy(contracts): 设置校验、事件说明与 API 摘要统一术语

恢复水位 / 硬阈值 → 恢复线 / 暂停线;库根、哨兵、Feed、快照、建档、纳管等按术语表改。

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-07 01:52:24 +08:00
1 parent 0ba947dce3
commit b752ce26f7
13 files changed
+44 -44

No files matched your search

+3 -3
View File
@@ -164,7 +164,7 @@ export const contentApi = {
// ── 文件(只读;写入只经整理/对账流程,不开放直接改) ── // ── 文件(只读;写入只经整理/对账流程,不开放直接改) ──
mediaAssets: crud( mediaAssets: crud(
"/media-assets", "/media-assets",
"资产", "文件",
{ {
row: assets.MediaAsset, row: assets.MediaAsset,
filters: { mediaEntityId: Id.optional(), status: assets.AssetStatus.optional() }, filters: { mediaEntityId: Id.optional(), status: assets.AssetStatus.optional() },
@@ -173,7 +173,7 @@ export const contentApi = {
), ),
assetUnitCoverage: crud( assetUnitCoverage: crud(
"/asset-unit-coverage", "/asset-unit-coverage",
"资产覆盖", "文件覆盖",
{ {
row: assets.AssetUnitCoverage, row: assets.AssetUnitCoverage,
filters: { mediaAssetId: Id.optional(), contentUnitId: Id.optional() }, filters: { mediaAssetId: Id.optional(), contentUnitId: Id.optional() },
@@ -182,7 +182,7 @@ export const contentApi = {
), ),
assetLocations: crud( assetLocations: crud(
"/asset-locations", "/asset-locations",
"资产位置", "文件位置",
{ {
row: assets.AssetLocation, row: assets.AssetLocation,
filters: { mediaAssetId: Id.optional(), status: assets.LocationStatus.optional() }, filters: { mediaAssetId: Id.optional(), status: assets.LocationStatus.optional() },
+2 -2
View File
@@ -41,7 +41,7 @@ export const executionApi = {
method: "get", method: "get",
path: "/tasks/{id}/operations", path: "/tasks/{id}/operations",
scope: "read", scope: "read",
summary: "任务的操作账本(业务时间线)", summary: "任务的操作记录(业务时间线)",
params: IdParam, params: IdParam,
response: z.object({ items: z.array(execution.AssetOperation) }), response: z.object({ items: z.array(execution.AssetOperation) }),
}), }),
@@ -86,7 +86,7 @@ export const executionApi = {
path: "/acquisition-plans/{id}/cancel", path: "/acquisition-plans/{id}/cancel",
scope: "organize.execute", scope: "organize.execute",
summary: summary:
"取消还没提交到下载器的计划(待批准 / 已预留):释放收录版本与预算预留;已有下载任务的返回 409。待批准的计划请拒绝审批(拒绝后计划自动取消)", "取消还没提交到下载器的计划(待批准 / 已预留):释放收录版本与预算预留;已有下载任务的返回 409。待批准的计划用拒绝审批(拒绝后计划自动取消)",
params: IdParam, params: IdParam,
response: execution.AcquisitionPlan, response: execution.AcquisitionPlan,
}), }),
+3 -3
View File
@@ -9,7 +9,7 @@ export const instancesApi = {
libraryRoots: { libraryRoots: {
...crud( ...crud(
"/library-roots", "/library-roots",
"库根", "媒体库目录",
{ {
row: instances.LibraryRoot, row: instances.LibraryRoot,
create: instances.LibraryRootCreate, create: instances.LibraryRootCreate,
@@ -17,8 +17,8 @@ export const instancesApi = {
}, },
R, R,
), ),
test: instanceTest("/library-roots/{id}/test", "检查库根在线(目录 + 哨兵文件)"), test: instanceTest("/library-roots/{id}/test", "检查媒体库目录在线(目录 + 标记文件)"),
remove: del("/library-roots/{id}", "删除库根"), remove: del("/library-roots/{id}", "删除媒体库目录"),
}, },
downloaderInstances: { downloaderInstances: {
...crud( ...crud(
+7 -7
View File
@@ -84,7 +84,7 @@ export const libraryApi = {
method: "get", method: "get",
path: "/library", path: "/library",
scope: "read", scope: "read",
summary: "媒体库列表(已登记资产或正在追踪的作品)", summary: "媒体库列表(有已整理文件或正在追踪的作品)",
query: organization.LibraryListQuery, query: organization.LibraryListQuery,
response: cursorPage(organization.LibraryItem), response: cursorPage(organization.LibraryItem),
}), }),
@@ -92,7 +92,7 @@ export const libraryApi = {
method: "get", method: "get",
path: "/library/{id}", path: "/library/{id}",
scope: "read", scope: "read",
summary: "作品详情:实际路径、身份映射、元数据来源、各收录版本、候选拒绝理由、下载历史与校验结果", summary: "作品详情:实际路径、身份映射、元数据来源、各收录版本、资源被拒原因、下载历史与校验结果",
params: IdParam, params: IdParam,
response: organization.LibraryDetail, response: organization.LibraryDetail,
}), }),
@@ -100,7 +100,7 @@ export const libraryApi = {
method: "get", method: "get",
path: "/library/{id}/versions", path: "/library/{id}/versions",
scope: "read", scope: "read",
summary: "作品的已整理版本:每季每集的本地文件版本与规格、季 / 作品汇总、与版本目标的比对", summary: "作品的已整理版本:每季每集的本地文件版本与规格、季 / 作品汇总、与收录版本的比对",
params: IdParam, params: IdParam,
query: libraryVersions.LibraryVersionsQuery, query: libraryVersions.LibraryVersionsQuery,
response: libraryVersions.LibraryVersions, response: libraryVersions.LibraryVersions,
@@ -118,7 +118,7 @@ export const libraryApi = {
method: "post", method: "post",
path: "/library/reconcile", path: "/library/reconcile",
scope: "admin", scope: "admin",
summary: "立即做一轮资产对账(库根哨兵 + 增量 stat;不删记录、不补下载)", summary: "立即做一轮文件核对(媒体库目录标记文件 + 增量 stat;不删记录、不补下载)",
body: z.object({ libraryRootId: z.uuid().optional() }), body: z.object({ libraryRootId: z.uuid().optional() }),
response: z.object({ runId: z.uuid(), stats: z.record(z.string(), z.unknown()) }), response: z.object({ runId: z.uuid(), stats: z.record(z.string(), z.unknown()) }),
}), }),
@@ -146,7 +146,7 @@ export const libraryApi = {
method: "get", method: "get",
path: "/dashboard", path: "/dashboard",
scope: "read", scope: "read",
summary: "首页聚合:自动化概览/当前工作/需要我处理/缓存与保留/来源健康/最近产出", summary: "首页聚合:自动化概览 / 当前工作 / 需要处理 / 缓存与保留 / 站点健康 / 最近产出",
response: organization.Dashboard, response: organization.Dashboard,
}), }),
}, },
@@ -164,7 +164,7 @@ export const libraryApi = {
method: "post", method: "post",
path: "/download-cache/cleanup", path: "/download-cache/cleanup",
scope: "admin", scope: "admin",
summary: "立即按阈值清理下载缓存(只清已核验整理且满足保留/H&R 的纳管任务)", summary: "立即按暂停线清理下载盘(只清已整理、满足保种与 H&R 要求的本工具管理任务)",
body: z.object({ downloaderInstanceId: z.uuid().optional() }), body: z.object({ downloaderInstanceId: z.uuid().optional() }),
response: z.object({ report: z.record(z.string(), z.unknown()) }), response: z.object({ report: z.record(z.string(), z.unknown()) }),
}), }),
@@ -203,7 +203,7 @@ export const libraryApi = {
path: "/media/tmdb/{type}/{id}", path: "/media/tmdb/{type}/{id}",
scope: "read", scope: "read",
summary: summary:
"仅 TMDB 作品的详情:TMDB 信息与季列表、本地库存对比、是否已建档、搜资源的查询词;include=mediaServers 同时查 Emby", "仅 TMDB 作品的详情:TMDB 信息与季列表、本地库存对比、是否已加入媒体库、搜资源的查询词;include=mediaServers 同时查 Emby",
params: search.TmdbMediaParams, params: search.TmdbMediaParams,
query: search.TmdbMediaQuery, query: search.TmdbMediaQuery,
response: search.TmdbMediaDetail, response: search.TmdbMediaDetail,
+4 -4
View File
@@ -141,7 +141,7 @@ export const organizeApi = {
method: "get", method: "get",
path: "/files/roots", path: "/files/roots",
scope: "organize.read", scope: "organize.read",
summary: "可浏览的根(库根 + 下载根),含实时健康与空间", summary: "可浏览的登记目录(媒体库目录 + 下载目录),含实时健康与空间",
response: files.FileRootList, response: files.FileRootList,
}), }),
list: ep({ list: ep({
@@ -204,7 +204,7 @@ export const organizeApi = {
method: "post", method: "post",
path: "/files/delete", path: "/files/delete",
scope: "organize.execute", scope: "organize.execute",
summary: "删除(默认进回收站;引用检查:做种/资产/绑定;permanent 仅 admin 会话)", summary: "删除(默认进回收站;引用检查:做种 / 已整理文件 / 绑定;permanent 仅 admin 会话)",
body: files.FilesDeleteRequest, body: files.FilesDeleteRequest,
response: files.FilesDeleteResult, response: files.FilesDeleteResult,
}), }),
@@ -262,7 +262,7 @@ export const organizeApi = {
method: "post", method: "post",
path: "/files/probe", path: "/files/probe",
scope: "organize.execute", scope: "organize.execute",
summary: "ffprobe 摘要(≤20 项;已登记资产写回 media_info,所以要 organize.execute)", summary: "ffprobe 摘要(≤20 项;已整理文件写回 media_info,所以要 organize.execute)",
body: files.FilesProbeRequest, body: files.FilesProbeRequest,
response: files.FilesProbeResult, response: files.FilesProbeResult,
}), }),
@@ -338,7 +338,7 @@ export const organizeApi = {
method: "post", method: "post",
path: "/files/suggest-name", path: "/files/suggest-name",
scope: "organize.read", scope: "organize.read",
summary: "按命名模板(作品 > 库根 > 全局)生成推荐名;识别不可信时 name=null + 原因", summary: "按命名模板(作品 > 媒体库目录 > 全局)生成推荐名;识别不可信时 name=null + 原因",
body: files.FilesSuggestNameRequest, body: files.FilesSuggestNameRequest,
response: files.FilesSuggestNameResult, response: files.FilesSuggestNameResult,
}), }),
+3 -3
View File
@@ -151,7 +151,7 @@ export const platformApi = {
path: "/naming/preview", path: "/naming/preview",
scope: "read", scope: "read",
summary: summary:
"预览:模板 + 样本(文件名 / 资源 / 资产 / 内置)→ 相对路径、段角色、错误(行列号)、提示;已有路径绑定时目录用绑定", "预览:模板 + 样本(文件名 / 资源 / 已整理文件 / 内置)→ 相对路径、段角色、错误(行列号)、提示;已有路径绑定时目录用绑定",
body: settingsNs.NamingPreviewInput, body: settingsNs.NamingPreviewInput,
response: settingsNs.NamingPreviewResult, response: settingsNs.NamingPreviewResult,
}), }),
@@ -159,7 +159,7 @@ export const platformApi = {
method: "get", method: "get",
path: "/naming/templates", path: "/naming/templates",
scope: "read", scope: "read",
summary: "全局 / 库根 / 作品三级模板与按类型的生效结果(作品 > 库根 > 全局)", summary: "全局 / 媒体库目录 / 作品三级模板与按类型的生效结果(作品 > 媒体库目录 > 全局)",
query: settingsNs.NamingTemplatesQuery, query: settingsNs.NamingTemplatesQuery,
response: settingsNs.NamingTemplatesView, response: settingsNs.NamingTemplatesView,
}), }),
@@ -175,7 +175,7 @@ export const platformApi = {
method: "put", method: "put",
path: "/library-roots/{id}/naming", path: "/library-roots/{id}/naming",
scope: "admin", scope: "admin",
summary: "库根级命名覆盖(按类型;null 清除)", summary: "媒体库目录级命名覆盖(按类型;null 清除)",
params: IdParam, params: IdParam,
body: settingsNs.LibraryRootNamingPut, body: settingsNs.LibraryRootNamingPut,
response: settingsNs.LibraryRootNamingPut, response: settingsNs.LibraryRootNamingPut,
+1 -1
View File
@@ -105,7 +105,7 @@ export const reviewApi = {
method: "post", method: "post",
path: "/agent/runs", path: "/agent/runs",
scope: "organize.execute", scope: "organize.execute",
summary: "委托目标:建 agent_run 并入队 runAgent(proposeFirst = 先看方案再执行;mode 已弃用)", summary: "委托目标:建 agent_run 并入队 runAgent(proposeFirst = 先给方案;mode 已弃用)",
body: agent.AgentRunStartInput, body: agent.AgentRunStartInput,
response: agent.AgentRun, response: agent.AgentRun,
}), }),
+2 -2
View File
@@ -41,7 +41,7 @@ export const rulesApi = {
method: "post", method: "post",
path: "/subscriptions/{id}/reset", path: "/subscriptions/{id}/reset",
scope: "admin", scope: "admin",
summary: "重置进度:取消已预留未提交、下载任务已不在的计划,清延后记录,立即重评;仍在下载的计划与已整理资产保留", summary: "重置进度:取消已预留未提交、下载任务已不在的计划,清延后记录,立即重评;仍在下载的计划与已整理文件保留",
params: IdParam, params: IdParam,
response: policy.SubscriptionResetResult, response: policy.SubscriptionResetResult,
}), }),
@@ -140,7 +140,7 @@ export const rulesApi = {
method: "get", method: "get",
path: "/subscriptions/{id}/progress", path: "/subscriptions/{id}/progress",
scope: "read", scope: "read",
summary: "订阅进度:季总集数账本、每集状态(已覆盖/在途/缺失/疑似缺集)、收录版本在途与延后", summary: "订阅进度:季总集数、每集状态(已覆盖/在途/缺失/疑似缺集)、收录版本在途与延后",
params: IdParam, params: IdParam,
response: policy.SubscriptionProgress, response: policy.SubscriptionProgress,
}), }),
+7 -7
View File
@@ -56,7 +56,7 @@ export const sourcesApi = {
method: "post", method: "post",
path: "/sites/{id}/disable", path: "/sites/{id}/disable",
scope: "admin", scope: "admin",
summary: "关闭站点(停止网络访问与新候选,历史资源仍可查)", summary: "关闭站点(停止访问站点、不再收新资源,历史资源仍可查)",
params: IdParam, params: IdParam,
response: sources.Site, response: sources.Site,
}), }),
@@ -64,7 +64,7 @@ export const sourcesApi = {
method: "post", method: "post",
path: "/sites/{id}/snapshot", path: "/sites/{id}/snapshot",
scope: "admin", scope: "admin",
summary: "立即同步站点快照(受站点限流)", summary: "立即抓取站点列表(受站点限流)",
params: IdParam, params: IdParam,
body: z.object({ maxPages: z.number().int().min(1).max(20).optional() }), body: z.object({ maxPages: z.number().int().min(1).max(20).optional() }),
response: TaskAccepted, response: TaskAccepted,
@@ -73,7 +73,7 @@ export const sourcesApi = {
method: "get", method: "get",
path: "/sites/{id}/snapshots", path: "/sites/{id}/snapshots",
scope: "read", scope: "read",
summary: "站点快照/补查记录(覆盖范围与新鲜度)", summary: "站点抓取记录(覆盖范围与更新时间)",
params: IdParam, params: IdParam,
query: CursorQuery, query: CursorQuery,
response: cursorPage(sources.SiteSnapshot), response: cursorPage(sources.SiteSnapshot),
@@ -127,7 +127,7 @@ export const sourcesApi = {
method: "get", method: "get",
path: "/sites/{id}/releases", path: "/sites/{id}/releases",
scope: "read", scope: "read",
summary: "资源列表/搜索(快照优先,必要时实时补查;标注来源与更新时间)", summary: "资源列表 / 搜索(先用上次抓取的结果,必要时去站点搜;标注来源与更新时间)",
params: IdParam, params: IdParam,
query: sources.WorkbenchReleaseQuery, query: sources.WorkbenchReleaseQuery,
response: sources.WorkbenchReleasePage, response: sources.WorkbenchReleasePage,
@@ -159,7 +159,7 @@ export const sourcesApi = {
feedSources: { feedSources: {
...crud( ...crud(
"/feed-sources", "/feed-sources",
"Feed", "RSS",
{ {
row: sources.FeedSource, row: sources.FeedSource,
create: sources.FeedSourceCreate, create: sources.FeedSourceCreate,
@@ -180,7 +180,7 @@ export const sourcesApi = {
method: "post", method: "post",
path: "/feed-sources/{id}/enable", path: "/feed-sources/{id}/enable",
scope: "admin", scope: "admin",
summary: "启用 Feed", summary: "启用 RSS",
params: IdParam, params: IdParam,
response: sources.FeedSource, response: sources.FeedSource,
}), }),
@@ -188,7 +188,7 @@ export const sourcesApi = {
method: "post", method: "post",
path: "/feed-sources/{id}/disable", path: "/feed-sources/{id}/disable",
scope: "admin", scope: "admin",
summary: "停用 Feed", summary: "停用 RSS",
params: IdParam, params: IdParam,
response: sources.FeedSource, response: sources.FeedSource,
}), }),
+1 -1
View File
@@ -24,7 +24,7 @@ export const subtitlesApi = {
method: "post", method: "post",
path: "/subtitles/apply", path: "/subtitles/apply",
scope: "organize.execute", scope: "organize.execute",
summary: "写入字幕:视频同目录、视频主干名 + 语言后缀;同名字幕先备份;登记为字幕资产并记历史", summary: "写入字幕:视频同目录、视频主干名 + 语言后缀;同名字幕先备份;登记为字幕文件并记历史",
body: subtitles.SubtitleApplyRequest, body: subtitles.SubtitleApplyRequest,
response: subtitles.SubtitleApplyResult, response: subtitles.SubtitleApplyResult,
}), }),
+6 -6
View File
@@ -346,12 +346,12 @@ export const TypedEventEnvelope = z.discriminatedUnion(
* 已有的 release.resolved 也算一条(资源识别完成)。emittedBy 是发出的模块,说明写在什么时候发。 * 已有的 release.resolved 也算一条(资源识别完成)。emittedBy 是发出的模块,说明写在什么时候发。
*/ */
export const LIFECYCLE_EVENTS: readonly { type: EventType; label: string; emittedBy: string; when: string }[] = [ export const LIFECYCLE_EVENTS: readonly { type: EventType; label: string; emittedBy: string; when: string }[] = [
{ type: "release.resolved", label: "资源识别完成", emittedBy: "sources-identity", when: "来源资源认到本地作品" }, { type: "release.resolved", label: "资源识别完成", emittedBy: "sources-identity", when: "资源识别到本地作品" },
{ {
type: "subscription.created", type: "subscription.created",
label: "订阅已建", label: "订阅已建",
emittedBy: "acquisition-organize", emittedBy: "acquisition-organize",
when: "新建订阅(含策略自动建)", when: "新建订阅(包括按策略自动建的)",
}, },
{ {
type: "subscription.updated", type: "subscription.updated",
@@ -367,8 +367,8 @@ export const LIFECYCLE_EVENTS: readonly { type: EventType; label: string; emitte
}, },
{ type: "subscription.cancelled", label: "订阅已取消", emittedBy: "acquisition-organize", when: "删除订阅" }, { type: "subscription.cancelled", label: "订阅已取消", emittedBy: "acquisition-organize", when: "删除订阅" },
{ type: "download.submitted", label: "下载已提交", emittedBy: "acquisition-organize", when: "种子交给下载器" }, { type: "download.submitted", label: "下载已提交", emittedBy: "acquisition-organize", when: "种子交给下载器" },
{ type: "download.completed", label: "下载已完成", emittedBy: "acquisition-organize", when: "下载器报告下完" }, { type: "download.completed", label: "下载已完成", emittedBy: "acquisition-organize", when: "下载器报告已下完" },
{ type: "download.failed", label: "下载失败", emittedBy: "acquisition-organize", when: "下载任务进入失败" }, { type: "download.failed", label: "下载失败", emittedBy: "acquisition-organize", when: "下载任务进入失败状态" },
{ {
type: "download.removed", type: "download.removed",
label: "下载已移除", label: "下载已移除",
@@ -393,7 +393,7 @@ export const LIFECYCLE_EVENTS: readonly { type: EventType; label: string; emitte
type: "media.watched", type: "media.watched",
label: "作品进入关注", label: "作品进入关注",
emittedBy: "sources-identity / acquisition-organize", emittedBy: "sources-identity / acquisition-organize",
when: "建订阅、开始下载、搜索认定作品", when: "新建订阅、开始下载或搜索时确认了作品",
}, },
{ {
type: "media.metadata_refreshed", type: "media.metadata_refreshed",
@@ -405,7 +405,7 @@ export const LIFECYCLE_EVENTS: readonly { type: EventType; label: string; emitte
type: "media.episode_structure_changed", type: "media.episode_structure_changed",
label: "季集结构已切换", label: "季集结构已切换",
emittedBy: "sources-identity", emittedBy: "sources-identity",
when: "作品切换剧集组", when: "作品换了季集结构",
}, },
{ {
type: "site.unreachable", type: "site.unreachable",
+1 -1
View File
@@ -38,7 +38,7 @@ const REASONS: Record<string, ReasonText> = {
entity_not_found: { text: "作品已不存在" }, entity_not_found: { text: "作品已不存在" },
no_library_root: { text: "没有可用的媒体库目录", action: "在设置 → 媒体库与目录里添加" }, no_library_root: { text: "没有可用的媒体库目录", action: "在设置 → 媒体库与目录里添加" },
library_root_offline: { text: "媒体库目录不可访问或未挂载", action: "检查挂载后重试" }, library_root_offline: { text: "媒体库目录不可访问或未挂载", action: "检查挂载后重试" },
target_dir_unavailable: { text: "整理目标目录建不出来", action: "检查目录权限或挂载,恢复后自动重试" }, target_dir_unavailable: { text: "无法创建整理目标目录", action: "检查目录权限或挂载,恢复后自动重试" },
organize_stalled: { text: "整理多次重试仍未完成", action: "检查后重新整理" }, organize_stalled: { text: "整理多次重试仍未完成", action: "检查后重新整理" },
excluded_unresolved: { text: "有视频没能确定季集或目标目录,留在原处", action: "确认季集后重新整理" }, excluded_unresolved: { text: "有视频没能确定季集或目标目录,留在原处", action: "确认季集后重新整理" },
target_dir_outside_root: { text: "目标目录不在媒体库目录里" }, target_dir_outside_root: { text: "目标目录不在媒体库目录里" },
+4 -4
View File
@@ -62,8 +62,8 @@ const CacheShape = z.object({
}); });
/** 下载缓存卷硬阈值与恢复水位(REQ §8.4.1) */ /** 下载缓存卷硬阈值与恢复水位(REQ §8.4.1) */
export const CacheSettings = CacheShape.refine((v) => v.resumePercent < v.hardThresholdPercent, { export const CacheSettings = CacheShape.refine((v) => v.resumePercent < v.hardThresholdPercent, {
message: "恢复水位必须低于硬阈值", message: "恢复线必须低于暂停线",
}).refine((v) => v.cleanupTargetPercent <= v.resumePercent, { message: "清理目标不得高于恢复水位" }); }).refine((v) => v.cleanupTargetPercent <= v.resumePercent, { message: "清理目标不能高于恢复线" });
export type CacheSettings = z.infer<typeof CacheSettings>; export type CacheSettings = z.infer<typeof CacheSettings>;
export const OrganizeMode = z.enum(["copy", "move"]); export const OrganizeMode = z.enum(["copy", "move"]);
@@ -534,7 +534,7 @@ export const StorageBudgets = z
/** Agent 会话 + 视觉证据(DATA_DIR/agent-sessions、tmp/evidence)预算(GB) */ /** Agent 会话 + 视觉证据(DATA_DIR/agent-sessions、tmp/evidence)预算(GB) */
sessionsEvidenceGb: Gb, sessionsEvidenceGb: Gb,
}) })
.refine((v) => v.pgDataWarnGb <= v.pgDataGb, { message: "PG 告警线不得高于预算" }); .refine((v) => v.pgDataWarnGb <= v.pgDataGb, { message: "PG 告警线不能高于预算" });
export type StorageBudgets = z.infer<typeof StorageBudgets>; export type StorageBudgets = z.infer<typeof StorageBudgets>;
/** 各类流水数据保留期(到期由 storage.retention.* 定时任务分批删除) */ /** 各类流水数据保留期(到期由 storage.retention.* 定时任务分批删除) */
@@ -1130,7 +1130,7 @@ export const SETTINGS_SECTIONS: readonly SettingsSectionDef[] = [
id: "agent", id: "agent",
group: "flow", group: "flow",
label: "智能体", label: "智能体",
description: "模型服务、用量与防跑飞限制、自动处理、网页搜索、MCP", description: "模型服务、用量与限制、自动处理、网页搜索、MCP",
legacyTabs: [{ tab: "pi" }], legacyTabs: [{ tab: "pi" }],
anchors: [ anchors: [
{ id: "upstream", label: "模型服务", keywords: ["默认模型服务", "订阅登录"] }, { id: "upstream", label: "模型服务", keywords: ["默认模型服务", "订阅登录"] },