feat(手动下载): 保存目录选项接口与下载前指定作品

- GET /download-save-paths:按下载器列出默认保存目录与路径映射的下载器侧目录,并给出「自动」会用的下载器(站点指定 > 默认 > 第一个)
- POST /acquisition-plans 新增 identity(本地作品或 TMDB 外部身份):转调 /source-releases/{id}/identity 建 / 绑作品并加人工锁,再按该作品下载
- 测试 harness 支持装配 agent 模块;pg 测试覆盖保存目录选项与指定身份下载

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-06 12:10:03 +08:00
1 parent fb6c76875c
commit 4bb19433ad
7 files changed
+260 -6

No files matched your search

+15 -1
View File
@@ -10,6 +10,7 @@ import { type AgentRunRow, sql } from "@umt/db";
import { hasScope } from "../../http/auth.ts";
import { ApiException } from "../../http/errors.ts";
import { planManualAcquisition } from "../acquisition-organize/services/manual-acquisition.ts";
import { InternalDispatcher } from "../search/dispatch.ts";
import type { ModuleApp, ServerContext } from "../types.ts";
import { planBinding } from "./authorize.ts";
import { cancelTask, retryTask, taskToApi } from "./capabilities.ts";
@@ -115,6 +116,7 @@ export function registerAgentRoutes(
limiter: InvokeLimiter = sharedInvokeLimiter(),
) {
const db = () => ctx.db;
const dispatch = new InternalDispatcher(app);
// ── Agent 会话 ──
route(app, Api.agentRuns.list, async ({ query }) => {
@@ -433,7 +435,19 @@ export function registerAgentRoutes(
});
// 手动资源选择不依赖订阅,提交后仍走共同下载及整理任务。
route(app, Api.acquisitionPlans.create, async ({ body, actorId, c }) => {
route(app, Api.acquisitionPlans.create, async ({ body: input, actorId, c }) => {
let body = input;
// 下载前指定了作品:走资源指定身份的同一端点(建 / 绑作品、人工锁、审计),再按该作品下载
if (body.identity) {
const { identity, ...rest } = body;
const assigned = await dispatch.call<{ mediaEntityId: string }>(
c,
"POST",
`/source-releases/${body.sourceReleaseId}/identity`,
{ ...identity, reason: "手动下载时指定" },
);
body = { ...rest, entityId: assigned.mediaEntityId };
}
const rel = await db()
.selectFrom("source_release")
.select("media_entity_id")